From 93f35ca321502702000f899928b2978a8e001e27 Mon Sep 17 00:00:00 2001 From: ADS Merger Date: Thu, 30 Apr 2020 00:53:43 +0000 Subject: [PATCH 1/4] Merge from vscode 27ada910e121e23a6d95ecca9cae595fb98ab568 --- .eslintrc.json | 6 +- .vscode/launch.json | 1 - .vscode/searches/ts36031.code-search | 22 +- .vscode/settings.json | 6 +- build/.nativeignore | 7 - build/gulpfile.vscode.js | 2 +- build/lib/extensions.js | 1 + build/lib/extensions.ts | 1 + build/lib/optimize.js | 7 +- build/lib/optimize.ts | 5 - build/package.json | 2 +- build/yarn.lock | 8 +- cglicenses.json | 32 + extensions/docker/package.json | 11 +- extensions/git/package.json | 17 +- extensions/git/package.nls.json | 2 + extensions/git/src/api/api1.ts | 17 +- extensions/git/src/api/git.d.ts | 13 + extensions/git/src/askpass.sh | 4 +- extensions/git/src/askpass.ts | 77 +- extensions/git/src/commands.ts | 10 +- extensions/git/src/git.ts | 9 +- extensions/git/src/github.ts | 66 + extensions/git/src/ipc/ipcServer.ts | 35 +- extensions/git/src/main.ts | 28 +- extensions/git/src/model.ts | 9 +- extensions/git/src/repository.ts | 4 + extensions/git/src/terminal.ts | 43 + extensions/github-authentication/package.json | 16 + .../github-authentication/src/extension.ts | 4 + .../github-authentication/src/github.ts | 90 +- .../github-authentication/src/githubServer.ts | 119 +- extensions/image-preview/src/extension.ts | 2 +- extensions/image-preview/src/preview.ts | 2 +- .../json-language-features/package.json | 6 +- .../json-language-features/server/README.md | 1 + .../server/package.json | 4 +- .../server/src/jsonServerMain.ts | 14 +- .../json-language-features/server/yarn.lock | 27 +- extensions/json-language-features/yarn.lock | 25 +- .../markdown-language-features/package.json | 2 +- .../theme-defaults/themes/dark_plus.json | 2 +- .../theme-defaults/themes/light_plus.json | 2 +- extensions/vscode-account/package.json | 12 - extensions/vscode-account/src/extension.ts | 36 - extensions/vscode-colorize-tests/.gitignore | 2 - .../vscode-colorize-tests/.vscode/launch.json | 17 - .../vscode-colorize-tests/.vscode/tasks.json | 11 - extensions/vscode-colorize-tests/package.json | 59 - .../producticons/ElegantIcons.woff | Bin 63664 -> 0 bytes .../producticons/index.html | 3049 ----------------- .../producticons/mit_license.txt | 21 - .../producticons/test-product-icon-theme.json | 43 - .../src/colorizer.test.ts | 76 - .../src/colorizerTestMain.ts | 66 - extensions/vscode-colorize-tests/src/index.ts | 30 - .../src/typings/ref.d.ts | 9 - .../test/semantic-test/.vscode/settings.json | 13 - .../test/semantic-test/semantic-test.json | 9 - .../vscode-colorize-tests/tsconfig.json | 9 - extensions/vscode-colorize-tests/yarn.lock | 2016 ----------- package.json | 14 +- product.json | 1 + remote/package.json | 8 +- remote/web/package.json | 46 +- remote/web/yarn.lock | 48 +- remote/yarn.lock | 43 +- scripts/code-cli.bat | 1 - scripts/code-cli.sh | 1 - scripts/code.bat | 1 - scripts/code.sh | 1 - scripts/test-integration.sh | 16 +- .../media/scrollableSplitview.css | 2 +- .../dashboard/browser/insightRegistry.ts | 2 +- .../modelViewEditor.contribution.ts | 2 +- .../views/{customView.ts => treeView.ts} | 597 ++-- src/sql/workbench/common/views.ts | 2 +- .../contrib/charts/browser/insight.ts | 8 +- .../contrib/charts/browser/interfaces.ts | 3 +- .../dashboardNavSection.component.ts | 2 +- .../dashboard/browser/core/dashboardHelper.ts | 3 +- .../browser/core/dashboardPage.component.ts | 2 +- .../browser/dashboard.contribution.ts | 2 +- .../browser/dataExplorerExtensionPoint.ts | 12 +- .../editData/browser/editData.contribution.ts | 4 +- .../editData/browser/editDataEditor.ts | 6 +- .../extensions/browser/extensionsActions.ts | 8 +- .../notebook/browser/notebook.contribution.ts | 2 +- .../profiler/browser/profiler.contribution.ts | 2 +- .../profiler/browser/profilerEditor.ts | 2 +- .../contrib/query/browser/gridPanel.ts | 4 +- .../query/browser/query.contribution.ts | 4 +- .../contrib/query/browser/queryEditor.ts | 6 +- .../browser/queryPlan.contribution.ts | 2 +- .../tasks/browser/tasks.contribution.ts | 2 +- .../welcome/page/browser/welcomePage.ts | 40 +- .../insights/browser/insightsDialogView.ts | 28 +- .../common/languageAssociation.ts | 2 +- .../services/restore/browser/restoreDialog.ts | 8 +- src/typings/slickgrid.d.ts | 2 +- src/vs/base/browser/markdownRenderer.ts | 5 +- src/vs/base/browser/ui/aria/aria.ts | 5 + src/vs/base/browser/ui/button/button.css | 9 +- src/vs/base/browser/ui/button/button.ts | 11 +- .../codicons/codicon/codicon-animations.css | 3 +- src/vs/base/browser/ui/list/listView.ts | 10 +- src/vs/base/browser/ui/list/listWidget.ts | 1 + src/vs/base/browser/ui/menu/menu.ts | 2 +- src/vs/base/browser/ui/splitview/paneview.ts | 7 +- src/vs/base/browser/ui/tree/media/tree.css | 3 +- src/vs/base/common/filters.ts | 8 +- src/vs/base/common/fuzzyScorer.ts | 69 +- src/vs/base/common/jsonSchema.ts | 23 +- .../parts/quickinput/browser/quickInput.ts | 9 +- src/vs/base/test/common/fuzzyScorer.test.ts | 17 +- .../code/browser/workbench/workbench-dev.html | 2 +- src/vs/code/browser/workbench/workbench.html | 2 +- .../issue/issueReporterMain.ts | 4 +- .../sharedProcess/sharedProcessMain.ts | 1 + .../electron-browser/workbench/workbench.html | 1 - src/vs/code/electron-main/app.ts | 4 +- src/vs/code/electron-main/window.ts | 6 +- src/vs/code/node/cliProcessMain.ts | 1 + src/vs/editor/browser/core/editorState.ts | 17 +- .../browser/viewParts/lines/viewLines.css | 5 - .../overviewRuler/decorationsOverviewRuler.ts | 7 +- .../editor/browser/widget/diffEditorWidget.ts | 15 +- src/vs/editor/browser/widget/diffReview.ts | 7 +- .../browser/widget/media/diagonal-fill.png | Bin 185 -> 0 bytes .../browser/widget/media/diffEditor.css | 10 - src/vs/editor/common/commands/shiftCommand.ts | 19 +- src/vs/editor/common/model/textModel.ts | 101 +- src/vs/editor/common/model/tokensStore.ts | 10 +- src/vs/editor/common/model/wordHelper.ts | 10 +- src/vs/editor/common/modes.ts | 15 + src/vs/editor/common/modes/modesRegistry.ts | 13 +- .../common/services/modelServiceImpl.ts | 97 +- .../editor/common/view/editorColorRegistry.ts | 1 + .../viewModel/monospaceLineBreaksComputer.ts | 38 +- src/vs/editor/contrib/clipboard/clipboard.css | 8 - src/vs/editor/contrib/clipboard/clipboard.ts | 1 - .../contrib/codeAction/codeActionCommands.ts | 1 - .../contrib/codeAction/lightBulbWidget.ts | 3 +- src/vs/editor/contrib/format/format.ts | 4 +- src/vs/editor/contrib/gotoError/gotoError.ts | 8 +- .../test/linesOperations.test.ts | 19 + .../quickAccess/gotoSymbolQuickAccess.ts | 4 +- src/vs/editor/contrib/rename/rename.ts | 4 +- .../editor/contrib/rename/renameInputField.ts | 14 +- .../smartSelect/test/smartSelect.test.ts | 2 +- .../wordHighlighter/wordHighlighter.ts | 62 +- src/vs/editor/editor.api.ts | 2 + .../standalone/browser/simpleServices.ts | 8 + .../standalone/browser/standaloneServices.ts | 2 +- .../standalone/common/monarch/monarchLexer.ts | 40 +- .../standalone/common/monarch/monarchTypes.ts | 6 +- .../standalone/test/monarch/monarch.test.ts | 106 + .../test/browser/controller/cursor.test.ts | 22 + .../test/common/model/tokensStore.test.ts | 48 +- .../test/common/services/modelService.test.ts | 68 +- .../monospaceLineBreaksComputer.test.ts | 13 +- src/vs/loader.js | 4 + src/vs/monaco.d.ts | 4 +- .../authentication/common/authentication.ts | 1 + .../common/configurationRegistry.ts | 5 + src/vs/platform/environment/node/argv.ts | 24 +- .../environment/node/environmentService.ts | 5 + .../common/extensionGalleryService.ts | 10 +- .../common/extensionManagement.ts | 2 +- .../node/extensionDownloader.ts | 104 + .../node/extensionManagementService.ts | 12 +- .../notification/common/notification.ts | 6 +- .../telemetry/common/errorTelemetry.ts | 2 +- src/vs/platform/telemetry/common/telemetry.ts | 4 + .../telemetry/common/telemetryService.ts | 16 + .../telemetry/common/telemetryUtils.ts | 7 + .../test/browser/telemetryService.test.ts | 7 +- src/vs/platform/theme/common/colorRegistry.ts | 1 + src/vs/platform/theme/common/iconRegistry.ts | 12 +- .../common/tokenClassificationRegistry.ts | 72 +- .../userDataSync/common/settingsMerge.ts | 12 +- .../userDataSync/common/settingsSync.ts | 9 +- .../userDataSync/common/snippetsSync.ts | 2 +- .../common/userDataAutoSyncService.ts | 20 +- .../userDataSync/common/userDataSync.ts | 13 - .../test/common/settingsMerge.test.ts | 17 + .../test/common/snippetsSync.test.ts | 12 +- src/vs/vscode.d.ts | 1 + src/vs/vscode.proposed.d.ts | 355 +- .../api/browser/mainThreadAuthentication.ts | 25 +- .../api/browser/mainThreadComments.ts | 4 + .../api/browser/mainThreadDebugService.ts | 6 +- .../api/browser/mainThreadEditors.ts | 25 +- .../api/browser/mainThreadNotebook.ts | 33 +- .../api/browser/mainThreadWebview.ts | 16 +- src/vs/workbench/api/browser/media/test.svg | 3 - .../api/browser/viewsExtensionPoint.ts | 8 +- .../api/common/configurationExtensionPoint.ts | 4 + .../workbench/api/common/extHost.api.impl.ts | 21 +- .../workbench/api/common/extHost.protocol.ts | 7 +- .../api/common/extHostApiCommands.ts | 8 +- .../api/common/extHostAuthentication.ts | 9 + .../workbench/api/common/extHostComments.ts | 12 + .../api/common/extHostDebugService.ts | 6 +- .../api/common/extHostExtensionService.ts | 17 +- .../workbench/api/common/extHostNotebook.ts | 40 +- .../api/common/extHostTerminalService.ts | 30 +- src/vs/workbench/api/common/extHostTypes.ts | 11 +- src/vs/workbench/api/common/extHostWebview.ts | 107 +- .../workbench/api/node/extHostDebugService.ts | 1 + .../api/node/extHostTerminalService.ts | 21 +- .../browser/actions/layoutActions.ts | 6 + src/vs/workbench/browser/contextkeys.ts | 12 +- src/vs/workbench/browser/layout.ts | 222 +- src/vs/workbench/browser/panecomposite.ts | 4 +- .../parts/activitybar/activitybarPart.ts | 31 +- .../activitybar/media/activitybarpart.css | 3 +- .../workbench/browser/parts/compositeBar.ts | 18 +- .../browser/parts/compositeBarActions.ts | 40 +- .../browser/parts/editor/breadcrumbsPicker.ts | 1 - .../workbench/browser/parts/editor/editor.ts | 4 +- .../browser/parts/editor/editorActions.ts | 54 +- .../browser/parts/editor/editorGroupView.ts | 41 +- .../browser/parts/editor/editorStatus.ts | 1 - .../browser/parts/editor/tabsTitleControl.ts | 17 +- .../browser/parts/panel/panelPart.ts | 23 +- .../browser/parts/sidebar/sidebarPart.ts | 24 +- .../browser/parts/statusbar/statusbarPart.ts | 4 +- .../browser/parts/views/media/views.css | 1 - .../browser/parts/views/viewPaneContainer.ts | 40 +- src/vs/workbench/browser/parts/views/views.ts | 87 +- src/vs/workbench/browser/web.main.ts | 196 +- .../browser/workbench.contribution.ts | 12 +- src/vs/workbench/common/editor.ts | 4 +- src/vs/workbench/common/editor/editorGroup.ts | 10 +- src/vs/workbench/common/panecomposite.ts | 2 +- src/vs/workbench/common/views.ts | 12 +- .../inspectEditorTokens.css | 1 + .../inspectEditorTokens.ts | 8 +- .../quickaccess/gotoSymbolQuickAccess.ts | 129 +- .../codeEditor.contribution.ts | 1 + .../electron-browser/startDebugTextMate.ts | 107 + .../contrib/comments/browser/commentNode.ts | 5 + .../comments/browser/commentThreadWidget.ts | 11 +- .../contrib/customEditor/browser/commands.ts | 51 +- .../customEditor/browser/customEditorInput.ts | 13 +- .../customEditor/browser/customEditors.ts | 36 +- .../customEditor/common/customEditor.ts | 3 +- .../common/customTextEditorModel.ts | 4 + .../contrib/debug/browser/callStackView.ts | 67 +- .../debug/browser/debug.contribution.ts | 1 + .../debug/browser/debugActionViewItems.ts | 16 +- .../browser/debugConfigurationManager.ts | 30 +- .../contrib/debug/browser/debugHover.ts | 4 +- .../contrib/debug/browser/debugService.ts | 2 +- .../contrib/debug/browser/debugToolBar.ts | 1 - .../debug/browser/media/debugViewlet.css | 20 +- .../contrib/debug/browser/rawDebugSession.ts | 4 +- .../workbench/contrib/debug/common/debug.ts | 4 +- .../contrib/debug/node/debugHelperService.ts | 2 +- .../workbench/contrib/debug/node/terminals.ts | 4 +- .../experiments/common/experimentService.ts | 9 +- .../experimentService.test.ts | 39 + .../extensions/browser/extensionEditor.ts | 6 +- .../extensions/browser/extensionsViewlet.ts | 2 +- .../contrib/files/browser/explorerViewlet.ts | 10 +- .../files/browser/fileActions.contribution.ts | 23 +- .../contrib/files/browser/fileActions.ts | 177 +- .../contrib/files/browser/fileCommands.ts | 20 + .../files/browser/files.contribution.ts | 5 + .../views/explorerDecorationsProvider.ts | 5 - .../files/browser/views/explorerView.ts | 111 +- .../files/browser/views/explorerViewer.ts | 9 +- .../files/common/dirtyFilesIndicator.ts | 8 +- .../contrib/files/common/explorerModel.ts | 24 +- .../contrib/files/common/explorerService.ts | 141 +- .../workbench/contrib/files/common/files.ts | 28 +- .../contrib/files/common/openWith.ts | 150 + .../markers/browser/markers.contribution.ts | 1 + .../contrib/markers/browser/markers.ts | 17 +- .../contrib/markers/browser/markersView.ts | 9 + .../markers/browser/markersViewActions.ts | 4 + .../contrib/notebook/browser/constants.ts | 18 +- .../notebook/browser/contrib/coreActions.ts | 377 +- .../browser/contrib/find/findController.ts | 4 +- .../notebook/browser/contrib/fold/folding.ts | 13 +- .../browser/contrib/fold/foldingModel.ts | 26 + .../contrib/fold/test/notebookFolding.test.ts | 88 + .../browser/contrib/format/formatting.ts | 89 + .../browser/contrib/toc/tocProvider.ts | 35 + .../notebook/browser/media/notebook.css | 159 +- .../notebook/browser/notebook.contribution.ts | 93 +- .../notebook/browser/notebookBrowser.ts | 34 +- .../notebook/browser/notebookEditor.ts | 159 +- .../notebook/browser/notebookEditorInput.ts | 75 +- .../notebook/browser/notebookService.ts | 5 + .../notebook/browser/view/notebookCellList.ts | 15 +- .../view/output/transforms/errorTransform.ts | 1 + .../view/renderers/backLayerWebView.ts | 19 +- .../browser/view/renderers/cellRenderer.ts | 337 +- .../browser/view/renderers/codeCell.ts | 2 +- .../browser/view/renderers/markdownCell.ts | 60 +- .../browser/viewModel/baseCellViewModel.ts | 12 +- .../notebook/browser/viewModel/cellEdit.ts | 25 +- .../browser/viewModel/codeCellViewModel.ts | 8 +- .../viewModel/markdownCellViewModel.ts | 2 +- .../browser/viewModel/notebookViewModel.ts | 136 +- .../common/model/notebookTextModel.ts | 55 +- .../contrib/notebook/common/notebookCommon.ts | 49 +- .../notebook/test/notebookViewModel.test.ts | 10 +- .../output/browser/output.contribution.ts | 1 + .../preferences/browser/keybindingsEditor.ts | 8 +- .../browser/media/settingsEditor2.css | 18 +- .../browser/media/settingsWidgets.css | 1 + .../preferences/browser/preferencesEditor.ts | 2 +- .../preferences/browser/settingsEditor2.ts | 16 +- .../preferences/browser/settingsTree.ts | 101 +- .../preferences/browser/settingsWidgets.ts | 10 +- .../contrib/remote/browser/tunnelView.ts | 4 +- .../workbench/contrib/scm/browser/activity.ts | 2 +- .../contrib/scm/browser/dirtydiffDecorator.ts | 9 +- .../workbench/contrib/scm/browser/mainPane.ts | 4 + .../contrib/scm/browser/repositoryPane.ts | 10 +- .../search/browser/search.contribution.ts | 22 +- .../contrib/search/browser/searchActions.ts | 28 +- .../contrib/search/browser/searchView.ts | 2 +- .../search/browser/symbolsQuickAccess.ts | 4 +- .../contrib/search/common/searchModel.ts | 2 +- .../search/test/browser/queryBuilder.test.ts | 4 +- .../browser/searchEditor.contribution.ts | 4 +- .../searchEditor/browser/searchEditorInput.ts | 8 +- .../tasks/browser/abstractTaskService.ts | 173 +- .../tasks/browser/task.contribution.ts | 21 +- .../contrib/tasks/browser/taskQuickPick.ts | 19 +- .../contrib/tasks/browser/tasksQuickAccess.ts | 6 +- .../contrib/tasks/common/taskConfiguration.ts | 2 +- .../workbench/contrib/tasks/common/tasks.ts | 43 +- .../browser/environmentVariableInfo.ts | 69 +- .../browser/links/terminalLinkManager.ts | 3 +- .../terminalValidatedLocalLinkProvider.ts | 4 +- .../browser/links/terminalWordLinkProvider.ts | 20 +- .../terminal/browser/media/widgets.css | 11 +- .../terminal/browser/terminal.contribution.ts | 1 + .../contrib/terminal/browser/terminal.ts | 2 +- .../terminal/browser/terminalActions.ts | 4 +- .../terminal/browser/terminalInstance.ts | 35 +- .../contrib/terminal/browser/terminalView.ts | 16 +- .../widgets/environmentVariableInfoWidget.ts | 38 +- .../terminal/browser/widgets/hoverWidget.ts | 85 +- .../terminal/common/environmentVariable.ts | 6 +- .../contrib/terminal/common/terminal.ts | 2 + .../terminal/common/terminalConfiguration.ts | 2 +- .../terminalInstanceService.ts | 6 +- .../electron-browser/windowsShellHelper.ts | 39 +- .../browser/links/terminalLinkManager.test.ts | 8 +- .../links/terminalWordLinkProvider.test.ts | 12 +- .../contrib/timeline/browser/timelinePane.ts | 2 +- .../contrib/update/browser/update.ts | 3 +- .../url/common/trustedDomainsValidator.ts | 4 + ...tection.test.ts => trustedDomains.test.ts} | 5 + .../userDataSync/browser/userDataSync.ts | 172 +- .../browser/userDataSyncAccount.ts | 151 +- .../userDataSync/browser/userDataSyncView.ts | 20 +- .../browser/webviewWorkbenchService.ts | 8 +- .../electron-browser/webviewElement.ts | 4 +- .../welcome/page/browser/welcomePage.ts | 23 +- .../activity/browser/activityService.ts | 29 +- .../services/activity/common/activity.ts | 52 +- .../common/variableResolver.ts | 8 +- .../configurationResolverService.test.ts | 77 +- .../services/editor/browser/editorService.ts | 10 +- .../editorAssociationsSetting.ts | 0 .../services/editor/common/editorService.ts | 5 +- .../environment/browser/environmentService.ts | 2 + .../environment/common/environmentService.ts | 2 + .../electron-browser/environmentService.ts | 3 + .../electron-browser/extensionTipsService.ts | 18 +- .../remoteExtensionManagementIpc.ts | 5 +- .../host/browser/browserHostService.ts | 24 +- .../keybinding/common/keybindingIO.ts | 12 +- .../preferences/common/preferences.ts | 1 + .../preferences/common/preferencesModels.ts | 3 +- .../progress/browser/progressService.ts | 4 +- .../test/browser/progressIndicator.test.ts | 2 +- .../telemetry/browser/telemetryService.ts | 11 +- .../electron-browser/telemetryService.ts | 12 +- .../browser/abstractTextMateService.ts | 81 +- .../textMate/browser/textMateService.ts | 48 +- .../textMate/common/TMGrammarFactory.ts | 8 +- .../services/textMate/common/cgmanifest.json | 32 +- .../textMate/common/textMateService.ts | 2 + .../electron-browser/textMateService.ts | 24 +- .../electron-browser/textMateWorker.ts | 62 +- .../themes/browser/workbenchThemeService.ts | 4 +- .../services/themes/common/colorThemeData.ts | 140 +- .../themes/common/themeConfiguration.ts | 53 +- .../themes/common/workbenchThemeService.ts | 25 +- .../tokenStyleResolving.test.ts | 66 +- .../views/browser/viewDescriptorService.ts | 104 +- .../views/common/viewContainerModel.ts | 11 +- .../test/browser/viewContainerModel.test.ts | 82 + .../browser/api/extHostDocumentData.test.ts | 16 + .../api/extHostNotebookConcatDocument.test.ts | 13 +- .../api/mainThreadDocumentsAndEditors.test.ts | 2 +- .../browser/api/mainThreadEditors.test.ts | 2 +- .../test/browser/workbenchTestServices.ts | 2 +- .../textsearch.perf.integrationTest.ts | 10 +- src/vs/workbench/workbench.web.api.ts | 15 +- test/automation/src/code.ts | 2 +- test/automation/src/playwrightDriver.ts | 2 +- test/integration/browser/src/index.ts | 2 +- test/unit/browser/index.js | 2 +- yarn.lock | 136 +- 413 files changed, 7190 insertions(+), 8756 deletions(-) create mode 100644 extensions/git/src/github.ts create mode 100644 extensions/git/src/terminal.ts delete mode 100644 extensions/vscode-colorize-tests/.gitignore delete mode 100644 extensions/vscode-colorize-tests/.vscode/launch.json delete mode 100644 extensions/vscode-colorize-tests/.vscode/tasks.json delete mode 100644 extensions/vscode-colorize-tests/package.json delete mode 100644 extensions/vscode-colorize-tests/producticons/ElegantIcons.woff delete mode 100644 extensions/vscode-colorize-tests/producticons/index.html delete mode 100644 extensions/vscode-colorize-tests/producticons/mit_license.txt delete mode 100644 extensions/vscode-colorize-tests/producticons/test-product-icon-theme.json delete mode 100644 extensions/vscode-colorize-tests/src/colorizer.test.ts delete mode 100644 extensions/vscode-colorize-tests/src/colorizerTestMain.ts delete mode 100644 extensions/vscode-colorize-tests/src/index.ts delete mode 100644 extensions/vscode-colorize-tests/src/typings/ref.d.ts delete mode 100644 extensions/vscode-colorize-tests/test/semantic-test/.vscode/settings.json delete mode 100644 extensions/vscode-colorize-tests/test/semantic-test/semantic-test.json delete mode 100644 extensions/vscode-colorize-tests/tsconfig.json delete mode 100644 extensions/vscode-colorize-tests/yarn.lock rename src/sql/workbench/browser/parts/views/{customView.ts => treeView.ts} (66%) delete mode 100644 src/vs/editor/browser/widget/media/diagonal-fill.png delete mode 100644 src/vs/editor/contrib/clipboard/clipboard.css create mode 100644 src/vs/editor/standalone/test/monarch/monarch.test.ts create mode 100644 src/vs/platform/extensionManagement/node/extensionDownloader.ts delete mode 100644 src/vs/workbench/api/browser/media/test.svg create mode 100644 src/vs/workbench/contrib/codeEditor/electron-browser/startDebugTextMate.ts create mode 100644 src/vs/workbench/contrib/files/common/openWith.ts create mode 100644 src/vs/workbench/contrib/notebook/browser/contrib/format/formatting.ts create mode 100644 src/vs/workbench/contrib/notebook/browser/contrib/toc/tocProvider.ts rename src/vs/workbench/contrib/url/test/browser/{linkProtection.test.ts => trustedDomains.test.ts} (93%) rename src/vs/workbench/services/editor/{browser => common}/editorAssociationsSetting.ts (100%) diff --git a/.eslintrc.json b/.eslintrc.json index bf8565b62a..06033278bd 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -485,7 +485,8 @@ "**/{vs,sql}/workbench/services/**/common/**", "**/{vs,sql}/workbench/api/**/common/**", "vs/workbench/contrib/files/common/editors/fileEditorInput", // this should be fine, it only accesses constants from contrib - "vscode-textmate" + "vscode-textmate", + "vscode-oniguruma" ] }, { @@ -519,7 +520,8 @@ "onigasm-umd", "sanitize-html", "@angular/*", - "rxjs/**" + "rxjs/**", + "vscode-oniguruma" ] }, { diff --git a/.vscode/launch.json b/.vscode/launch.json index 38b5245f1a..a631078f52 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -79,7 +79,6 @@ "type": "pwa-chrome", "request": "launch", "name": "Launch azuredatastudio", - "browserLaunchLocation": "workspace", "windows": { "runtimeExecutable": "${workspaceFolder}/scripts/sql.bat" }, diff --git a/.vscode/searches/ts36031.code-search b/.vscode/searches/ts36031.code-search index a232dfbd34..8a74db2a9a 100644 --- a/.vscode/searches/ts36031.code-search +++ b/.vscode/searches/ts36031.code-search @@ -1,9 +1,19 @@ -# Query: \\w+\\?\\..+![(.[] +# Query: \\w+\\?\\.\\w+![(.[] # Flags: RegExp # ContextLines: 2 + +2 results - 2 files + src/vs/base/browser/ui/tree/asyncDataTree.ts: - 270 } : undefined, - 271 isChecked: options.ariaProvider!.isChecked ? (e) => { - 272: return options.ariaProvider?.isChecked!(e.element as T); - 273 } : undefined - 274 }, + 243 } : () => 'treeitem', + 244 isChecked: options.accessibilityProvider!.isChecked ? (e) => { + 245: return !!(options.accessibilityProvider?.isChecked!(e.element as T)); + 246 } : undefined, + 247 getAriaLabel(e) { + +src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts: + 254 + 255 return debugDynamicExtensions.map(e => { + 256: const type = e.contributes?.debuggers![0].type!; + 257 return { + 258 label: this.getDebuggerLabel(type)!, diff --git a/.vscode/settings.json b/.vscode/settings.json index 6aadf86f7e..1ffd534543 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,7 +7,8 @@ "**/.DS_Store": true, "build/**/*.js": { "when": "$(basename).ts" - } + }, + "src/vs/server": false }, "files.associations": { "cglicenses.json": "jsonc" @@ -24,7 +25,8 @@ "test/smoke/out/**": true, "test/automation/out/**": true, "test/integration/browser/out/**": true, - "src/vs/base/test/node/uri.test.data.txt": true + "src/vs/base/test/node/uri.test.data.txt": true, + "src/vs/server": false }, "lcov.path": [ "./.build/coverage/lcov.info", diff --git a/build/.nativeignore b/build/.nativeignore index 9f7967cecf..bd49233e3d 100644 --- a/build/.nativeignore +++ b/build/.nativeignore @@ -19,13 +19,6 @@ vscode-sqlite3/build/** vscode-sqlite3/src/** !vscode-sqlite3/build/Release/*.node -oniguruma/binding.gyp -oniguruma/build/** -oniguruma/src/** -oniguruma/deps/** -!oniguruma/build/Release/*.node -!oniguruma/src/*.js - windows-mutex/binding.gyp windows-mutex/build/** windows-mutex/src/** diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index f83c7dd514..f468ba892c 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -244,7 +244,7 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op const deps = gulp.src(dependenciesSrc, { base: '.', dot: true }) .pipe(filter(['**', '!**/package-lock.json'])) .pipe(util.cleanNodeModules(path.join(__dirname, '.nativeignore'))) - .pipe(createAsar(path.join(process.cwd(), 'node_modules'), ['**/*.node', '**/vscode-ripgrep/bin/*', '**/node-pty/build/Release/*'], 'app/node_modules.asar')); + .pipe(createAsar(path.join(process.cwd(), 'node_modules'), ['**/*.node', '**/vscode-ripgrep/bin/*', '**/node-pty/build/Release/*', '**/*.wasm'], 'app/node_modules.asar')); let all = es.merge( packageJsonStream, diff --git a/build/lib/extensions.js b/build/lib/extensions.js index 637ff6b32d..19a38340ac 100644 --- a/build/lib/extensions.js +++ b/build/lib/extensions.js @@ -187,6 +187,7 @@ const excludedExtensions = [ 'vscode-test-resolver', 'ms-vscode.node-debug', 'ms-vscode.node-debug2', + 'vscode-notebook-tests', 'integration-tests', ]; // {{SQL CARBON EDIT}} diff --git a/build/lib/extensions.ts b/build/lib/extensions.ts index 47cc41a510..624be94601 100644 --- a/build/lib/extensions.ts +++ b/build/lib/extensions.ts @@ -221,6 +221,7 @@ const excludedExtensions = [ 'vscode-test-resolver', 'ms-vscode.node-debug', 'ms-vscode.node-debug2', + 'vscode-notebook-tests', 'integration-tests', // {{SQL CARBON EDIT}} ]; diff --git a/build/lib/optimize.js b/build/lib/optimize.js index dc47154a78..19104ca2d3 100644 --- a/build/lib/optimize.js +++ b/build/lib/optimize.js @@ -69,12 +69,7 @@ function loader(src, bundledFileHeader, bundleLoader) { this.emit('data', data); } })) - .pipe(util.loadSourcemaps()) - .pipe(concat('vs/loader.js')) - .pipe(es.mapSync(function (f) { - f.sourceMap.sourceRoot = util.toFileUri(path.join(REPO_ROOT_PATH, 'src')); - return f; - }))); + .pipe(concat('vs/loader.js'))); } function toConcatStream(src, bundledFileHeader, sources, dest) { const useSourcemaps = /\.js$/.test(dest) && !/\.nls\.js$/.test(dest); diff --git a/build/lib/optimize.ts b/build/lib/optimize.ts index 1e56a9ea68..b3e1f95759 100644 --- a/build/lib/optimize.ts +++ b/build/lib/optimize.ts @@ -81,12 +81,7 @@ function loader(src: string, bundledFileHeader: string, bundleLoader: boolean): this.emit('data', data); } })) - .pipe(util.loadSourcemaps()) .pipe(concat('vs/loader.js')) - .pipe(es.mapSync(function (f) { - f.sourceMap.sourceRoot = util.toFileUri(path.join(REPO_ROOT_PATH, 'src')); - return f; - })) ); } diff --git a/build/package.json b/build/package.json index b65f7654a1..3d6f7f49ae 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.20200420", + "typescript": "^3.9.0-dev.20200427", "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 1605a3f7d0..1d8f6f03a7 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.20200420: - version "3.9.0-dev.20200420" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.0-dev.20200420.tgz#99c2bc0936dbf4479b0b5260d80475ed494b1532" - integrity sha512-36MW6V+oXNnsSgliSjUWvtOkO21g9+iFGHPFv+O06HsCl3dcuqzBac17m8xuOuWo1LUlEgS6yAnD9fiVgvmCfg== +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== typical@^4.0.0: version "4.0.0" diff --git a/cglicenses.json b/cglicenses.json index 0d588440ae..fcbac22e31 100644 --- a/cglicenses.json +++ b/cglicenses.json @@ -169,5 +169,37 @@ "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", "SOFTWARE." ] + }, + { + // Reason: The license at https://github.com/floatdrop/pinkie/blob/master/license + // cannot be found by the OSS tool automatically. + "name": "pinkie", + "fullLicenseText": [ + "The MIT License (MIT)", + "", + "Copyright (c) Vsevolod Strukchinsky (github.com/floatdrop)", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy", + "of this software and associated documentation files (the \"Software\"), to deal", + "in the Software without restriction, including without limitation the rights", + "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", + "copies of the Software, and to permit persons to whom the Software is", + "furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in", + "all copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", + "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN", + "THE SOFTWARE." + ] + }, + { + "name": "big-integer", + "prependLicenseText": ["Copyright released to public domain"] } ] diff --git a/extensions/docker/package.json b/extensions/docker/package.json index f5f2f795fc..aa4ef1c294 100644 --- a/extensions/docker/package.json +++ b/extensions/docker/package.json @@ -21,6 +21,13 @@ "language": "dockerfile", "scopeName": "source.dockerfile", "path": "./syntaxes/docker.tmLanguage.json" - }] + }], + "configurationDefaults": { + "[dockerfile]": { + "editor.quickSuggestions": { + "strings": true + } + } + } } -} \ No newline at end of file +} diff --git a/extensions/git/package.json b/extensions/git/package.json index a29fc3db06..7c28c7e4b0 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -609,10 +609,6 @@ "command": "git.pullRebase", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, - { - "command": "git.pullFrom", - "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" - }, { "command": "git.merge", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" @@ -1669,6 +1665,18 @@ "scope": "resource", "default": true, "description": "%config.showCommitInput%" + }, + "git.terminalAuthentication": { + "type": "boolean", + "scope": "resource", + "default": true, + "description": "%config.terminalAuthentication%" + }, + "git.githubAuthentication": { + "type": "boolean", + "scope": "resource", + "default": true, + "description": "%config.githubAuthentication%" } } }, @@ -1781,6 +1789,7 @@ "ignore" ], "filenames": [ + ".gitignore_global", ".gitignore" ], "configuration": "./languages/ignore.language-configuration.json" diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index d376e32dd5..3f5269dd51 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -144,6 +144,8 @@ "config.untrackedChanges.separate": "Untracked changes appear separately in the Source Control view. They are also excluded from several actions.", "config.untrackedChanges.hidden": "Untracked changes are hidden and excluded from several actions.", "config.showCommitInput": "Controls whether to show the commit input in the Git source control panel.", + "config.terminalAuthentication": "Controls whether to enable VS Code to be the authentication handler for git processes spawned in the integrated terminal. Note: terminals need to be restarted to pick up a change in this setting.", + "config.githubAuthentication": "Controls whether to enable automatic GitHub authentication for git commands within VS Code.", "colors.added": "Color for added resources.", "colors.modified": "Color for modified resources.", "colors.deleted": "Color for deleted resources.", diff --git a/extensions/git/src/api/api1.ts b/extensions/git/src/api/api1.ts index 177d62889f..dd74f5bb65 100644 --- a/extensions/git/src/api/api1.ts +++ b/extensions/git/src/api/api1.ts @@ -5,7 +5,7 @@ import { Model } from '../model'; import { Repository as BaseRepository, Resource } from '../repository'; -import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, GitExtension, RefType, RemoteSourceProvider } from './git'; +import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, GitExtension, RefType, RemoteSourceProvider, CredentialsProvider } from './git'; import { Event, SourceControlInputBox, Uri, SourceControl, Disposable, commands } from 'vscode'; import { mapEvent } from '../util'; import { toGitUri } from '../uri'; @@ -183,6 +183,10 @@ export class ApiRepository implements Repository { return this._repository.removeRemote(name); } + renameRemote(name: string, newName: string): Promise { + return this._repository.renameRemote(name, newName); + } + fetch(remote?: string | undefined, ref?: string | undefined, depth?: number | undefined): Promise { return this._repository.fetch(remote, ref, depth); } @@ -248,10 +252,21 @@ export class ApiImpl implements API { return result ? new ApiRepository(result) : null; } + async init(root: Uri): Promise { + const path = root.fsPath; + await this._model.git.init(path); + await this._model.openRepository(path); + return this.getRepository(root) || null; + } + registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable { return this._model.registerRemoteSourceProvider(provider); } + registerCredentialsProvider(provider: CredentialsProvider): Disposable { + return this._model.registerCredentialsProvider(provider); + } + constructor(private _model: Model) { } } diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index 8868493e0a..e1b68f1ac0 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -179,6 +179,7 @@ export interface Repository { addRemote(name: string, url: string): Promise; removeRemote(name: string): Promise; + renameRemote(name: string, newName: string): Promise; fetch(remote?: string, ref?: string, depth?: number): Promise; pull(unshallow?: boolean): Promise; @@ -203,6 +204,15 @@ export interface RemoteSourceProvider { getRemoteSources(query?: string): ProviderResult; } +export interface Credentials { + readonly username: string; + readonly password: string; +} + +export interface CredentialsProvider { + getCredentials(host: Uri): ProviderResult; +} + export type APIState = 'uninitialized' | 'initialized'; export interface API { @@ -215,7 +225,10 @@ export interface API { toGitUri(uri: Uri, ref: string): Uri; getRepository(uri: Uri): Repository | null; + init(root: Uri): Promise; + registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable; + registerCredentialsProvider(provider: CredentialsProvider): Disposable; } export interface GitExtension { diff --git a/extensions/git/src/askpass.sh b/extensions/git/src/askpass.sh index b6d374f2c3..d19b62affa 100644 --- a/extensions/git/src/askpass.sh +++ b/extensions/git/src/askpass.sh @@ -1,5 +1,5 @@ #!/bin/sh VSCODE_GIT_ASKPASS_PIPE=`mktemp` -VSCODE_GIT_ASKPASS_PIPE="$VSCODE_GIT_ASKPASS_PIPE" "$VSCODE_GIT_ASKPASS_NODE" "$VSCODE_GIT_ASKPASS_MAIN" $* +ELECTRON_RUN_AS_NODE="1" VSCODE_GIT_ASKPASS_PIPE="$VSCODE_GIT_ASKPASS_PIPE" "$VSCODE_GIT_ASKPASS_NODE" "$VSCODE_GIT_ASKPASS_MAIN" $* cat $VSCODE_GIT_ASKPASS_PIPE -rm $VSCODE_GIT_ASKPASS_PIPE \ No newline at end of file +rm $VSCODE_GIT_ASKPASS_PIPE diff --git a/extensions/git/src/askpass.ts b/extensions/git/src/askpass.ts index 14d7e7f260..0a177470d4 100644 --- a/extensions/git/src/askpass.ts +++ b/extensions/git/src/askpass.ts @@ -3,36 +3,60 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { window, InputBoxOptions } from 'vscode'; -import { IDisposable } from './util'; +import { window, InputBoxOptions, Uri, OutputChannel, Disposable } from 'vscode'; +import { IDisposable, EmptyDisposable, toDisposable } from './util'; import * as path from 'path'; -import { IIPCHandler, IIPCServer } from './ipc/ipcServer'; - -export interface AskpassEnvironment { - GIT_ASKPASS: string; - ELECTRON_RUN_AS_NODE?: string; - VSCODE_GIT_ASKPASS_NODE?: string; - VSCODE_GIT_ASKPASS_MAIN?: string; - VSCODE_GIT_ASKPASS_HANDLE?: string; -} +import { IIPCHandler, IIPCServer, createIPCServer } from './ipc/ipcServer'; +import { CredentialsProvider, Credentials } from './api/git'; export class Askpass implements IIPCHandler { - private disposable: IDisposable; + private disposable: IDisposable = EmptyDisposable; + private cache = new Map(); + private credentialsProviders = new Set(); - static getDisabledEnv(): AskpassEnvironment { - return { - GIT_ASKPASS: path.join(__dirname, 'askpass-empty.sh') - }; + static async create(outputChannel: OutputChannel, context?: string): Promise { + try { + return new Askpass(await createIPCServer(context)); + } catch (err) { + outputChannel.appendLine(`[error] Failed to create git askpass IPC: ${err}`); + return new Askpass(); + } } - constructor(ipc: IIPCServer) { - this.disposable = ipc.registerHandler('askpass', this); + private constructor(private ipc?: IIPCServer) { + if (ipc) { + this.disposable = ipc.registerHandler('askpass', this); + } } async handle({ request, host }: { request: string, host: string }): Promise { + const uri = Uri.parse(host); + const authority = uri.authority.replace(/^.*@/, ''); + const password = /password/i.test(request); + const cached = this.cache.get(authority); + + if (cached && password) { + this.cache.delete(authority); + return cached.password; + } + + if (!password) { + for (const credentialsProvider of this.credentialsProviders) { + try { + const credentials = await credentialsProvider.getCredentials(uri); + + if (credentials) { + this.cache.set(authority, credentials); + setTimeout(() => this.cache.delete(authority), 60_000); + return credentials.username; + } + } catch { } + } + } + const options: InputBoxOptions = { - password: /password/i.test(request), + password, placeHolder: request, prompt: `Git: ${host}`, ignoreFocusOut: true @@ -41,15 +65,26 @@ export class Askpass implements IIPCHandler { return await window.showInputBox(options) || ''; } - getEnv(): AskpassEnvironment { + getEnv(): { [key: string]: string; } { + if (!this.ipc) { + return { + GIT_ASKPASS: path.join(__dirname, 'askpass-empty.sh') + }; + } + return { - ELECTRON_RUN_AS_NODE: '1', + ...this.ipc.getEnv(), GIT_ASKPASS: path.join(__dirname, 'askpass.sh'), VSCODE_GIT_ASKPASS_NODE: process.execPath, VSCODE_GIT_ASKPASS_MAIN: path.join(__dirname, 'askpass-main.js') }; } + registerCredentialsProvider(provider: CredentialsProvider): Disposable { + this.credentialsProviders.add(provider); + return toDisposable(() => this.credentialsProviders.delete(provider)); + } + dispose(): void { this.disposable.dispose(); } diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 7b93854bab..e31124e15d 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -524,7 +524,7 @@ export class CommandCenter { quickpick.ignoreFocusOut = true; const providers = this.model.getRemoteProviders() - .map(provider => ({ label: (provider.icon ? `$(${provider.icon}) ` : '') + localize('clonefrom', "Clone from {1}", provider.name), alwaysShow: true, provider })); + .map(provider => ({ label: (provider.icon ? `$(${provider.icon}) ` : '') + localize('clonefrom', "Clone from {0}", provider.name), alwaysShow: true, provider })); quickpick.placeholder = providers.length === 0 ? localize('provide url', "Provide repository URL.") @@ -2560,6 +2560,14 @@ export class CommandCenter { type = 'warning'; options.modal = false; break; + case GitErrorCodes.AuthenticationFailed: + const regex = /Authentication failed for '(.*)'/i; + const match = regex.exec(err.stderr || String(err)); + + message = match + ? localize('auth failed specific', "Failed to authenticate to git remote:\n\n{0}", match[1]) + : localize('auth failed', "Failed to authenticate to git remote."); + break; case GitErrorCodes.NoUserNameConfigured: case GitErrorCodes.NoUserEmailConfigured: message = localize('missing user info', "Make sure you configure your 'user.name' and 'user.email' in git."); diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 265dae3375..0dabbcf951 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -306,7 +306,7 @@ export interface IGitOptions { function getGitErrorCode(stderr: string): string | undefined { if (/Another git process seems to be running in this repository|If no other git process is currently running/.test(stderr)) { return GitErrorCodes.RepositoryIsLocked; - } else if (/Authentication failed/.test(stderr)) { + } else if (/Authentication failed/i.test(stderr)) { return GitErrorCodes.AuthenticationFailed; } else if (/Not a git repository/i.test(stderr)) { return GitErrorCodes.NotAGitRepository; @@ -1498,7 +1498,12 @@ export class Repository { } async removeRemote(name: string): Promise { - const args = ['remote', 'rm', name]; + const args = ['remote', 'remove', name]; + await this.run(args); + } + + async renameRemote(name: string, newName: string): Promise { + const args = ['remote', 'rename', name, newName]; await this.run(args); } diff --git a/extensions/git/src/github.ts b/extensions/git/src/github.ts new file mode 100644 index 0000000000..a031607ba8 --- /dev/null +++ b/extensions/git/src/github.ts @@ -0,0 +1,66 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CredentialsProvider, Credentials } from './api/git'; +import { IDisposable, filterEvent, EmptyDisposable } from './util'; +import { workspace, Uri, AuthenticationSession, authentication } from 'vscode'; +import { Askpass } from './askpass'; + +export class GitHubCredentialProvider implements CredentialsProvider { + + async getCredentials(host: Uri): Promise { + if (!/github\.com/i.test(host.authority)) { + return; + } + + const session = await this.getSession(); + return { username: session.account.id, password: await session.getAccessToken() }; + } + + private async getSession(): Promise { + const authenticationSessions = await authentication.getSessions('github', ['repo']); + + if (authenticationSessions.length) { + return await authenticationSessions[0]; + } else { + return await authentication.login('github', ['repo']); + } + } +} + +export class GithubCredentialProviderManager { + + private providerDisposable: IDisposable = EmptyDisposable; + private readonly disposable: IDisposable; + + private _enabled = false; + private set enabled(enabled: boolean) { + if (this._enabled === enabled) { + return; + } + + this._enabled = enabled; + + if (enabled) { + this.providerDisposable = this.askpass.registerCredentialsProvider(new GitHubCredentialProvider()); + } else { + this.providerDisposable.dispose(); + } + } + + constructor(private readonly askpass: Askpass) { + this.disposable = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git'))(this.refresh, this); + this.refresh(); + } + + private refresh(): void { + this.enabled = workspace.getConfiguration('git', null).get('githubAuthentication', true); + } + + dispose(): void { + this.enabled = false; + this.disposable.dispose(); + } +} diff --git a/extensions/git/src/ipc/ipcServer.ts b/extensions/git/src/ipc/ipcServer.ts index 218a7c1a53..5fddbf54aa 100644 --- a/extensions/git/src/ipc/ipcServer.ts +++ b/extensions/git/src/ipc/ipcServer.ts @@ -11,27 +11,42 @@ import * as os from 'os'; import * as fs from 'fs'; import * as crypto from 'crypto'; -function getIPCHandlePath(nonce: string): string { +function getIPCHandlePath(id: string): string { if (process.platform === 'win32') { - return `\\\\.\\pipe\\vscode-git-ipc-${nonce}-sock`; + return `\\\\.\\pipe\\vscode-git-${id}-sock`; } if (process.env['XDG_RUNTIME_DIR']) { - return path.join(process.env['XDG_RUNTIME_DIR'] as string, `vscode-git-ipc-${nonce}.sock`); + return path.join(process.env['XDG_RUNTIME_DIR'] as string, `vscode-git-${id}.sock`); } - return path.join(os.tmpdir(), `vscode-git-ipc-${nonce}.sock`); + return path.join(os.tmpdir(), `vscode-git-${id}.sock`); } export interface IIPCHandler { handle(request: any): Promise; } -export async function createIPCServer(): Promise { +export async function createIPCServer(context?: string): Promise { const server = http.createServer(); - const buffer = await new Promise((c, e) => crypto.randomBytes(20, (err, buf) => err ? e(err) : c(buf))); - const nonce = buffer.toString('hex'); - const ipcHandlePath = getIPCHandlePath(nonce); + const hash = crypto.createHash('sha1'); + + if (!context) { + const buffer = await new Promise((c, e) => crypto.randomBytes(20, (err, buf) => err ? e(err) : c(buf))); + hash.update(buffer); + } else { + hash.update(context); + } + + const ipcHandlePath = getIPCHandlePath(hash.digest('hex').substr(0, 10)); + + if (process.platform !== 'win32') { + try { + await fs.promises.unlink(ipcHandlePath); + } catch { + // noop + } + } return new Promise((c, e) => { try { @@ -46,7 +61,7 @@ export async function createIPCServer(): Promise { export interface IIPCServer extends Disposable { readonly ipcHandlePath: string | undefined; - getEnv(): any; + getEnv(): { [key: string]: string; }; registerHandler(name: string, handler: IIPCHandler): Disposable; } @@ -91,7 +106,7 @@ class IPCServer implements IIPCServer, Disposable { }); } - getEnv(): any { + getEnv(): { [key: string]: string; } { return { VSCODE_GIT_IPC_HANDLE: this.ipcHandlePath }; } diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index 269ccdf9dc..7fa135d95d 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -20,9 +20,10 @@ import { GitProtocolHandler } from './protocolHandler'; import { GitExtensionImpl } from './api/extension'; // import * as path from 'path'; // import * as fs from 'fs'; -import { createIPCServer, IIPCServer } from './ipc/ipcServer'; import { GitTimelineProvider } from './timelineProvider'; import { registerAPICommands } from './api/api1'; +import { GithubCredentialProviderManager } from './github'; +import { TerminalEnvironmentManager } from './terminal'; const deactivateTasks: { (): Promise; }[] = []; @@ -36,27 +37,18 @@ async function createModel(context: ExtensionContext, outputChannel: OutputChann const pathHint = workspace.getConfiguration('git').get('path'); const info = await findGit(pathHint, path => outputChannel.appendLine(localize('looking', "Looking for git in: {0}", path))); - let env: any = {}; - let ipc: IIPCServer | undefined; + const askpass = await Askpass.create(outputChannel, context.storagePath); + disposables.push(askpass); - try { - ipc = await createIPCServer(); - disposables.push(ipc); - env = { ...env, ...ipc.getEnv() }; - } catch { - // noop - } + const env = askpass.getEnv(); + const terminalEnvironmentManager = new TerminalEnvironmentManager(context, env); + disposables.push(terminalEnvironmentManager); - if (ipc) { - const askpass = new Askpass(ipc); - disposables.push(askpass); - env = { ...env, ...askpass.getEnv() }; - } else { - env = { ...env, ...Askpass.getDisabledEnv() }; - } + const githubCredentialProviderManager = new GithubCredentialProviderManager(askpass); + context.subscriptions.push(githubCredentialProviderManager); const git = new Git({ gitPath: info.path, version: info.version, env }); - const model = new Model(git, context.globalState, outputChannel); + const model = new Model(git, askpass, context.globalState, outputChannel); disposables.push(model); const onRepository = () => commands.executeCommand('setContext', 'gitOpenRepositoryCount', `${model.repositories.length}`); diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 9b0958fe26..1231028d6f 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -12,7 +12,8 @@ import * as path from 'path'; import * as fs from 'fs'; import * as nls from 'vscode-nls'; import { fromGitUri } from './uri'; -import { GitErrorCodes, APIState as State, RemoteSourceProvider } from './api/git'; +import { GitErrorCodes, APIState as State, RemoteSourceProvider, CredentialsProvider } from './api/git'; +import { Askpass } from './askpass'; const localize = nls.loadMessageBundle(); @@ -78,7 +79,7 @@ export class Model { private disposables: Disposable[] = []; - constructor(readonly git: Git, private globalState: Memento, private outputChannel: OutputChannel) { + constructor(readonly git: Git, private readonly askpass: Askpass, private globalState: Memento, private outputChannel: OutputChannel) { workspace.onDidChangeWorkspaceFolders(this.onDidChangeWorkspaceFolders, this, this.disposables); window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, this.disposables); workspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this, this.disposables); @@ -454,6 +455,10 @@ export class Model { return toDisposable(() => this.remoteProviders.delete(provider)); } + registerCredentialsProvider(provider: CredentialsProvider): Disposable { + return this.askpass.registerCredentialsProvider(provider); + } + getRemoteProviders(): RemoteSourceProvider[] { return [...this.remoteProviders.values()]; } diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 86ccd958b1..3d1f49f78c 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1096,6 +1096,10 @@ export class Repository implements Disposable { await this.run(Operation.Remote, () => this.repository.removeRemote(name)); } + async renameRemote(name: string, newName: string): Promise { + await this.run(Operation.Remote, () => this.repository.renameRemote(name, newName)); + } + @throttle async fetchDefault(options: { silent?: boolean } = {}): Promise { await this.run(Operation.Fetch, () => this.repository.fetch(options)); diff --git a/extensions/git/src/terminal.ts b/extensions/git/src/terminal.ts new file mode 100644 index 0000000000..c8679a8271 --- /dev/null +++ b/extensions/git/src/terminal.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ExtensionContext, workspace } from 'vscode'; +import { filterEvent, IDisposable } from './util'; + +export class TerminalEnvironmentManager { + + private readonly disposable: IDisposable; + + private _enabled = false; + private set enabled(enabled: boolean) { + if (this._enabled === enabled) { + return; + } + + this._enabled = enabled; + this.context.environmentVariableCollection.clear(); + + if (enabled) { + for (const name of Object.keys(this.env)) { + this.context.environmentVariableCollection.replace(name, this.env[name]); + } + } + } + + constructor(private readonly context: ExtensionContext, private readonly env: { [key: string]: string }) { + this.disposable = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git')) + (this.refresh, this); + + this.refresh(); + } + + private refresh(): void { + this.enabled = workspace.getConfiguration('git', null).get('terminalAuthentication', true); + } + + dispose(): void { + this.disposable.dispose(); + } +} diff --git a/extensions/github-authentication/package.json b/extensions/github-authentication/package.json index 54d30a027f..6dab089127 100644 --- a/extensions/github-authentication/package.json +++ b/extensions/github-authentication/package.json @@ -14,6 +14,22 @@ "activationEvents": [ "*" ], + "contributes": { + "commands": [ + { + "command": "github.provide-token", + "title": "Manually Provide Token" + } + ], + "menus": { + "commandPalette": [ + { + "command": "github.provide-token", + "when": "false" + } + ] + } + }, "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", "main": "./out/extension.js", "scripts": { diff --git a/extensions/github-authentication/src/extension.ts b/extensions/github-authentication/src/extension.ts index 25271ae17b..444bd903bf 100644 --- a/extensions/github-authentication/src/extension.ts +++ b/extensions/github-authentication/src/extension.ts @@ -18,6 +18,10 @@ export async function activate(context: vscode.ExtensionContext) { await loginService.initialize(); + context.subscriptions.push(vscode.commands.registerCommand('github.provide-token', () => { + return loginService.manuallyProvideToken(); + })); + vscode.authentication.registerAuthenticationProvider({ id: 'github', displayName: 'GitHub', diff --git a/extensions/github-authentication/src/github.ts b/extensions/github-authentication/src/github.ts index fc204a1a34..c9443eab4d 100644 --- a/extensions/github-authentication/src/github.ts +++ b/extensions/github-authentication/src/github.ts @@ -6,7 +6,7 @@ import * as vscode from 'vscode'; import * as uuid from 'uuid'; import { keychain } from './common/keychain'; -import { GitHubServer } from './githubServer'; +import { GitHubServer, NETWORK_ERROR } from './githubServer'; import Logger from './common/logger'; export const onDidChangeSessions = new vscode.EventEmitter(); @@ -38,13 +38,26 @@ export class GitHubAuthenticationProvider { private _githubServer = new GitHubServer(); public async initialize(): Promise { - this._sessions = await this.readSessions(); + try { + this._sessions = await this.readSessions(); + } catch (e) { + // Ignore, network request failed + } + + // TODO revert Cannot validate tokens from auth server, no available clientId + // await this.validateSessions(); this.pollForChange(); } private pollForChange() { setTimeout(async () => { - const storedSessions = await this.readSessions(); + let storedSessions: vscode.AuthenticationSession[]; + try { + storedSessions = await this.readSessions(); + } catch (e) { + // Ignore, network request failed + return; + } const added: string[] = []; const removed: string[] = []; @@ -53,6 +66,7 @@ export class GitHubAuthenticationProvider { const matchesExisting = this._sessions.some(s => s.id === session.id); // Another window added a session to the keychain, add it to our state as well if (!matchesExisting) { + Logger.info('Adding session found in keychain'); this._sessions.push(session); added.push(session.id); } @@ -62,6 +76,7 @@ export class GitHubAuthenticationProvider { const matchesExisting = storedSessions.some(s => s.id === session.id); // Another window has logged out, remove from our state if (!matchesExisting) { + Logger.info('Removing session no longer found in keychain'); const sessionIndex = this._sessions.findIndex(s => s.id === session.id); if (sessionIndex > -1) { this._sessions.splice(sessionIndex, 1); @@ -84,35 +99,36 @@ export class GitHubAuthenticationProvider { if (storedSessions) { try { const sessionData: (SessionData | OldSessionData)[] = JSON.parse(storedSessions); - const sessionPromises = sessionData.map(async (session: SessionData | OldSessionData): Promise => { - try { - const needsUserInfo = isOldSessionData(session) || !session.account; - let userInfo: { id: string, accountName: string }; - if (needsUserInfo) { - userInfo = await this._githubServer.getUserInfo(session.accessToken); - } - - return { - id: session.id, - account: { - displayName: isOldSessionData(session) - ? session.accountName - : session.account?.displayName ?? userInfo!.accountName, - id: isOldSessionData(session) - ? userInfo!.id - : session.account?.id ?? userInfo!.id - }, - scopes: session.scopes, - getAccessToken: () => Promise.resolve(session.accessToken) - }; - } catch (e) { - return undefined; + const sessionPromises = sessionData.map(async (session: SessionData | OldSessionData): Promise => { + const needsUserInfo = isOldSessionData(session) || !session.account; + let userInfo: { id: string, accountName: string }; + if (needsUserInfo) { + userInfo = await this._githubServer.getUserInfo(session.accessToken); } + + return { + id: session.id, + account: { + displayName: isOldSessionData(session) + ? session.accountName + : session.account?.displayName ?? userInfo!.accountName, + id: isOldSessionData(session) + ? userInfo!.id + : session.account?.id ?? userInfo!.id + }, + scopes: session.scopes, + getAccessToken: () => Promise.resolve(session.accessToken) + }; }); - return (await Promise.all(sessionPromises)).filter((x: vscode.AuthenticationSession | undefined): x is vscode.AuthenticationSession => !!x); + return Promise.all(sessionPromises); } catch (e) { + if (e === NETWORK_ERROR) { + return []; + } + Logger.error(`Error reading sessions: ${e}`); + await keychain.deleteToken(); } } @@ -154,6 +170,10 @@ export class GitHubAuthenticationProvider { } } + public async manuallyProvideToken(): Promise { + this._githubServer.manuallyProvideToken(); + } + private async tokenToSession(token: string, scopes: string[]): Promise { const userInfo = await this._githubServer.getUserInfo(token); return { @@ -180,13 +200,15 @@ export class GitHubAuthenticationProvider { public async logout(id: string) { const sessionIndex = this._sessions.findIndex(session => session.id === id); if (sessionIndex > -1) { - const session = this._sessions.splice(sessionIndex, 1)[0]; - const token = await session.getAccessToken(); - try { - await this._githubServer.revokeToken(token); - } catch (_) { - // ignore, should still remove from keychain - } + this._sessions.splice(sessionIndex, 1); + // TODO revert + // Cannot revoke tokens from auth server, no clientId available + // const token = await session.getAccessToken(); + // try { + // await this._githubServer.revokeToken(token); + // } catch (_) { + // // ignore, should still remove from keychain + // } } await this.storeSessions(); diff --git a/extensions/github-authentication/src/githubServer.ts b/extensions/github-authentication/src/githubServer.ts index 890993950b..c7882c676f 100644 --- a/extensions/github-authentication/src/githubServer.ts +++ b/extensions/github-authentication/src/githubServer.ts @@ -4,11 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import * as https from 'https'; +import * as nls from 'vscode-nls'; import * as vscode from 'vscode'; import * as uuid from 'uuid'; import { PromiseAdapter, promiseFromEvent } from './common/utils'; import Logger from './common/logger'; -import ClientRegistrar, { ClientDetails } from './common/clientRegistrar'; +import ClientRegistrar from './common/clientRegistrar'; + +const localize = nls.loadMessageBundle(); + +export const NETWORK_ERROR = 'network error'; +const AUTH_RELAY_SERVER = 'vscode-auth.github.com'; class UriEventHandler extends vscode.EventEmitter implements vscode.UriHandler { public handleUri(uri: vscode.Uri) { @@ -18,8 +24,8 @@ class UriEventHandler extends vscode.EventEmitter implements vscode. export const uriHandler = new UriEventHandler; -const exchangeCodeForToken: (state: string, clientDetails: ClientDetails) => PromiseAdapter = - (state, clientDetails) => async (uri, resolve, reject) => { +const exchangeCodeForToken: (state: string, host: string, getPath: (code: string) => string) => PromiseAdapter = + (state, host, getPath) => async (uri, resolve, reject) => { Logger.info('Exchanging code for token...'); const query = parseQuery(uri); const code = query.code; @@ -30,8 +36,8 @@ const exchangeCodeForToken: (state: string, clientDetails: ClientDetails) => Pro } const post = https.request({ - host: 'github.com', - path: `/login/oauth/access_token?client_id=${clientDetails.id}&client_secret=${clientDetails.secret}&state=${query.state}&code=${code}`, + host: host, + path: getPath(code), method: 'POST', headers: { Accept: 'application/json' @@ -67,15 +73,61 @@ function parseQuery(uri: vscode.Uri) { } export class GitHubServer { + private _statusBarItem: vscode.StatusBarItem | undefined; + public async login(scopes: string): Promise { Logger.info('Logging in...'); + this.updateStatusBarItem(true); + const state = uuid(); const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/did-authenticate`)); - const clientDetails = scopes === 'vso' ? ClientRegistrar.getGitHubAppDetails() : ClientRegistrar.getClientDetails(callbackUri); - const uri = vscode.Uri.parse(`https://github.com/login/oauth/authorize?redirect_uri=${encodeURIComponent(callbackUri.toString())}&scope=${scopes}&state=${state}&client_id=${clientDetails.id}`); + let uri = vscode.Uri.parse(`https://${AUTH_RELAY_SERVER}/authorize/?callbackUri=${encodeURIComponent(callbackUri.toString())}&scope=${scopes}&state=${state}&responseType=code`); + if (scopes === 'vso') { + const clientDetails = ClientRegistrar.getGitHubAppDetails(); + uri = vscode.Uri.parse(`https://github.com/login/oauth/authorize?redirect_uri=${encodeURIComponent(callbackUri.toString())}&scope=${scopes}&state=${state}&client_id=${clientDetails.id}`); + } vscode.env.openExternal(uri); - return promiseFromEvent(uriHandler.event, exchangeCodeForToken(state, clientDetails)); + + return promiseFromEvent(uriHandler.event, exchangeCodeForToken(state, + scopes === 'vso' ? 'github.com' : AUTH_RELAY_SERVER, + (code) => { + if (scopes === 'vso') { + const clientDetails = ClientRegistrar.getGitHubAppDetails(); + return `/login/oauth/access_token?client_id=${clientDetails.id}&client_secret=${clientDetails.secret}&state=${state}&code=${code}`; + } else { + return `/token?code=${code}&state=${state}`; + } + })).finally(() => { + this.updateStatusBarItem(false); + }); + } + + private updateStatusBarItem(isStart?: boolean) { + if (isStart && !this._statusBarItem) { + this._statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left); + this._statusBarItem.text = localize('signingIn', "$(mark-github) Signing in to github.com..."); + this._statusBarItem.command = 'github.provide-token'; + this._statusBarItem.show(); + } + + if (!isStart && this._statusBarItem) { + this._statusBarItem.dispose(); + this._statusBarItem = undefined; + } + } + + public async manuallyProvideToken() { + const uriOrToken = await vscode.window.showInputBox({ prompt: 'Token', ignoreFocusOut: true }); + if (!uriOrToken) { return; } + try { + const uri = vscode.Uri.parse(uriOrToken); + if (!uri.scheme || uri.scheme === 'file') { throw new Error; } + uriHandler.handleUri(uri); + } catch (e) { + Logger.error(e); + vscode.window.showErrorMessage(localize('unexpectedInput', "The input did not matched the expected format")); + } } public async hasUserInstallation(token: string): Promise { @@ -120,7 +172,7 @@ export class GitHubServer { const uri = vscode.Uri.parse(`https://github.com/apps/microsoft-visual-studio-code/installations/new?state=${state}`); vscode.env.openExternal(uri); - return promiseFromEvent(uriHandler.event, exchangeCodeForToken(state, clientDetails)); + return promiseFromEvent(uriHandler.event, exchangeCodeForToken(state, 'github.com', (code) => `/login/oauth/access_token?client_id=${clientDetails.id}&client_secret=${clientDetails.secret}&state=${state}&code=${code}`)); } public async getUserInfo(token: string): Promise<{ id: string, accountName: string }> { @@ -145,7 +197,7 @@ export class GitHubServer { Logger.info('Got account info!'); resolve({ id: json.id, accountName: json.login }); } else { - Logger.error('Getting account info failed'); + Logger.error(`Getting account info failed: ${result.statusMessage}`); reject(new Error(result.statusMessage)); } }); @@ -153,7 +205,52 @@ export class GitHubServer { post.end(); post.on('error', err => { - reject(err); + Logger.error(err.message); + reject(new Error(NETWORK_ERROR)); + }); + }); + } + + public async validateToken(token: string): Promise { + return new Promise(async (resolve, reject) => { + const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/did-authenticate`)); + const clientDetails = ClientRegistrar.getClientDetails(callbackUri); + const detailsString = `${clientDetails.id}:${clientDetails.secret}`; + + const payload = JSON.stringify({ access_token: token }); + + Logger.info('Validating token...'); + const post = https.request({ + host: 'api.github.com', + path: `/applications/${clientDetails.id}/token`, + method: 'POST', + headers: { + Authorization: `Basic ${Buffer.from(detailsString).toString('base64')}`, + 'User-Agent': 'Visual-Studio-Code', + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(payload) + } + }, result => { + const buffer: Buffer[] = []; + result.on('data', (chunk: Buffer) => { + buffer.push(chunk); + }); + result.on('end', () => { + if (result.statusCode === 200) { + Logger.info('Validated token!'); + resolve(); + } else { + Logger.info(`Validating token failed: ${result.statusMessage}`); + reject(new Error(result.statusMessage)); + } + }); + }); + + post.write(payload); + post.end(); + post.on('error', err => { + Logger.error(err.message); + reject(new Error(NETWORK_ERROR)); }); }); } diff --git a/extensions/image-preview/src/extension.ts b/extensions/image-preview/src/extension.ts index 0c5aedf6e2..ff91e5a909 100644 --- a/extensions/image-preview/src/extension.ts +++ b/extensions/image-preview/src/extension.ts @@ -24,7 +24,7 @@ export function activate(context: vscode.ExtensionContext) { const previewManager = new PreviewManager(extensionRoot, sizeStatusBarEntry, binarySizeStatusBarEntry, zoomStatusBarEntry); context.subscriptions.push(vscode.window.registerCustomEditorProvider2(PreviewManager.viewType, previewManager, { - supportsMultipleEditorsPerResource: true, + supportsMultipleEditorsPerDocument: true, })); context.subscriptions.push(vscode.commands.registerCommand('imagePreview.zoomIn', () => { diff --git a/extensions/image-preview/src/preview.ts b/extensions/image-preview/src/preview.ts index b61e029bd7..7f375919ac 100644 --- a/extensions/image-preview/src/preview.ts +++ b/extensions/image-preview/src/preview.ts @@ -13,7 +13,7 @@ import { BinarySizeStatusBarEntry } from './binarySizeStatusBarEntry'; const localize = nls.loadMessageBundle(); -export class PreviewManager implements vscode.CustomEditorProvider { +export class PreviewManager implements vscode.CustomReadonlyEditorProvider { public static readonly viewType = 'imagePreview.previewEditor'; diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index 8a12ff009c..d2edb7d54a 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -120,10 +120,10 @@ ] }, "dependencies": { - "request-light": "^0.2.5", + "request-light": "^0.3.0", "vscode-extension-telemetry": "0.1.1", - "vscode-languageclient": "^6.1.1", - "vscode-nls": "^4.1.1" + "vscode-languageclient": "^6.1.3", + "vscode-nls": "^4.1.2" }, "devDependencies": { "@types/node": "^12.11.7" diff --git a/extensions/json-language-features/server/README.md b/extensions/json-language-features/server/README.md index 18e397c533..f68b03fcce 100644 --- a/extensions/json-language-features/server/README.md +++ b/extensions/json-language-features/server/README.md @@ -23,6 +23,7 @@ The server implements the following capabilities of the language server protocol - [Code Formatting](https://microsoft.github.io/language-server-protocol/specification#textDocument_rangeFormatting) supporting ranges and formatting the whole document. - [Folding Ranges](https://microsoft.github.io/language-server-protocol/specification#textDocument_foldingRange) for all folding ranges in the document. - Semantic Selection for semantic selection for one or multiple cursor positions. +- [Goto Definition](https://microsoft.github.io/language-server-protocol/specification#textDocument_definition) for $ref references in JSON schemas - [Diagnostics (Validation)](https://microsoft.github.io/language-server-protocol/specification#textDocument_publishDiagnostics) are pushed for all open documents - syntax errors - structural validation based on the document's [JSON schema](http://json-schema.org/). diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index b66807fddf..b48b844c03 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -13,8 +13,8 @@ "main": "./out/jsonServerMain", "dependencies": { "jsonc-parser": "^2.2.1", - "request-light": "^0.2.5", - "vscode-json-languageservice": "^3.5.2", + "request-light": "^0.3.0", + "vscode-json-languageservice": "^3.6.0", "vscode-languageserver": "^6.1.1", "vscode-uri": "^2.1.1" }, diff --git a/extensions/json-language-features/server/src/jsonServerMain.ts b/extensions/json-language-features/server/src/jsonServerMain.ts index f4c36b2bd9..2c8cd6ee48 100644 --- a/extensions/json-language-features/server/src/jsonServerMain.ts +++ b/extensions/json-language-features/server/src/jsonServerMain.ts @@ -174,7 +174,8 @@ connection.onInitialize((params: InitializeParams): InitializeResult => { documentRangeFormattingProvider: params.initializationOptions.provideFormatter === true, colorProvider: {}, foldingRangeProvider: true, - selectionRangeProvider: true + selectionRangeProvider: true, + definitionProvider: true }; return { capabilities }; @@ -516,5 +517,16 @@ connection.onSelectionRanges((params, token) => { }, [], `Error while computing selection ranges for ${params.textDocument.uri}`, token); }); +connection.onDefinition((params, token) => { + return runSafeAsync(async () => { + const document = documents.get(params.textDocument.uri); + if (document) { + const jsonDocument = getJSONDocument(document); + return languageService.findDefinition(document, params.position, jsonDocument); + } + return []; + }, [], `Error while computing definitions for ${params.textDocument.uri}`, token); +}); + // Listen on the connection connection.listen(); diff --git a/extensions/json-language-features/server/yarn.lock b/extensions/json-language-features/server/yarn.lock index eb34e96baa..4d15a61184 100644 --- a/extensions/json-language-features/server/yarn.lock +++ b/extensions/json-language-features/server/yarn.lock @@ -53,7 +53,7 @@ http-proxy-agent@^2.1.0: agent-base "4" debug "3.1.0" -https-proxy-agent@^2.2.3: +https-proxy-agent@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== @@ -71,24 +71,24 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= -request-light@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.2.5.tgz#38a3da7b2e56f7af8cbba57e8a94930ee2380746" - integrity sha512-eBEh+GzJAftUnex6tcL6eV2JCifY0+sZMIUpUPOVXbs2nV5hla4ZMmO3icYKGuGVuQ2zHE9evh4OrRcH4iyYYw== +request-light@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.3.0.tgz#04daa783e7f0a70392328dda4b546f3e27845f2d" + integrity sha512-xlVlZVT0ZvCT+c3zm3SjeFCzchoQxsUUmx5fkal0I6RIDJK+lmb1UYyKJ7WM4dTfnzHP4ElWwAf8Dli8c0/tVA== dependencies: http-proxy-agent "^2.1.0" - https-proxy-agent "^2.2.3" + https-proxy-agent "^2.2.4" vscode-nls "^4.1.1" -vscode-json-languageservice@^3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.5.2.tgz#4b898140a8e581359c10660845a4cae15dcbb4f9" - integrity sha512-9cUvBq00O08lpWVVOx6tQ1yLxCHss79nsUdEAVYGomRyMbnPBmc0AkYPcXI9WK1EM6HBo0R9Zo3NjFhcICpy4A== +vscode-json-languageservice@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.6.0.tgz#133a1e2c3a3dffe38564a1ba948516805c3c1869" + integrity sha512-dXzFywypUZ9T0tjr4fREZiknXDz6vAGx1zsxbQY1+9DOpjMfbz0VLP873KmcbuvL4K3nseKTxc4TKHu8kLXRMw== dependencies: jsonc-parser "^2.2.1" vscode-languageserver-textdocument "^1.0.1" vscode-languageserver-types "^3.15.1" - vscode-nls "^4.1.1" + vscode-nls "^4.1.2" vscode-uri "^2.1.1" vscode-jsonrpc@^5.0.1: @@ -126,6 +126,11 @@ vscode-nls@^4.1.1: resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.1.tgz#f9916b64e4947b20322defb1e676a495861f133c" integrity sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A== +vscode-nls@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.2.tgz#ca8bf8bb82a0987b32801f9fddfdd2fb9fd3c167" + integrity sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw== + vscode-uri@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-2.1.1.tgz#5aa1803391b6ebdd17d047f51365cf62c38f6e90" diff --git a/extensions/json-language-features/yarn.lock b/extensions/json-language-features/yarn.lock index cbda171084..93d78c8c00 100644 --- a/extensions/json-language-features/yarn.lock +++ b/extensions/json-language-features/yarn.lock @@ -76,7 +76,7 @@ http-proxy-agent@^2.1.0: agent-base "4" debug "3.1.0" -https-proxy-agent@^2.2.3: +https-proxy-agent@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== @@ -94,13 +94,13 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== -request-light@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.2.5.tgz#38a3da7b2e56f7af8cbba57e8a94930ee2380746" - integrity sha512-eBEh+GzJAftUnex6tcL6eV2JCifY0+sZMIUpUPOVXbs2nV5hla4ZMmO3icYKGuGVuQ2zHE9evh4OrRcH4iyYYw== +request-light@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.3.0.tgz#04daa783e7f0a70392328dda4b546f3e27845f2d" + integrity sha512-xlVlZVT0ZvCT+c3zm3SjeFCzchoQxsUUmx5fkal0I6RIDJK+lmb1UYyKJ7WM4dTfnzHP4ElWwAf8Dli8c0/tVA== dependencies: http-proxy-agent "^2.1.0" - https-proxy-agent "^2.2.3" + https-proxy-agent "^2.2.4" vscode-nls "^4.1.1" semver@^5.3.0: @@ -125,10 +125,10 @@ vscode-jsonrpc@^5.0.1: resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-5.0.1.tgz#9bab9c330d89f43fc8c1e8702b5c36e058a01794" integrity sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A== -vscode-languageclient@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-6.1.1.tgz#91b62e416c5abbf2013ae3726f314a19c22a8457" - integrity sha512-mB6d8Tg+82l8EFUfR+SBu0+lCshyKVgC5E5+MQ0/BJa+9AgeBjtG5npoGaCo4/VvWzK0ZRGm85zU5iRp1RYPIA== +vscode-languageclient@^6.1.3: + version "6.1.3" + resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-6.1.3.tgz#c979c5bb5855714a0307e998c18ca827c1b3953a" + integrity sha512-YciJxk08iU5LmWu7j5dUt9/1OLjokKET6rME3cI4BRpiF6HZlusm2ZwPt0MYJ0lV5y43sZsQHhyon2xBg4ZJVA== dependencies: semver "^6.3.0" vscode-languageserver-protocol "^3.15.3" @@ -151,6 +151,11 @@ vscode-nls@^4.1.1: resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.1.tgz#f9916b64e4947b20322defb1e676a495861f133c" integrity sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A== +vscode-nls@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.2.tgz#ca8bf8bb82a0987b32801f9fddfdd2fb9fd3c167" + integrity sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw== + zone.js@0.7.6: version "0.7.6" resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009" diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 7b0cd21765..4bba6c740b 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -313,7 +313,7 @@ "customEditors": [ { "viewType": "vscode.markdown.preview.editor", - "displayName": "(Experimental) VS Code Markdown Preview", + "displayName": "Markdown Preview (Experimental)", "priority": "option", "selector": [ { diff --git a/extensions/theme-defaults/themes/dark_plus.json b/extensions/theme-defaults/themes/dark_plus.json index 560fd159a0..f83f763b9a 100644 --- a/extensions/theme-defaults/themes/dark_plus.json +++ b/extensions/theme-defaults/themes/dark_plus.json @@ -104,7 +104,7 @@ "variable.other.enummember" ], "settings": { - "foreground": "#D4D4D4", + "foreground": "#51B6C4", } }, { diff --git a/extensions/theme-defaults/themes/light_plus.json b/extensions/theme-defaults/themes/light_plus.json index 8f65dc5b4f..00c2a6cb47 100644 --- a/extensions/theme-defaults/themes/light_plus.json +++ b/extensions/theme-defaults/themes/light_plus.json @@ -104,7 +104,7 @@ "variable.other.enummember" ], "settings": { - "foreground": "#000000", + "foreground": "#328267", } }, { diff --git a/extensions/vscode-account/package.json b/extensions/vscode-account/package.json index 38fc902a52..0deacbea46 100644 --- a/extensions/vscode-account/package.json +++ b/extensions/vscode-account/package.json @@ -17,18 +17,6 @@ "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", "main": "./out/extension.js", "contributes": { - "commands": [ - { - "command": "microsoft.signin", - "title": "%signIn%", - "category": "%displayName%" - }, - { - "command": "microsoft.signout", - "title": "%signOut%", - "category": "%displayName%" - } - ], "configuration": { "title": "Microsoft Account", "properties": { diff --git a/extensions/vscode-account/src/extension.ts b/extensions/vscode-account/src/extension.ts index 7833166849..4f19901e18 100644 --- a/extensions/vscode-account/src/extension.ts +++ b/extensions/vscode-account/src/extension.ts @@ -5,11 +5,8 @@ import * as vscode from 'vscode'; import { AzureActiveDirectoryService, onDidChangeSessions } from './AADHelper'; -import * as nls from 'vscode-nls'; import TelemetryReporter from 'vscode-extension-telemetry'; -const localize = nls.loadMessageBundle(); - export const DEFAULT_SCOPES = 'https://management.core.windows.net/.default offline_access'; export async function activate(context: vscode.ExtensionContext) { @@ -48,39 +45,6 @@ export async function activate(context: vscode.ExtensionContext) { } })); - context.subscriptions.push(vscode.commands.registerCommand('microsoft.signin', () => { - return loginService.login(DEFAULT_SCOPES); - })); - - context.subscriptions.push(vscode.commands.registerCommand('microsoft.signout', async () => { - const sessions = loginService.sessions; - if (sessions.length === 0) { - return; - } - - if (sessions.length === 1) { - const id = loginService.sessions[0].id; - await loginService.logout(id); - onDidChangeSessions.fire({ added: [], removed: [id], changed: [] }); - vscode.window.showInformationMessage(localize('signedOut', "Successfully signed out.")); - return; - } - - const selectedSession = await vscode.window.showQuickPick(sessions.map(session => { - return { - id: session.id, - label: session.account.displayName - }; - })); - - if (selectedSession) { - await loginService.logout(selectedSession.id); - onDidChangeSessions.fire({ added: [], removed: [selectedSession.id], changed: [] }); - vscode.window.showInformationMessage(localize('signedOut', "Successfully signed out.")); - return; - } - })); - return; } diff --git a/extensions/vscode-colorize-tests/.gitignore b/extensions/vscode-colorize-tests/.gitignore deleted file mode 100644 index 8e5962ee72..0000000000 --- a/extensions/vscode-colorize-tests/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -out -node_modules \ No newline at end of file diff --git a/extensions/vscode-colorize-tests/.vscode/launch.json b/extensions/vscode-colorize-tests/.vscode/launch.json deleted file mode 100644 index 73c3753c75..0000000000 --- a/extensions/vscode-colorize-tests/.vscode/launch.json +++ /dev/null @@ -1,17 +0,0 @@ -// A launch configuration that compiles the extension and then opens it inside a new window -{ - "version": "0.1.0", - "configurations": [ - { - "name": "Launch Tests", - "type": "extensionHost", - "request": "launch", - "runtimeExecutable": "${execPath}", - "args": ["${workspaceFolder}/../../", "${workspaceFolder}/test", "--extensionDevelopmentPath=${workspaceFolder}", "--extensionTestsPath=${workspaceFolder}/out" ], - "stopOnEntry": false, - "sourceMaps": true, - "outDir": "${workspaceFolder}/out", - "preLaunchTask": "npm" - } - ] -} \ No newline at end of file diff --git a/extensions/vscode-colorize-tests/.vscode/tasks.json b/extensions/vscode-colorize-tests/.vscode/tasks.json deleted file mode 100644 index 390a93a3a7..0000000000 --- a/extensions/vscode-colorize-tests/.vscode/tasks.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version": "2.0.0", - "command": "npm", - "type": "shell", - "presentation": { - "reveal": "silent" - }, - "args": ["run", "compile"], - "isBackground": true, - "problemMatcher": "$tsc-watch" -} diff --git a/extensions/vscode-colorize-tests/package.json b/extensions/vscode-colorize-tests/package.json deleted file mode 100644 index c976dbc8d0..0000000000 --- a/extensions/vscode-colorize-tests/package.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "name": "vscode-colorize-tests", - "description": "Colorize tests for VS Code", - "version": "0.0.1", - "publisher": "vscode", - "license": "MIT", - "private": true, - "activationEvents": [ - "onLanguage:json" - ], - "main": "./out/colorizerTestMain", - "enableProposedApi": true, - "engines": { - "vscode": "*" - }, - "scripts": { - "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-colorize-tests ./tsconfig.json" - }, - "dependencies": { - "jsonc-parser": "2.2.1" - }, - "devDependencies": { - "@types/node": "^12.11.7", - "mocha-junit-reporter": "^1.17.0", - "mocha-multi-reporters": "^1.1.7", - "vscode": "1.1.5" - }, - "contributes": { - "semanticTokenTypes": [ - { - "id": "testToken", - "description": "A test token" - } - ], - "semanticTokenModifiers": [ - { - "id": "testModifier", - "description": "A test modifier" - } - ], - "semanticTokenScopes": [ - { - "scopes": { - "testToken": [ - "entity.name.function.special" - ] - } - } - ], - "productIconThemes": [ - { - "id": "Test Product Icons", - "label": "The Test Product Icon Theme", - "path": "./producticons/test-product-icon-theme.json", - "_watch": true - } - ] - } -} diff --git a/extensions/vscode-colorize-tests/producticons/ElegantIcons.woff b/extensions/vscode-colorize-tests/producticons/ElegantIcons.woff deleted file mode 100644 index 393305253e5fc01d3369b4cf2735b08929432f96..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63664 zcmaf(b9Cllv%qWH_SSZ{-g0Z(wr#uJVvDV9+qU1@erwykz4!0?oi8VIGV?o=OeT3w zp2;Nc^5Ww1Dk}0IAkVcR@E{-{U~~o`px@j7XAuz>CjkKgg#iJ%O#uNTb#mbTm=G6N zkp%&{%l&r8`xXK-hs7`nQ8Dpv*7V}Teb`fv6c+ZsB6 zfZWr62UP|E0k^v=Y?rh!H8cSMc?|gWVg44*PpA?Ui*NYNYQJrgZ=rxd`P#Rzb@BLS zv)}gS_x@`*(^%-Xh92L?d-`8LsQ(pED+LfcLtE2t*7|Lqf1i85ZLh4VgT1rMcfN(+ z4B=ZyK)^vjJTeb_VSQA@mh2a0?I}n@(&7T*Niu|1>T9n;#A1S|_en^|SQ#M{Sy-bG zq5{@2TyibQBY99LVnCwJmP({bX?&iZ9YxEjqp(vk`}f$*Cr@>$cD6pO~u497uZH7 z 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 z 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 - - - - Your Font/Glyphs - - - - - - -
-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- -
- -
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- -
- -
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- -
- -
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- -
- -
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
-
-
-

Class Names

-
- - -  arrow_up - - - -  arrow_down - - - -  arrow_left - - - -  arrow_right - - - -  arrow_left-up - - - -  arrow_right-up - - - -  arrow_right-down - - - -  arrow_left-down - - - -  arrow-up-down - - - -  arrow_up-down_alt - - - -  arrow_left-right_alt - - - -  arrow_left-right - - - -  arrow_expand_alt2 - - - -  arrow_expand_alt - - - -  arrow_condense - - - -  arrow_expand - - - -  arrow_move - - - -  arrow_carrot-up - - - -  arrow_carrot-down - - - -  arrow_carrot-left - - - -  arrow_carrot-right - - - -  arrow_carrot-2up - - - -  arrow_carrot-2down - - - -  arrow_carrot-2left - - - -  arrow_carrot-2right - - - -  arrow_carrot-up_alt2 - - - -  arrow_carrot-down_alt2 - - - -  arrow_carrot-left_alt2 - - - -  arrow_carrot-right_alt2 - - - -  arrow_carrot-2up_alt2 - - - -  arrow_carrot-2down_alt2 - - - -  arrow_carrot-2left_alt2 - - - -  arrow_carrot-2right_alt2 - - - -  arrow_triangle-up - - - -  arrow_triangle-down - - - -  arrow_triangle-left - - - -  arrow_triangle-right - - - -  arrow_triangle-up_alt2 - - - -  arrow_triangle-down_alt2 - - - -  arrow_triangle-left_alt2 - - - -  arrow_triangle-right_alt2 - - - -  arrow_back - - - -  icon_minus-06 - - - -  icon_plus - - - -  icon_close - - - -  icon_check - - - -  icon_minus_alt2 - - - -  icon_plus_alt2 - - - -  icon_close_alt2 - - - -  icon_check_alt2 - - - -  icon_zoom-out_alt - - - -  icon_zoom-in_alt - - - -  icon_search - - - -  icon_box-empty - - - -  icon_box-selected - - - -  icon_minus-box - - - -  icon_plus-box - - - -  icon_box-checked - - - -  icon_circle-empty - - - -  icon_circle-slelected - - - -  icon_stop_alt2 - - - -  icon_stop - - - -  icon_pause_alt2 - - - -  icon_pause - - - -  icon_menu - - - -  icon_menu-square_alt2 - - - -  icon_menu-circle_alt2 - - - -  icon_ul - - - -  icon_ol - - - -  icon_adjust-horiz - - - -  icon_adjust-vert - - - -  icon_document_alt - - - -  icon_documents_alt - - - -  icon_pencil - - - -  icon_pencil-edit_alt - - - -  icon_pencil-edit - - - -  icon_folder-alt - - - -  icon_folder-open_alt - - - -  icon_folder-add_alt - - - -  icon_info_alt - - - -  icon_error-oct_alt - - - -  icon_error-circle_alt - - - -  icon_error-triangle_alt - - - -  icon_question_alt2 - - - -  icon_question - - - -  icon_comment_alt - - - -  icon_chat_alt - - - -  icon_vol-mute_alt - - - -  icon_volume-low_alt - - - -  icon_volume-high_alt - - - -  icon_quotations - - - -  icon_quotations_alt2 - - - -  icon_clock_alt - - - -  icon_lock_alt - - - -  icon_lock-open_alt - - - -  icon_key_alt - - - -  icon_cloud_alt - - - -  icon_cloud-upload_alt - - - -  icon_cloud-download_alt - - - -  icon_image - - - -  icon_images - - - -  icon_lightbulb_alt - - - -  icon_gift_alt - - - -  icon_house_alt - - - -  icon_genius - - - -  icon_mobile - - - -  icon_tablet - - - -  icon_laptop - - - -  icon_desktop - - - -  icon_camera_alt - - - -  icon_mail_alt - - - -  icon_cone_alt - - - -  icon_ribbon_alt - - - -  icon_bag_alt - - - -  icon_creditcard - - - -  icon_cart_alt - - - -  icon_paperclip - - - -  icon_tag_alt - - - -  icon_tags_alt - - - -  icon_trash_alt - - - -  icon_cursor_alt - - - -  icon_mic_alt - - - -  icon_compass_alt - - - -  icon_pin_alt - - - -  icon_pushpin_alt - - - -  icon_map_alt - - - -  icon_drawer_alt - - - -  icon_toolbox_alt - - - -  icon_book_alt - - - -  icon_calendar - - - -  icon_film - - - -  icon_table - - - -  icon_contacts_alt - - - -  icon_headphones - - - -  icon_lifesaver - - - -  icon_piechart - - - -  icon_refresh - - - -  icon_link_alt - - - -  icon_link - - - -  icon_loading - - - -  icon_blocked - - - -  icon_archive_alt - - - -  icon_heart_alt - - -
- - - -  icon_printer - - - -  icon_calulator - - - -  icon_building - - - -  icon_floppy - - - -  icon_drive - - - -  icon_search-2 - - - -  icon_id - - - -  icon_id-2 - - - -  icon_puzzle - - - -  icon_like - - - -  icon_dislike - - - -  icon_mug - - - -  icon_currency - - - -  icon_wallet - - - -  icon_pens - - - -  icon_easel - - - -  icon_flowchart - - - -  icon_datareport - - - -  icon_briefcase - - - -  icon_shield - - - -  icon_percent - - - -  icon_globe - - - -  icon_globe-2 - - - -  icon_target - - - -  icon_hourglass - - - -  icon_balance - - -
- - - -  icon_star_alt - - - -  icon_star-half_alt - - - -  icon_star - - - -  icon_star-half - - - -  icon_tools - - - -  icon_tool - - - -  icon_cog - - - -  icon_cogs - - - -  arrow_up_alt - - - -  arrow_down_alt - - - -  arrow_left_alt - - - -  arrow_right_alt - - - -  arrow_left-up_alt - - - -  arrow_right-up_alt - - - -  arrow_right-down_alt - - - -  arrow_left-down_alt - - - -  arrow_condense_alt - - - -  arrow_expand_alt3 - - - -  arrow_carrot_up_alt - - - -  arrow_carrot-down_alt - - - -  arrow_carrot-left_alt - - - -  arrow_carrot-right_alt - - - -  arrow_carrot-2up_alt - - - -  arrow_carrot-2dwnn_alt - - - -  arrow_carrot-2left_alt - - - -  arrow_carrot-2right_alt - - - -  arrow_triangle-up_alt - - - -  arrow_triangle-down_alt - - - -  arrow_triangle-left_alt - - - -  arrow_triangle-right_alt - - - -  icon_minus_alt - - - -  icon_plus_alt - - - -  icon_close_alt - - - -  icon_check_alt - - - -  icon_zoom-out - - - -  icon_zoom-in - - - -  icon_stop_alt - - - -  icon_menu-square_alt - - - -  icon_menu-circle_alt - - - -  icon_document - - - -  icon_documents - - - -  icon_pencil_alt - - - -  icon_folder - - - -  icon_folder-open - - - -  icon_folder-add - - - -  icon_folder_upload - - - -  icon_folder_download - - - -  icon_info - - - -  icon_error-circle - - - -  icon_error-oct - - - -  icon_error-triangle - - - -  icon_question_alt - - - -  icon_comment - - - -  icon_chat - - - -  icon_vol-mute - - - -  icon_volume-low - - - -  icon_volume-high - - - -  icon_quotations_alt - - - -  icon_clock - - - -  icon_lock - - - -  icon_lock-open - - - -  icon_key - - - -  icon_cloud - - - -  icon_cloud-upload - - - -  icon_cloud-download - - - -  icon_lightbulb - - - -  icon_gift - - - -  icon_house - - - -  icon_camera - - - -  icon_mail - - - -  icon_cone - - - -  icon_ribbon - - - -  icon_bag - - - -  icon_cart - - - -  icon_tag - - - -  icon_tags - - - -  icon_trash - - - -  icon_cursor - - - -  icon_mic - - - -  icon_compass - - - -  icon_pin - - - -  icon_pushpin - - - -  icon_map - - - -  icon_drawer - - - -  icon_toolbox - - - -  icon_book - - - -  icon_contacts - - - -  icon_archive - - - -  icon_heart - - - -  icon_profile - - - -  icon_group - - - -  icon_grid-2x2 - - - -  icon_grid-3x3 - - - -  icon_music - - - -  icon_pause_alt - - - -  icon_phone - - - -  icon_upload - - - -  icon_download - - - -  icon_rook - - -
- - - -  icon_printer-alt - - - -  icon_calculator_alt - - - -  icon_building_alt - - - -  icon_floppy_alt - - - -  icon_drive_alt - - - -  icon_search_alt - - - -  icon_id_alt - - - -  icon_id-2_alt - - - -  icon_puzzle_alt - - - -  icon_like_alt - - - -  icon_dislike_alt - - - -  icon_mug_alt - - - -  icon_currency_alt - - - -  icon_wallet_alt - - - -  icon_pens_alt - - - -  icon_easel_alt - - - -  icon_flowchart_alt - - - -  icon_datareport_alt - - - -  icon_briefcase_alt - - - -  icon_shield_alt - - - -  icon_percent_alt - - - -  icon_globe_alt - - - -  icon_clipboard - - -
- - - -  social_facebook - - - -  social_twitter - - - -  social_pinterest - - - -  social_googleplus - - - -  social_tumblr - - - -  social_tumbleupon - - - -  social_wordpress - - - -  social_instagram - - - -  social_dribbble - - - -  social_vimeo - - - -  social_linkedin - - - -  social_rss - - - -  social_deviantart - - - -  social_share - - - -  social_myspace - - - -  social_skype - - - -  social_youtube - - - -  social_picassa - - - -  social_googledrive - - - -  social_flickr - - - -  social_blogger - - - -  social_spotify - - - -  social_delicious - - - -  social_facebook_circle - - - -  social_twitter_circle - - - -  social_pinterest_circle - - - -  social_googleplus_circle - - - -  social_tumblr_circle - - - -  social_stumbleupon_circle - - - -  social_wordpress_circle - - - -  social_instagram_circle - - - -  social_dribbble_circle - - - -  social_vimeo_circle - - - -  social_linkedin_circle - - - -  social_rss_circle - - - -  social_deviantart_circle - - - -  social_share_circle - - - -  social_myspace_circle - - - -  social_skype_circle - - - -  social_youtube_circle - - - -  social_picassa_circle - - - -  social_googledrive_alt2 - - - -  social_flickr_circle - - - -  social_blogger_circle - - - -  social_spotify_circle - - - -  social_delicious_circle - - - -  social_facebook_square - - - -  social_twitter_square - - - -  social_pinterest_square - - - -  social_googleplus_square - - - -  social_tumblr_square - - - -  social_stumbleupon_square - - - -  social_wordpress_square - - - -  social_instagram_square - - - -  social_dribbble_square - - - -  social_vimeo_square - - - -  social_linkedin_square - - - -  social_rss_square - - - -  social_deviantart_square - - - -  social_share_square - - - -  social_myspace_square - - - -  social_skype_square - - - -  social_youtube_square - - - -  social_picassa_square - - - -  social_googledrive_square - - - -  social_flickr_square - - - -  social_blogger_square - - - -  social_spotify_square - - - -  social_delicious_square - -
- -
- - - - diff --git a/extensions/vscode-colorize-tests/producticons/mit_license.txt b/extensions/vscode-colorize-tests/producticons/mit_license.txt deleted file mode 100644 index effefee5f0..0000000000 --- a/extensions/vscode-colorize-tests/producticons/mit_license.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) <2013> - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file diff --git a/extensions/vscode-colorize-tests/producticons/test-product-icon-theme.json b/extensions/vscode-colorize-tests/producticons/test-product-icon-theme.json deleted file mode 100644 index dc076aef5f..0000000000 --- a/extensions/vscode-colorize-tests/producticons/test-product-icon-theme.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - // ElegantIcons from https://www.elegantthemes.com/icons/elegant_font.zip - "fonts": [ - { - "id": "elegant", - "src": [ - { - "path": "./ElegantIcons.woff", - "format": "woff" - } - ], - "weight": "normal", - "style": "normal", - } - ], - "iconDefinitions": { - "chevron-down": { - "fontCharacter": "\\43", - }, - "chevron-right": { - "fontCharacter": "\\45" - }, - "error": { - "fontCharacter": "\\e062" - }, - "warning": { - "fontCharacter": "\\e063" - }, - "settings-gear": { - "fontCharacter": "\\e030" - }, - "files": { - "fontCharacter": "\\e056" - }, - "extensions": { - "fontCharacter": "\\e015" - }, - "debug-alt-2": { - "fontCharacter": "\\e072" - } - - } -} diff --git a/extensions/vscode-colorize-tests/src/colorizer.test.ts b/extensions/vscode-colorize-tests/src/colorizer.test.ts deleted file mode 100644 index 3abe4fb96c..0000000000 --- a/extensions/vscode-colorize-tests/src/colorizer.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import 'mocha'; -import * as assert from 'assert'; -import { commands, Uri } from 'vscode'; -import { join, basename, normalize, dirname } from 'path'; -import * as fs from 'fs'; - -function assertUnchangedTokens(testFixurePath: string, done: any) { - let fileName = basename(testFixurePath); - - return commands.executeCommand('_workbench.captureSyntaxTokens', Uri.file(testFixurePath)).then(data => { - try { - let resultsFolderPath = join(dirname(dirname(testFixurePath)), 'colorize-results'); - if (!fs.existsSync(resultsFolderPath)) { - fs.mkdirSync(resultsFolderPath); - } - let resultPath = join(resultsFolderPath, fileName.replace('.', '_') + '.json'); - if (fs.existsSync(resultPath)) { - let previousData = JSON.parse(fs.readFileSync(resultPath).toString()); - try { - assert.deepEqual(data, previousData); - } catch (e) { - fs.writeFileSync(resultPath, JSON.stringify(data, null, '\t'), { flag: 'w' }); - if (Array.isArray(data) && Array.isArray(previousData) && data.length === previousData.length) { - for (let i= 0; i < data.length; i++) { - let d = data[i]; - let p = previousData[i]; - if (d.c !== p.c || hasThemeChange(d.r, p.r)) { - throw e; - } - } - // different but no tokenization ot color change: no failure - } else { - throw e; - } - } - } else { - fs.writeFileSync(resultPath, JSON.stringify(data, null, '\t')); - } - done(); - } catch (e) { - done(e); - } - }, done); -} - -function hasThemeChange(d: any, p: any) : boolean { - let keys = Object.keys(d); - for (let key of keys) { - if (d[key] !== p[key]) { - return true; - } - } - return false; -} - -suite('colorization', () => { - let extensionsFolder = normalize(join(__dirname, '../../')); - let extensions = fs.readdirSync(extensionsFolder); - extensions.forEach(extension => { - let extensionColorizeFixturePath = join(extensionsFolder, extension, 'test', 'colorize-fixtures'); - if (fs.existsSync(extensionColorizeFixturePath)) { - let fixturesFiles = fs.readdirSync(extensionColorizeFixturePath); - fixturesFiles.forEach(fixturesFile => { - // define a test for each fixture - test(extension + '-' + fixturesFile, function (done) { - assertUnchangedTokens(join(extensionColorizeFixturePath, fixturesFile), done); - }); - }); - } - }); -}); diff --git a/extensions/vscode-colorize-tests/src/colorizerTestMain.ts b/extensions/vscode-colorize-tests/src/colorizerTestMain.ts deleted file mode 100644 index c12e4af29e..0000000000 --- a/extensions/vscode-colorize-tests/src/colorizerTestMain.ts +++ /dev/null @@ -1,66 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as vscode from 'vscode'; -import * as jsoncParser from 'jsonc-parser'; - -export function activate(context: vscode.ExtensionContext): any { - - const tokenTypes = ['type', 'struct', 'class', 'interface', 'enum', 'parameterType', 'function', 'variable', 'testToken']; - const tokenModifiers = ['static', 'abstract', 'deprecated', 'declaration', 'documentation', 'member', 'async', 'testModifier']; - - const legend = new vscode.SemanticTokensLegend(tokenTypes, tokenModifiers); - - const outputChannel = vscode.window.createOutputChannel('Semantic Tokens Test'); - - const documentSemanticHighlightProvider: vscode.DocumentSemanticTokensProvider = { - provideDocumentSemanticTokens(document: vscode.TextDocument): vscode.ProviderResult { - const builder = new vscode.SemanticTokensBuilder(); - - function addToken(value: string, startLine: number, startCharacter: number, length: number) { - const [type, ...modifiers] = value.split('.'); - - let tokenType = legend.tokenTypes.indexOf(type); - if (tokenType === -1) { - return; - } - - let tokenModifiers = 0; - for (let i = 0; i < modifiers.length; i++) { - const index = legend.tokenModifiers.indexOf(modifiers[i]); - if (index !== -1) { - tokenModifiers = tokenModifiers | 1 << index; - } - } - - - builder.push(startLine, startCharacter, length, tokenType, tokenModifiers); - - const selectedModifiers = legend.tokenModifiers.filter((_val, bit) => tokenModifiers & (1 << bit)).join(' '); - outputChannel.appendLine(`line: ${startLine}, character: ${startCharacter}, length ${length}, ${legend.tokenTypes[tokenType]} (${tokenType}), ${selectedModifiers} ${tokenModifiers.toString(2)}`); - } - - outputChannel.appendLine('---'); - - const visitor: jsoncParser.JSONVisitor = { - onObjectProperty: (property: string, _offset: number, _length: number, startLine: number, startCharacter: number) => { - addToken(property, startLine, startCharacter, property.length + 2); - }, - onLiteralValue: (value: any, _offset: number, length: number, startLine: number, startCharacter: number) => { - if (typeof value === 'string') { - addToken(value, startLine, startCharacter, length); - } - } - }; - jsoncParser.visit(document.getText(), visitor); - - return builder.build(); - } - }; - - - context.subscriptions.push(vscode.languages.registerDocumentSemanticTokensProvider({ pattern: '**/*semantic-test.json' }, documentSemanticHighlightProvider, legend)); - -} diff --git a/extensions/vscode-colorize-tests/src/index.ts b/extensions/vscode-colorize-tests/src/index.ts deleted file mode 100644 index 94656bf0ba..0000000000 --- a/extensions/vscode-colorize-tests/src/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -const path = require('path'); -const testRunner = require('vscode/lib/testrunner'); - -const suite = 'Integration Colorize Tests'; - -const options: any = { - ui: 'tdd', - useColors: (!process.env.BUILD_ARTIFACTSTAGINGDIRECTORY && process.platform !== 'win32'), - timeout: 60000 -}; - -if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) { - options.reporter = 'mocha-multi-reporters'; - options.reporterOptions = { - reporterEnabled: 'spec, mocha-junit-reporter', - mochaJunitReporterReporterOptions: { - testsuitesTitle: `${suite} ${process.platform}`, - mochaFile: path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${suite.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`) - } - }; -} - -testRunner.configure(options); - -export = testRunner; diff --git a/extensions/vscode-colorize-tests/src/typings/ref.d.ts b/extensions/vscode-colorize-tests/src/typings/ref.d.ts deleted file mode 100644 index b6afd2b313..0000000000 --- a/extensions/vscode-colorize-tests/src/typings/ref.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/// -/// -/// - diff --git a/extensions/vscode-colorize-tests/test/semantic-test/.vscode/settings.json b/extensions/vscode-colorize-tests/test/semantic-test/.vscode/settings.json deleted file mode 100644 index c91ebc862e..0000000000 --- a/extensions/vscode-colorize-tests/test/semantic-test/.vscode/settings.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "editor.tokenColorCustomizationsExperimental": { - "class": "#00b0b0", - "interface": "#845faf", - "function": "#ff00ff", - "*.declaration": { - "fontStyle": "underline" - }, - "*.declaration.member": { - "fontStyle": "italic bold", - } - } -} \ No newline at end of file diff --git a/extensions/vscode-colorize-tests/test/semantic-test/semantic-test.json b/extensions/vscode-colorize-tests/test/semantic-test/semantic-test.json deleted file mode 100644 index b250b5d2bc..0000000000 --- a/extensions/vscode-colorize-tests/test/semantic-test/semantic-test.json +++ /dev/null @@ -1,9 +0,0 @@ -[ - "class", "function.member.declaration", - "parameterType.declaration", "type", "parameterType.declaration", "type", - "variable.declaration", "parameterNames", - "function.member.declaration", - "interface.declaration", - "function.member.declaration", "testToken.testModifier" - -] diff --git a/extensions/vscode-colorize-tests/tsconfig.json b/extensions/vscode-colorize-tests/tsconfig.json deleted file mode 100644 index 296ddb38fc..0000000000 --- a/extensions/vscode-colorize-tests/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../shared.tsconfig.json", - "compilerOptions": { - "outDir": "./out" - }, - "include": [ - "src/**/*" - ] -} \ No newline at end of file diff --git a/extensions/vscode-colorize-tests/yarn.lock b/extensions/vscode-colorize-tests/yarn.lock deleted file mode 100644 index b8a66d65ef..0000000000 --- a/extensions/vscode-colorize-tests/yarn.lock +++ /dev/null @@ -1,2016 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@types/node@^12.11.7": - version "12.11.7" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a" - integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA== - -ajv@^5.1.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.3.0.tgz#4414ff74a50879c208ee5fdc826e32c303549eda" - integrity sha1-RBT/dKUIecII7l/cgm4ywwNUnto= - dependencies: - co "^4.6.0" - fast-deep-equal "^1.0.0" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.3.0" - -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= - -ansi-styles@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" - integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= - -arr-diff@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" - integrity sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8= - dependencies: - arr-flatten "^1.0.1" - -arr-flatten@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== - -array-differ@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031" - integrity sha1-7/UuN1gknTO+QCuLuOVkuytdQDE= - -array-union@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" - integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk= - dependencies: - array-uniq "^1.0.1" - -array-uniq@^1.0.1, array-uniq@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" - integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= - -array-unique@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" - integrity sha1-odl8yvy8JiXMcPrc6zalDFiwGlM= - -arrify@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" - integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= - -asn1@~0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" - integrity sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y= - -assert-plus@1.0.0, assert-plus@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= - -assert-plus@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" - integrity sha1-104bh+ev/A24qttwIfP+SBAasjQ= - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= - -aws-sign2@~0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" - integrity sha1-FDQt0428yU0OW4fXY81jYSwOeU8= - -aws-sign2@~0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" - integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= - -aws4@^1.2.1, aws4@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" - integrity sha1-g+9cqGCysy5KDe7e6MdxudtXRx4= - -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= - -bcrypt-pbkdf@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" - integrity sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40= - dependencies: - tweetnacl "^0.14.3" - -beeper@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/beeper/-/beeper-1.1.1.tgz#e6d5ea8c5dad001304a70b22638447f69cb2f809" - integrity sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak= - -block-stream@*: - version "0.0.9" - resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" - integrity sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo= - dependencies: - inherits "~2.0.0" - -boom@2.x.x: - version "2.10.1" - resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" - integrity sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8= - dependencies: - hoek "2.x.x" - -boom@4.x.x: - version "4.3.1" - resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31" - integrity sha1-T4owBctKfjiJ90kDD9JbluAdLjE= - dependencies: - hoek "4.x.x" - -boom@5.x.x: - version "5.2.0" - resolved "https://registry.yarnpkg.com/boom/-/boom-5.2.0.tgz#5dd9da6ee3a5f302077436290cb717d3f4a54e02" - integrity sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw== - dependencies: - hoek "4.x.x" - -brace-expansion@^1.1.7: - version "1.1.8" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" - integrity sha1-wHshHHyVLsH479Uad+8NHTmQopI= - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@^1.8.2: - version "1.8.5" - resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" - integrity sha1-uneWLhLf+WnWt2cR6RS3N4V79qc= - dependencies: - expand-range "^1.8.1" - preserve "^0.2.0" - repeat-element "^1.1.2" - -browser-stdout@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" - integrity sha1-81HTKWnTL6XXpVZxVCY9korjvR8= - -buffer-crc32@~0.2.3: - version "0.2.13" - resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" - integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= - -caseless@~0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" - integrity sha1-cVuW6phBWTzDMGeSP17GDr2k99c= - -caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" - integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= - -chalk@^1.0.0, chalk@^1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= - dependencies: - ansi-styles "^2.2.1" - escape-string-regexp "^1.0.2" - has-ansi "^2.0.0" - strip-ansi "^3.0.0" - supports-color "^2.0.0" - -charenc@~0.0.1: - version "0.0.2" - resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" - integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= - -clone-buffer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" - integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg= - -clone-stats@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1" - integrity sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE= - -clone-stats@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680" - integrity sha1-s3gt/4u1R04Yuba/D9/ngvh3doA= - -clone@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/clone/-/clone-0.2.0.tgz#c6126a90ad4f72dbf5acdb243cc37724fe93fc1f" - integrity sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8= - -clone@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.3.tgz#298d7e2231660f40c003c2ed3140decf3f53085f" - integrity sha1-KY1+IjFmD0DAA8LtMUDezz9TCF8= - -cloneable-readable@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/cloneable-readable/-/cloneable-readable-1.0.0.tgz#a6290d413f217a61232f95e458ff38418cfb0117" - integrity sha1-pikNQT8hemEjL5XkWP84QYz7ARc= - dependencies: - inherits "^2.0.1" - process-nextick-args "^1.0.6" - through2 "^2.0.1" - -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= - -combined-stream@^1.0.5, combined-stream@~1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" - integrity sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk= - dependencies: - delayed-stream "~1.0.0" - -commander@2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" - integrity sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q= - dependencies: - graceful-readlink ">= 1.0.0" - -commander@^2.9.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" - integrity sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -convert-source-map@^1.1.1: - version "1.5.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5" - integrity sha1-ms1whRxtXf3ZPZKC5e35SgP/RrU= - -core-util-is@1.0.2, core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= - -crypt@~0.0.1: - version "0.0.2" - resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" - integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= - -cryptiles@2.x.x: - version "2.0.5" - resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" - integrity sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g= - dependencies: - boom "2.x.x" - -cryptiles@3.x.x: - version "3.1.2" - resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe" - integrity sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4= - dependencies: - boom "5.x.x" - -dashdash@^1.12.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" - integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= - dependencies: - assert-plus "^1.0.0" - -dateformat@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.2.0.tgz#4065e2013cf9fb916ddfd82efb506ad4c6769062" - integrity sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI= - -debug@2.6.8: - version "2.6.8" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" - integrity sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw= - dependencies: - ms "2.0.0" - -debug@^2.2.0: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== - dependencies: - ms "2.0.0" - -deep-assign@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/deep-assign/-/deep-assign-1.0.0.tgz#b092743be8427dc621ea0067cdec7e70dd19f37b" - integrity sha1-sJJ0O+hCfcYh6gBnzex+cN0Z83s= - dependencies: - is-obj "^1.0.0" - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= - -diff@3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9" - integrity sha1-yc45Okt8vQsFinJck98pkCeGj/k= - -duplexer2@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.0.2.tgz#c614dcf67e2fb14995a91711e5a617e8a60a31db" - integrity sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds= - dependencies: - readable-stream "~1.1.9" - -duplexer@~0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" - integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E= - -duplexify@^3.2.0: - version "3.5.1" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.1.tgz#4e1516be68838bc90a49994f0b39a6e5960befcd" - integrity sha512-j5goxHTwVED1Fpe5hh3q9R93Kip0Bg2KVAt4f8CEYM3UEwYcPSvWbXaUQOzdX/HtiNomipv+gU7ASQPDbV7pGQ== - dependencies: - end-of-stream "^1.0.0" - inherits "^2.0.1" - readable-stream "^2.0.0" - stream-shift "^1.0.0" - -ecc-jsbn@~0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" - integrity sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU= - dependencies: - jsbn "~0.1.0" - -end-of-stream@^1.0.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.0.tgz#7a90d833efda6cfa6eac0f4949dbb0fad3a63206" - integrity sha1-epDYM+/abPpurA9JSduw+tOmMgY= - dependencies: - once "^1.4.0" - -escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= - -event-stream@^3.3.1, event-stream@~3.3.4: - version "3.3.4" - resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571" - integrity sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE= - dependencies: - duplexer "~0.1.1" - from "~0" - map-stream "~0.1.0" - pause-stream "0.0.11" - split "0.3" - stream-combiner "~0.0.4" - through "~2.3.1" - -expand-brackets@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" - integrity sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s= - dependencies: - is-posix-bracket "^0.1.0" - -expand-range@^1.8.1: - version "1.8.2" - resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" - integrity sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc= - dependencies: - fill-range "^2.1.0" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= - dependencies: - is-extendable "^0.1.0" - -extend@^3.0.0, extend@~3.0.0, extend@~3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" - integrity sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ= - -extglob@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" - integrity sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE= - dependencies: - is-extglob "^1.0.0" - -extsprintf@1.3.0, extsprintf@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" - integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= - -fancy-log@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.0.tgz#45be17d02bb9917d60ccffd4995c999e6c8c9948" - integrity sha1-Rb4X0Cu5kX1gzP/UmVyZnmyMmUg= - dependencies: - chalk "^1.1.1" - time-stamp "^1.0.0" - -fast-deep-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" - integrity sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8= - -fast-json-stable-stringify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" - integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= - -fd-slicer@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65" - integrity sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU= - dependencies: - pend "~1.2.0" - -filename-regex@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" - integrity sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY= - -fill-range@^2.1.0: - version "2.2.3" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" - integrity sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM= - dependencies: - is-number "^2.1.0" - isobject "^2.0.0" - randomatic "^1.1.3" - repeat-element "^1.1.2" - repeat-string "^1.5.2" - -first-chunk-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz#59bfb50cd905f60d7c394cd3d9acaab4e6ad934e" - integrity sha1-Wb+1DNkF9g18OUzT2ayqtOatk04= - -for-in@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= - -for-own@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" - integrity sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4= - dependencies: - for-in "^1.0.1" - -forever-agent@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" - integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= - -form-data@~2.1.1: - version "2.1.4" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" - integrity sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE= - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.5" - mime-types "^2.1.12" - -form-data@~2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf" - integrity sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8= - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.5" - mime-types "^2.1.12" - -from@~0: - version "0.1.7" - resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" - integrity sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4= - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -fstream@^1.0.2: - version "1.0.11" - resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" - integrity sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE= - dependencies: - graceful-fs "^4.1.2" - inherits "~2.0.0" - mkdirp ">=0.5 0" - rimraf "2" - -generate-function@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74" - integrity sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ= - -generate-object-property@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0" - integrity sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA= - dependencies: - is-property "^1.0.0" - -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= - dependencies: - assert-plus "^1.0.0" - -glob-base@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" - integrity sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q= - dependencies: - glob-parent "^2.0.0" - is-glob "^2.0.0" - -glob-parent@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" - integrity sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg= - dependencies: - is-glob "^2.0.0" - -glob-parent@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" - integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= - dependencies: - is-glob "^3.1.0" - path-dirname "^1.0.0" - -glob-stream@^5.3.2: - version "5.3.5" - resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-5.3.5.tgz#a55665a9a8ccdc41915a87c701e32d4e016fad22" - integrity sha1-pVZlqajM3EGRWofHAeMtTgFvrSI= - dependencies: - extend "^3.0.0" - glob "^5.0.3" - glob-parent "^3.0.0" - micromatch "^2.3.7" - ordered-read-streams "^0.3.0" - through2 "^0.6.0" - to-absolute-glob "^0.1.1" - unique-stream "^2.0.2" - -glob@7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" - integrity sha1-gFIR3wT6rxxjo2ADBs31reULLsg= - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.2" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^5.0.3: - version "5.0.15" - resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" - integrity sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E= - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "2 || 3" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^7.0.5, glob@^7.1.1: - version "7.1.2" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" - integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glogg@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.0.tgz#7fe0f199f57ac906cf512feead8f90ee4a284fc5" - integrity sha1-f+DxmfV6yQbPUS/urY+Q7kooT8U= - dependencies: - sparkles "^1.0.0" - -graceful-fs@^4.0.0, graceful-fs@^4.1.2: - version "4.1.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" - integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg= - -"graceful-readlink@>= 1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" - integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU= - -growl@1.9.2: - version "1.9.2" - resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" - integrity sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8= - -gulp-chmod@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/gulp-chmod/-/gulp-chmod-2.0.0.tgz#00c390b928a0799b251accf631aa09e01cc6299c" - integrity sha1-AMOQuSigeZslGsz2MaoJ4BzGKZw= - dependencies: - deep-assign "^1.0.0" - stat-mode "^0.2.0" - through2 "^2.0.0" - -gulp-filter@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/gulp-filter/-/gulp-filter-5.0.1.tgz#5d87f662e317e5839ef7650e620e6c9008ff92d0" - integrity sha512-5olRzAhFdXB2klCu1lnazP65aO9YdA/5WfC9VdInIc8PrUeDIoZfaA3Edb0yUBGhVdHv4eHKL9Fg5tUoEJ9z5A== - dependencies: - gulp-util "^3.0.6" - multimatch "^2.0.0" - streamfilter "^1.0.5" - -gulp-gunzip@0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/gulp-gunzip/-/gulp-gunzip-0.0.3.tgz#7b6e07b0f58fd3d42515c48ead5a63df0572f62f" - integrity sha1-e24HsPWP09QlFcSOrVpj3wVy9i8= - dependencies: - through2 "~0.6.5" - vinyl "~0.4.6" - -gulp-remote-src@^0.4.2: - version "0.4.3" - resolved "https://registry.yarnpkg.com/gulp-remote-src/-/gulp-remote-src-0.4.3.tgz#5728cfd643433dd4845ddef0969f0f971a2ab4a1" - integrity sha1-VyjP1kNDPdSEXd7wlp8PlxoqtKE= - dependencies: - event-stream "~3.3.4" - node.extend "~1.1.2" - request "~2.79.0" - through2 "~2.0.3" - vinyl "~2.0.1" - -gulp-sourcemaps@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/gulp-sourcemaps/-/gulp-sourcemaps-1.6.0.tgz#b86ff349d801ceb56e1d9e7dc7bbcb4b7dee600c" - integrity sha1-uG/zSdgBzrVuHZ59x7vLS33uYAw= - dependencies: - convert-source-map "^1.1.1" - graceful-fs "^4.1.2" - strip-bom "^2.0.0" - through2 "^2.0.0" - vinyl "^1.0.0" - -gulp-symdest@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/gulp-symdest/-/gulp-symdest-1.1.0.tgz#c165320732d192ce56fd94271ffa123234bf2ae0" - integrity sha1-wWUyBzLRks5W/ZQnH/oSMjS/KuA= - dependencies: - event-stream "^3.3.1" - mkdirp "^0.5.1" - queue "^3.1.0" - vinyl-fs "^2.4.3" - -gulp-untar@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/gulp-untar/-/gulp-untar-0.0.6.tgz#d6bdefde7e9a8e054c9f162385a0782c4be74000" - integrity sha1-1r3v3n6ajgVMnxYjhaB4LEvnQAA= - dependencies: - event-stream "~3.3.4" - gulp-util "~3.0.8" - streamifier "~0.1.1" - tar "^2.2.1" - through2 "~2.0.3" - -gulp-util@^3.0.6, gulp-util@~3.0.8: - version "3.0.8" - resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-3.0.8.tgz#0054e1e744502e27c04c187c3ecc505dd54bbb4f" - integrity sha1-AFTh50RQLifATBh8PsxQXdVLu08= - dependencies: - array-differ "^1.0.0" - array-uniq "^1.0.2" - beeper "^1.0.0" - chalk "^1.0.0" - dateformat "^2.0.0" - fancy-log "^1.1.0" - gulplog "^1.0.0" - has-gulplog "^0.1.0" - lodash._reescape "^3.0.0" - lodash._reevaluate "^3.0.0" - lodash._reinterpolate "^3.0.0" - lodash.template "^3.0.0" - minimist "^1.1.0" - multipipe "^0.1.2" - object-assign "^3.0.0" - replace-ext "0.0.1" - through2 "^2.0.0" - vinyl "^0.5.0" - -gulp-vinyl-zip@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/gulp-vinyl-zip/-/gulp-vinyl-zip-1.4.0.tgz#56382f2ccb57231bb0478c78737ccd572973bee1" - integrity sha1-VjgvLMtXIxuwR4x4c3zNVylzvuE= - dependencies: - event-stream "^3.3.1" - queue "^3.0.10" - through2 "^0.6.3" - vinyl "^0.4.6" - vinyl-fs "^2.0.0" - yauzl "^2.2.1" - yazl "^2.2.1" - -gulplog@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/gulplog/-/gulplog-1.0.0.tgz#e28c4d45d05ecbbed818363ce8f9c5926229ffe5" - integrity sha1-4oxNRdBey77YGDY86PnFkmIp/+U= - dependencies: - glogg "^1.0.0" - -har-schema@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" - integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= - -har-validator@~2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d" - integrity sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0= - dependencies: - chalk "^1.1.1" - commander "^2.9.0" - is-my-json-valid "^2.12.4" - pinkie-promise "^2.0.0" - -har-validator@~5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd" - integrity sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0= - dependencies: - ajv "^5.1.0" - har-schema "^2.0.0" - -has-ansi@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" - integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= - dependencies: - ansi-regex "^2.0.0" - -has-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" - integrity sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo= - -has-gulplog@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/has-gulplog/-/has-gulplog-0.1.0.tgz#6414c82913697da51590397dafb12f22967811ce" - integrity sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4= - dependencies: - sparkles "^1.0.0" - -hawk@~3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" - integrity sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ= - dependencies: - boom "2.x.x" - cryptiles "2.x.x" - hoek "2.x.x" - sntp "1.x.x" - -hawk@~6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038" - integrity sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ== - dependencies: - boom "4.x.x" - cryptiles "3.x.x" - hoek "4.x.x" - sntp "2.x.x" - -he@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" - integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0= - -hoek@2.x.x: - version "2.16.3" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" - integrity sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0= - -hoek@4.x.x: - version "4.2.0" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d" - integrity sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ== - -http-signature@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" - integrity sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8= - dependencies: - assert-plus "^0.2.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - -http-signature@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" - integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= - dependencies: - assert-plus "^1.0.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@^2.0.1, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= - -is-buffer@^1.1.5, is-buffer@~1.1.1: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - -is-dotfile@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" - integrity sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE= - -is-equal-shallow@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534" - integrity sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ= - dependencies: - is-primitive "^2.0.0" - -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= - -is-extglob@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" - integrity sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA= - -is-extglob@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= - -is-glob@^2.0.0, is-glob@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" - integrity sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM= - dependencies: - is-extglob "^1.0.0" - -is-glob@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" - integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= - dependencies: - is-extglob "^2.1.0" - -is-my-json-valid@^2.12.4: - version "2.16.1" - resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz#5a846777e2c2620d1e69104e5d3a03b1f6088f11" - integrity sha512-ochPsqWS1WXj8ZnMIV0vnNXooaMhp7cyL4FMSIPKTtnV0Ha/T19G2b9kkhcNsabV9bxYkze7/aLZJb/bYuFduQ== - dependencies: - generate-function "^2.0.0" - generate-object-property "^1.1.0" - jsonpointer "^4.0.0" - xtend "^4.0.0" - -is-number@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" - integrity sha1-Afy7s5NGOlSPL0ZszhbezknbkI8= - dependencies: - kind-of "^3.0.2" - -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= - dependencies: - kind-of "^3.0.2" - -is-obj@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" - integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= - -is-posix-bracket@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" - integrity sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q= - -is-primitive@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" - integrity sha1-IHurkWOEmcB7Kt8kCkGochADRXU= - -is-property@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" - integrity sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ= - -is-stream@^1.0.1, is-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= - -is-typedarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= - -is-utf8@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" - integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= - -is-valid-glob@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-0.3.0.tgz#d4b55c69f51886f9b65c70d6c2622d37e29f48fe" - integrity sha1-1LVcafUYhvm2XHDWwmItN+KfSP4= - -is@^3.1.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/is/-/is-3.2.1.tgz#d0ac2ad55eb7b0bec926a5266f6c662aaa83dca5" - integrity sha1-0Kwq1V63sL7JJqUmb2xmKqqD3KU= - -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= - -isarray@1.0.0, isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= - dependencies: - isarray "1.0.0" - -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= - -jsbn@~0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" - integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= - -json-schema-traverse@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" - integrity sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A= - -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= - -json-stable-stringify@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" - integrity sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8= - dependencies: - jsonify "~0.0.0" - -json-stringify-safe@~5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= - -json3@3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" - integrity sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE= - -jsonc-parser@2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.1.tgz#db73cd59d78cce28723199466b2a03d1be1df2bc" - integrity sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w== - -jsonify@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" - integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= - -jsonpointer@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" - integrity sha1-T9kss04OnbPInIYi7PUfm5eMbLk= - -jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= - dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.2.3" - verror "1.10.0" - -kind-of@^3.0.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= - dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= - dependencies: - is-buffer "^1.1.5" - -lazystream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" - integrity sha1-9plf4PggOS9hOWvolGJAe7dxaOQ= - dependencies: - readable-stream "^2.0.5" - -lodash._baseassign@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz#8c38a099500f215ad09e59f1722fd0c52bfe0a4e" - integrity sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4= - dependencies: - lodash._basecopy "^3.0.0" - lodash.keys "^3.0.0" - -lodash._basecopy@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36" - integrity sha1-jaDmqHbPNEwK2KVIghEd08XHyjY= - -lodash._basecreate@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz#1bc661614daa7fc311b7d03bf16806a0213cf821" - integrity sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE= - -lodash._basetostring@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz#d1861d877f824a52f669832dcaf3ee15566a07d5" - integrity sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U= - -lodash._basevalues@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz#5b775762802bde3d3297503e26300820fdf661b7" - integrity sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc= - -lodash._getnative@^3.0.0: - version "3.9.1" - resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" - integrity sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U= - -lodash._isiterateecall@^3.0.0: - version "3.0.9" - resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c" - integrity sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw= - -lodash._reescape@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reescape/-/lodash._reescape-3.0.0.tgz#2b1d6f5dfe07c8a355753e5f27fac7f1cde1616a" - integrity sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo= - -lodash._reevaluate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz#58bc74c40664953ae0b124d806996daca431e2ed" - integrity sha1-WLx0xAZklTrgsSTYBpltrKQx4u0= - -lodash._reinterpolate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" - integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= - -lodash._root@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692" - integrity sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI= - -lodash.create@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/lodash.create/-/lodash.create-3.1.1.tgz#d7f2849f0dbda7e04682bb8cd72ab022461debe7" - integrity sha1-1/KEnw29p+BGgruM1yqwIkYd6+c= - dependencies: - lodash._baseassign "^3.0.0" - lodash._basecreate "^3.0.0" - lodash._isiterateecall "^3.0.0" - -lodash.escape@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-3.2.0.tgz#995ee0dc18c1b48cc92effae71a10aab5b487698" - integrity sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg= - dependencies: - lodash._root "^3.0.0" - -lodash.isarguments@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" - integrity sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo= - -lodash.isarray@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" - integrity sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U= - -lodash.isequal@^4.0.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" - integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= - -lodash.keys@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" - integrity sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo= - dependencies: - lodash._getnative "^3.0.0" - lodash.isarguments "^3.0.0" - lodash.isarray "^3.0.0" - -lodash.restparam@^3.0.0: - version "3.6.1" - resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" - integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU= - -lodash.template@^3.0.0: - version "3.6.2" - resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-3.6.2.tgz#f8cdecc6169a255be9098ae8b0c53d378931d14f" - integrity sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8= - dependencies: - lodash._basecopy "^3.0.0" - lodash._basetostring "^3.0.0" - lodash._basevalues "^3.0.0" - lodash._isiterateecall "^3.0.0" - lodash._reinterpolate "^3.0.0" - lodash.escape "^3.0.0" - lodash.keys "^3.0.0" - lodash.restparam "^3.0.0" - lodash.templatesettings "^3.0.0" - -lodash.templatesettings@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz#fb307844753b66b9f1afa54e262c745307dba8e5" - integrity sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU= - dependencies: - lodash._reinterpolate "^3.0.0" - lodash.escape "^3.0.0" - -lodash@^4.16.4: - version "4.17.10" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" - integrity sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg== - -map-stream@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194" - integrity sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ= - -md5@^2.1.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9" - integrity sha1-U6s41f48iJG6RlMp6iP6wFQBJvk= - dependencies: - charenc "~0.0.1" - crypt "~0.0.1" - is-buffer "~1.1.1" - -merge-stream@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-1.0.1.tgz#4041202d508a342ba00174008df0c251b8c135e1" - integrity sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE= - dependencies: - readable-stream "^2.0.1" - -micromatch@^2.3.7: - version "2.3.11" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" - integrity sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU= - dependencies: - arr-diff "^2.0.0" - array-unique "^0.2.1" - braces "^1.8.2" - expand-brackets "^0.1.4" - extglob "^0.3.1" - filename-regex "^2.0.0" - is-extglob "^1.0.0" - is-glob "^2.0.1" - kind-of "^3.0.2" - normalize-path "^2.0.1" - object.omit "^2.0.0" - parse-glob "^3.0.4" - regex-cache "^0.4.2" - -mime-db@~1.30.0: - version "1.30.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" - integrity sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE= - -mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.7: - version "2.1.17" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" - integrity sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo= - dependencies: - mime-db "~1.30.0" - -"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -minimist@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= - -minimist@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= - -mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= - dependencies: - minimist "0.0.8" - -mocha-junit-reporter@^1.17.0: - version "1.17.0" - resolved "https://registry.yarnpkg.com/mocha-junit-reporter/-/mocha-junit-reporter-1.17.0.tgz#2e5149ed40fc5d2e3ca71e42db5ab1fec9c6d85c" - integrity sha1-LlFJ7UD8XS48px5C21qx/snG2Fw= - dependencies: - debug "^2.2.0" - md5 "^2.1.0" - mkdirp "~0.5.1" - strip-ansi "^4.0.0" - xml "^1.0.0" - -mocha-multi-reporters@^1.1.7: - version "1.1.7" - resolved "https://registry.yarnpkg.com/mocha-multi-reporters/-/mocha-multi-reporters-1.1.7.tgz#cc7f3f4d32f478520941d852abb64d9988587d82" - integrity sha1-zH8/TTL0eFIJQdhSq7ZNmYhYfYI= - dependencies: - debug "^3.1.0" - lodash "^4.16.4" - -mocha@^3.2.0: - version "3.5.3" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-3.5.3.tgz#1e0480fe36d2da5858d1eb6acc38418b26eaa20d" - integrity sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg== - dependencies: - browser-stdout "1.3.0" - commander "2.9.0" - debug "2.6.8" - diff "3.2.0" - escape-string-regexp "1.0.5" - glob "7.1.1" - growl "1.9.2" - he "1.1.1" - json3 "3.3.2" - lodash.create "3.1.1" - mkdirp "0.5.1" - supports-color "3.1.2" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - -multimatch@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-2.1.0.tgz#9c7906a22fb4c02919e2f5f75161b4cdbd4b2a2b" - integrity sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis= - dependencies: - array-differ "^1.0.0" - array-union "^1.0.1" - arrify "^1.0.0" - minimatch "^3.0.0" - -multipipe@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/multipipe/-/multipipe-0.1.2.tgz#2a8f2ddf70eed564dff2d57f1e1a137d9f05078b" - integrity sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s= - dependencies: - duplexer2 "0.0.2" - -node.extend@~1.1.2: - version "1.1.6" - resolved "https://registry.yarnpkg.com/node.extend/-/node.extend-1.1.6.tgz#a7b882c82d6c93a4863a5504bd5de8ec86258b96" - integrity sha1-p7iCyC1sk6SGOlUEvV3o7IYli5Y= - dependencies: - is "^3.1.0" - -normalize-path@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" - integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= - dependencies: - remove-trailing-separator "^1.0.1" - -oauth-sign@~0.8.1, oauth-sign@~0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" - integrity sha1-Rqarfwrq2N6unsBWV4C31O/rnUM= - -object-assign@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2" - integrity sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I= - -object-assign@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= - -object.omit@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" - integrity sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo= - dependencies: - for-own "^0.1.4" - is-extendable "^0.1.1" - -once@^1.3.0, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -ordered-read-streams@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz#7137e69b3298bb342247a1bbee3881c80e2fd78b" - integrity sha1-cTfmmzKYuzQiR6G77jiByA4v14s= - dependencies: - is-stream "^1.0.1" - readable-stream "^2.0.1" - -parse-glob@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" - integrity sha1-ssN2z7EfNVE7rdFz7wu246OIORw= - dependencies: - glob-base "^0.3.0" - is-dotfile "^1.0.0" - is-extglob "^1.0.0" - is-glob "^2.0.0" - -path-dirname@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" - integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -pause-stream@0.0.11: - version "0.0.11" - resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445" - integrity sha1-/lo0sMvOErWqaitAPuLnO2AvFEU= - dependencies: - through "~2.3" - -pend@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" - integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= - -performance-now@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" - integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= - -pinkie-promise@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" - integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o= - dependencies: - pinkie "^2.0.0" - -pinkie@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" - integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= - -preserve@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" - integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks= - -process-nextick-args@^1.0.6, process-nextick-args@~1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" - integrity sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M= - -punycode@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= - -qs@~6.3.0: - version "6.3.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c" - integrity sha1-51vV9uJoEioqDgvaYwslUMFmUCw= - -qs@~6.5.1: - version "6.5.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" - integrity sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A== - -querystringify@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-1.0.0.tgz#6286242112c5b712fa654e526652bf6a13ff05cb" - integrity sha1-YoYkIRLFtxL6ZU5SZlK/ahP/Bcs= - -queue@^3.0.10, queue@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/queue/-/queue-3.1.0.tgz#6c49d01f009e2256788789f2bffac6b8b9990585" - integrity sha1-bEnQHwCeIlZ4h4nyv/rGuLmZBYU= - dependencies: - inherits "~2.0.0" - -randomatic@^1.1.3: - version "1.1.7" - resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" - integrity sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how== - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - -"readable-stream@>=1.0.33-1 <1.1.0-0": - version "1.0.34" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" - integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw= - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - -readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.1.5: - version "2.3.3" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" - integrity sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - safe-buffer "~5.1.1" - string_decoder "~1.0.3" - util-deprecate "~1.0.1" - -readable-stream@~1.1.9: - version "1.1.14" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" - integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk= - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - -regex-cache@^0.4.2: - version "0.4.4" - resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd" - integrity sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ== - dependencies: - is-equal-shallow "^0.1.3" - -remove-trailing-separator@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" - integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= - -repeat-element@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" - integrity sha1-7wiaF40Ug7quTZPrmLT55OEdmQo= - -repeat-string@^1.5.2: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= - -replace-ext@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924" - integrity sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ= - -replace-ext@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" - integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs= - -request@^2.79.0: - version "2.83.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356" - integrity sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.6.0" - caseless "~0.12.0" - combined-stream "~1.0.5" - extend "~3.0.1" - forever-agent "~0.6.1" - form-data "~2.3.1" - har-validator "~5.0.3" - hawk "~6.0.2" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.17" - oauth-sign "~0.8.2" - performance-now "^2.1.0" - qs "~6.5.1" - safe-buffer "^5.1.1" - stringstream "~0.0.5" - tough-cookie "~2.3.3" - tunnel-agent "^0.6.0" - uuid "^3.1.0" - -request@~2.79.0: - version "2.79.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" - integrity sha1-Tf5b9r6LjNw3/Pk+BLZVd3InEN4= - dependencies: - aws-sign2 "~0.6.0" - aws4 "^1.2.1" - caseless "~0.11.0" - combined-stream "~1.0.5" - extend "~3.0.0" - forever-agent "~0.6.1" - form-data "~2.1.1" - har-validator "~2.0.6" - hawk "~3.1.3" - http-signature "~1.1.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.7" - oauth-sign "~0.8.1" - qs "~6.3.0" - stringstream "~0.0.4" - tough-cookie "~2.3.0" - tunnel-agent "~0.4.1" - uuid "^3.0.0" - -requires-port@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= - -rimraf@2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" - integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w== - dependencies: - glob "^7.0.5" - -safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" - integrity sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg== - -semver@^5.3.0: - version "5.4.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" - integrity sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg== - -sntp@1.x.x: - version "1.0.9" - resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" - integrity sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg= - dependencies: - hoek "2.x.x" - -sntp@2.x.x: - version "2.1.0" - resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8" - integrity sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg== - dependencies: - hoek "4.x.x" - -source-map-support@^0.4.11: - version "0.4.18" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" - integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA== - dependencies: - source-map "^0.5.6" - -source-map@^0.5.6: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= - -sparkles@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.0.tgz#1acbbfb592436d10bbe8f785b7cc6f82815012c3" - integrity sha1-Gsu/tZJDbRC76PeFt8xvgoFQEsM= - -split@0.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/split/-/split-0.3.3.tgz#cd0eea5e63a211dfff7eb0f091c4133e2d0dd28f" - integrity sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8= - dependencies: - through "2" - -sshpk@^1.7.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3" - integrity sha1-US322mKHFEMW3EwY/hzx2UBzm+M= - dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - dashdash "^1.12.0" - getpass "^0.1.1" - optionalDependencies: - bcrypt-pbkdf "^1.0.0" - ecc-jsbn "~0.1.1" - jsbn "~0.1.0" - tweetnacl "~0.14.0" - -stat-mode@^0.2.0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/stat-mode/-/stat-mode-0.2.2.tgz#e6c80b623123d7d80cf132ce538f346289072502" - integrity sha1-5sgLYjEj19gM8TLOU480YokHJQI= - -stream-combiner@~0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.0.4.tgz#4d5e433c185261dde623ca3f44c586bcf5c4ad14" - integrity sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ= - dependencies: - duplexer "~0.1.1" - -stream-shift@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" - integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI= - -streamfilter@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/streamfilter/-/streamfilter-1.0.5.tgz#87507111beb8e298451717b511cfed8f002abf53" - integrity sha1-h1BxEb644phFFxe1Ec/tjwAqv1M= - dependencies: - readable-stream "^2.0.2" - -streamifier@~0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/streamifier/-/streamifier-0.1.1.tgz#97e98d8fa4d105d62a2691d1dc07e820db8dfc4f" - integrity sha1-l+mNj6TRBdYqJpHR3AfoINuN/E8= - -string_decoder@~0.10.x: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= - -string_decoder@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" - integrity sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ== - dependencies: - safe-buffer "~5.1.0" - -stringstream@~0.0.4, stringstream@~0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" - integrity sha1-TkhM1N5aC7vuGORjB3EKioFiGHg= - -strip-ansi@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= - dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" - -strip-bom-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz#e7144398577d51a6bed0fa1994fa05f43fd988ee" - integrity sha1-5xRDmFd9Uaa+0PoZlPoF9D/ZiO4= - dependencies: - first-chunk-stream "^1.0.0" - strip-bom "^2.0.0" - -strip-bom@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" - integrity sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4= - dependencies: - is-utf8 "^0.2.0" - -supports-color@3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5" - integrity sha1-cqJiiU2dQIuVbKBf83su2KbiotU= - dependencies: - has-flag "^1.0.0" - -supports-color@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" - integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= - -tar@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" - integrity sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE= - dependencies: - block-stream "*" - fstream "^1.0.2" - inherits "2" - -through2-filter@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-2.0.0.tgz#60bc55a0dacb76085db1f9dae99ab43f83d622ec" - integrity sha1-YLxVoNrLdghdsfna6Zq0P4PWIuw= - dependencies: - through2 "~2.0.0" - xtend "~4.0.0" - -through2@^0.6.0, through2@^0.6.1, through2@^0.6.3, through2@~0.6.5: - version "0.6.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48" - integrity sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg= - dependencies: - readable-stream ">=1.0.33-1 <1.1.0-0" - xtend ">=4.0.0 <4.1.0-0" - -through2@^2.0.0, through2@^2.0.1, through2@~2.0.0, through2@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" - integrity sha1-AARWmzfHx0ujnEPzzteNGtlBQL4= - dependencies: - readable-stream "^2.1.5" - xtend "~4.0.1" - -through@2, through@~2.3, through@~2.3.1: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= - -time-stamp@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" - integrity sha1-dkpaEa9QVhkhsTPztE5hhofg9cM= - -to-absolute-glob@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-0.1.1.tgz#1cdfa472a9ef50c239ee66999b662ca0eb39937f" - integrity sha1-HN+kcqnvUMI57maZm2YsoOs5k38= - dependencies: - extend-shallow "^2.0.1" - -tough-cookie@~2.3.0, tough-cookie@~2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561" - integrity sha1-C2GKVWW23qkL80JdBNVe3EdadWE= - dependencies: - punycode "^1.4.1" - -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= - dependencies: - safe-buffer "^5.0.1" - -tunnel-agent@~0.4.1: - version "0.4.3" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" - integrity sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us= - -tweetnacl@^0.14.3, tweetnacl@~0.14.0: - version "0.14.5" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= - -unique-stream@^2.0.2: - version "2.2.1" - resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.2.1.tgz#5aa003cfbe94c5ff866c4e7d668bb1c4dbadb369" - integrity sha1-WqADz76Uxf+GbE59ZouxxNuts2k= - dependencies: - json-stable-stringify "^1.0.0" - through2-filter "^2.0.0" - -url-parse@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.2.0.tgz#3a19e8aaa6d023ddd27dcc44cb4fc8f7fec23986" - integrity sha512-DT1XbYAfmQP65M/mE6OALxmXzZ/z1+e5zk2TcSKe/KiYbNGZxgtttzC0mR/sjopbpOXcbniq7eIKmocJnUWlEw== - dependencies: - querystringify "~1.0.0" - requires-port "~1.0.0" - -util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= - -uuid@^3.0.0, uuid@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" - integrity sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g== - -vali-date@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/vali-date/-/vali-date-1.0.0.tgz#1b904a59609fb328ef078138420934f6b86709a6" - integrity sha1-G5BKWWCfsyjvB4E4Qgk09rhnCaY= - -verror@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" - integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - -vinyl-fs@^2.0.0, vinyl-fs@^2.4.3: - version "2.4.4" - resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-2.4.4.tgz#be6ff3270cb55dfd7d3063640de81f25d7532239" - integrity sha1-vm/zJwy1Xf19MGNkDegfJddTIjk= - dependencies: - duplexify "^3.2.0" - glob-stream "^5.3.2" - graceful-fs "^4.0.0" - gulp-sourcemaps "1.6.0" - is-valid-glob "^0.3.0" - lazystream "^1.0.0" - lodash.isequal "^4.0.0" - merge-stream "^1.0.0" - mkdirp "^0.5.0" - object-assign "^4.0.0" - readable-stream "^2.0.4" - strip-bom "^2.0.0" - strip-bom-stream "^1.0.0" - through2 "^2.0.0" - through2-filter "^2.0.0" - vali-date "^1.0.0" - vinyl "^1.0.0" - -vinyl-source-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/vinyl-source-stream/-/vinyl-source-stream-1.1.0.tgz#44cbe5108205279deb0c5653c094a2887938b1ab" - integrity sha1-RMvlEIIFJ53rDFZTwJSiiHk4sas= - dependencies: - through2 "^0.6.1" - vinyl "^0.4.3" - -vinyl@^0.4.3, vinyl@^0.4.6, vinyl@~0.4.6: - version "0.4.6" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.4.6.tgz#2f356c87a550a255461f36bbeb2a5ba8bf784847" - integrity sha1-LzVsh6VQolVGHza76ypbqL94SEc= - dependencies: - clone "^0.2.0" - clone-stats "^0.0.1" - -vinyl@^0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.5.3.tgz#b0455b38fc5e0cf30d4325132e461970c2091cde" - integrity sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4= - dependencies: - clone "^1.0.0" - clone-stats "^0.0.1" - replace-ext "0.0.1" - -vinyl@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-1.2.0.tgz#5c88036cf565e5df05558bfc911f8656df218884" - integrity sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ= - dependencies: - clone "^1.0.0" - clone-stats "^0.0.1" - replace-ext "0.0.1" - -vinyl@~2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.0.2.tgz#0a3713d8d4e9221c58f10ca16c0116c9e25eda7c" - integrity sha1-CjcT2NTpIhxY8QyhbAEWyeJe2nw= - dependencies: - clone "^1.0.0" - clone-buffer "^1.0.0" - clone-stats "^1.0.0" - cloneable-readable "^1.0.0" - is-stream "^1.1.0" - remove-trailing-separator "^1.0.1" - replace-ext "^1.0.0" - -vscode@1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/vscode/-/vscode-1.1.5.tgz#10eb104001840c3dd0813815fd4a05f8fc882d14" - integrity sha1-EOsQQAGEDD3QgTgV/UoF+PyILRQ= - dependencies: - glob "^7.1.1" - gulp-chmod "^2.0.0" - gulp-filter "^5.0.0" - gulp-gunzip "0.0.3" - gulp-remote-src "^0.4.2" - gulp-symdest "^1.1.0" - gulp-untar "^0.0.6" - gulp-vinyl-zip "^1.4.0" - mocha "^3.2.0" - request "^2.79.0" - semver "^5.3.0" - source-map-support "^0.4.11" - url-parse "^1.1.9" - vinyl-source-stream "^1.1.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -xml@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" - integrity sha1-eLpyAgApxbyHuKgaPPzXS0ovweU= - -"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" - integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68= - -yauzl@^2.2.1: - version "2.9.1" - resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.9.1.tgz#a81981ea70a57946133883f029c5821a89359a7f" - integrity sha1-qBmB6nCleUYTOIPwKcWCGok1mn8= - dependencies: - buffer-crc32 "~0.2.3" - fd-slicer "~1.0.1" - -yazl@^2.2.1: - version "2.4.3" - resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.4.3.tgz#ec26e5cc87d5601b9df8432dbdd3cd2e5173a071" - integrity sha1-7CblzIfVYBud+EMtvdPNLlFzoHE= - dependencies: - buffer-crc32 "~0.2.3" diff --git a/package.json b/package.json index 2786217bf1..5915ec4d70 100644 --- a/package.json +++ b/package.json @@ -62,11 +62,10 @@ "keytar": "^5.5.0", "minimist": "^1.2.5", "native-is-elevated": "0.4.1", - "native-keymap": "2.1.1", + "native-keymap": "2.1.2", "native-watchdog": "1.3.0", "ng2-charts": "^1.6.0", "node-pty": "0.10.0-beta8", - "onigasm-umd": "2.2.5", "plotly.js-dist-min": "^1.53.0", "reflect-metadata": "^0.1.8", "rxjs": "5.4.0", @@ -77,15 +76,16 @@ "sudo-prompt": "9.1.1", "v8-inspect-profiler": "^0.0.20", "vscode-nsfw": "1.2.8", + "vscode-oniguruma": "1.3.0", "vscode-proxy-agent": "^0.5.2", "vscode-ripgrep": "^1.5.8", "vscode-sqlite3": "4.0.10", - "vscode-textmate": "4.4.0", - "xterm": "4.6.0-beta.15", + "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.6", + "xterm-addon-webgl": "0.7.0-beta.8", "yauzl": "^2.9.2", "yazl": "^2.4.3", "zone.js": "^0.8.4" @@ -172,7 +172,7 @@ "opn": "^6.0.0", "optimist": "0.3.5", "p-all": "^1.0.0", - "playwright": "0.12.1", + "playwright": "0.15.0", "pump": "^1.0.1", "queue": "3.0.6", "rcedit": "^1.1.0", @@ -183,7 +183,7 @@ "temp-write": "^3.4.0", "ts-loader": "^4.4.2", "typemoq": "^0.3.2", - "typescript": "^3.9.0-dev.20200420", + "typescript": "^3.9.0-dev.20200427", "typescript-formatter": "7.1.0", "underscore": "^1.8.2", "vinyl": "^2.0.0", diff --git a/product.json b/product.json index 890872c65e..3216b3f133 100644 --- a/product.json +++ b/product.json @@ -60,6 +60,7 @@ ] }, "extensionAllowedProposedApi": [ + "ms-vscode.vscode-js-profile-table", "ms-vscode.references-view" ], "extensionsGallery": { diff --git a/remote/package.json b/remote/package.json index c13dcc10af..d680f68509 100644 --- a/remote/package.json +++ b/remote/package.json @@ -27,7 +27,6 @@ "native-watchdog": "1.3.0", "ng2-charts": "^1.6.0", "node-pty": "0.10.0-beta8", - "onigasm-umd": "2.2.5", "reflect-metadata": "^0.1.8", "rxjs": "5.4.0", "sanitize-html": "^1.19.1", @@ -35,14 +34,15 @@ "slickgrid": "github:anthonydresser/SlickGrid#2.3.33", "spdlog": "^0.11.1", "vscode-nsfw": "1.2.8", + "vscode-oniguruma": "1.3.0", "vscode-proxy-agent": "^0.5.2", "vscode-ripgrep": "^1.5.8", - "vscode-textmate": "4.4.0", - "xterm": "4.6.0-beta.15", + "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.6", + "xterm-addon-webgl": "0.7.0-beta.8", "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 37e5402fe8..0ca8abaa74 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -2,32 +2,32 @@ "name": "vscode-web", "version": "0.0.0", "dependencies": { - "@angular/animations": "~4.1.3", - "@angular/common": "~4.1.3", - "@angular/compiler": "~4.1.3", - "@angular/core": "~4.1.3", - "@angular/forms": "~4.1.3", - "@angular/platform-browser": "~4.1.3", - "@angular/platform-browser-dynamic": "~4.1.3", - "@angular/router": "~4.1.3", - "angular2-grid": "2.0.6", - "ansi_up": "^3.0.0", - "chart.js": "^2.6.0", - "html-query-plan": "git://github.com/anthonydresser/html-query-plan.git#2.6", - "jquery": "3.4.0", - "ng2-charts": "^1.6.0", - "onigasm-umd": "2.2.5", - "reflect-metadata": "^0.1.8", - "rxjs": "5.4.0", - "sanitize-html": "^1.19.1", + "@angular/animations": "~4.1.3", + "@angular/common": "~4.1.3", + "@angular/compiler": "~4.1.3", + "@angular/core": "~4.1.3", + "@angular/forms": "~4.1.3", + "@angular/platform-browser": "~4.1.3", + "@angular/platform-browser-dynamic": "~4.1.3", + "@angular/router": "~4.1.3", + "angular2-grid": "2.0.6", + "ansi_up": "^3.0.0", + "chart.js": "^2.6.0", + "html-query-plan": "git://github.com/anthonydresser/html-query-plan.git#2.6", + "jquery": "3.4.0", + "ng2-charts": "^1.6.0", + "reflect-metadata": "^0.1.8", + "rxjs": "5.4.0", + "sanitize-html": "^1.19.1", "semver-umd": "^5.5.6", - "slickgrid": "github:anthonydresser/SlickGrid#2.3.33", - "vscode-textmate": "4.4.0", - "xterm": "4.6.0-beta.15", + "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.6", - "zone.js": "^0.8.4" + "xterm-addon-webgl": "0.7.0-beta.8", + "zone.js": "^0.8.4" } } diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index fe6d1966c9..99c4e117c7 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -222,11 +222,6 @@ moment@^2.10.2: resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== -nan@^2.14.0: - version "2.14.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" - integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== - ng2-charts@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/ng2-charts/-/ng2-charts-1.6.0.tgz#108a2133ff62a8623895240fadbddbea2951f29d" @@ -239,18 +234,6 @@ number-is-nan@^1.0.0: resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= -onigasm-umd@2.2.5: - version "2.2.5" - resolved "https://registry.yarnpkg.com/onigasm-umd/-/onigasm-umd-2.2.5.tgz#f104247334a543accd3f8d641a4d99b3d908d6a1" - integrity sha512-R3qD7hq6i2bBklF+QyjqZl/G4fe7GwtukI28YLH2vuiatqx52tb9vpg2sxwemKc3nF76SgkeyOKJLchBmTm0Aw== - -oniguruma@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/oniguruma/-/oniguruma-7.2.0.tgz#c9a59c1ea7b9fe67e237a02e02139b638856f3af" - integrity sha512-bh+ZLdykY1sdIx8jBp2zpLbVFDBc3XmKH4Ceo2lijNaN1WhEqtnpqFlmtCbRuDB17nJ58RAUStVwfW8e8uEbnA== - dependencies: - nan "^2.14.0" - postcss@^7.0.5: version "7.0.21" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.21.tgz#06bb07824c19c2021c5d056d5b10c35b989f7e17" @@ -355,12 +338,15 @@ util-deprecate@^1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= -vscode-textmate@4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-4.4.0.tgz#14032afeb50152e8f53258c95643e555f2948305" - integrity sha512-dFpm2eK0HwEjeFSD1DDh3j0q47bDSVuZt20RiJWxGqjtm73Wu2jip3C2KaZI3dQx/fSeeXCr/uEN4LNaNj7Ytw== - dependencies: - oniguruma "^7.2.0" +vscode-oniguruma@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.3.0.tgz#6788a9db2f8b0781243b4eb8c7a1dd25f6c0e2c8" + integrity sha512-m4Br19v6XD4MRbVrgsLNSZgQrBzk1BCMCleL8+GrcoGxKEJJd62zOFcTaoQR3hCrSlLqoxWmJ7Cc0VieVV3iTQ== + +vscode-textmate@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-5.1.1.tgz#d88dbf271bee7cede455a21bd4894ba5724a4a7e" + integrity sha512-5VHjF+Fglf9d2JI5OyQ7FHutK6/29G0qYyD920K0SWO7uY8JTWbqyKAHEtfB/ZDk2fOe/E23n3wz9fHXKi63yg== xtend@^4.0.1: version "4.0.2" @@ -382,15 +368,15 @@ xterm-addon-web-links@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-webgl@0.7.0-beta.6: - version "0.7.0-beta.6" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.7.0-beta.6.tgz#340d728bae7456a1d67a7cb8996dfd51d96721b0" - integrity sha512-IoR0hPtG5qBrcLG1B7GSzo4W2hYocP8UgG5LlyXkEkT/0BqVcGnICgR8Ck7EfMmU8ci4jNFiHYjK/Bgc4m2S4g== +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@4.6.0-beta.15: - version "4.6.0-beta.15" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.6.0-beta.15.tgz#93a0cb1ff047a452b03070499e2ccd0ba53e0678" - integrity sha512-5G+3QSM/GKN/Tdq/IIU7FDivzh0eXsv3sNmZFDdtvNggnu2K56l4N5P0KZo0HdFztDfTyW/FLfqsPAwhrK4Khg== +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== zone.js@^0.8.4: version "0.8.29" diff --git a/remote/yarn.lock b/remote/yarn.lock index ef524e3759..33f5869647 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -515,18 +515,6 @@ number-is-nan@^1.0.0: resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= -onigasm-umd@2.2.5: - version "2.2.5" - resolved "https://registry.yarnpkg.com/onigasm-umd/-/onigasm-umd-2.2.5.tgz#f104247334a543accd3f8d641a4d99b3d908d6a1" - integrity sha512-R3qD7hq6i2bBklF+QyjqZl/G4fe7GwtukI28YLH2vuiatqx52tb9vpg2sxwemKc3nF76SgkeyOKJLchBmTm0Aw== - -oniguruma@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/oniguruma/-/oniguruma-7.2.0.tgz#c9a59c1ea7b9fe67e237a02e02139b638856f3af" - integrity sha512-bh+ZLdykY1sdIx8jBp2zpLbVFDBc3XmKH4Ceo2lijNaN1WhEqtnpqFlmtCbRuDB17nJ58RAUStVwfW8e8uEbnA== - dependencies: - nan "^2.14.0" - pend@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" @@ -710,6 +698,11 @@ vscode-nsfw@1.2.8: lodash.isundefined "^3.0.1" nan "^2.10.0" +vscode-oniguruma@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.3.0.tgz#6788a9db2f8b0781243b4eb8c7a1dd25f6c0e2c8" + integrity sha512-m4Br19v6XD4MRbVrgsLNSZgQrBzk1BCMCleL8+GrcoGxKEJJd62zOFcTaoQR3hCrSlLqoxWmJ7Cc0VieVV3iTQ== + vscode-proxy-agent@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.5.2.tgz#0c90d24d353957b841d741da7b2701e3f0a044c4" @@ -725,12 +718,10 @@ vscode-ripgrep@^1.5.8: resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.5.8.tgz#32cb33da6d1a9ca8f5de8c2813ed5114fd55fc11" integrity sha512-l6Pv/t1Jk63RU+kEkMO04XxnNRYdyzuesizj9AzFpcfrUxxpAjEJBK1qO9Mov30UUGZl7uDUBn+uCv9koaHPPA== -vscode-textmate@4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-4.4.0.tgz#14032afeb50152e8f53258c95643e555f2948305" - integrity sha512-dFpm2eK0HwEjeFSD1DDh3j0q47bDSVuZt20RiJWxGqjtm73Wu2jip3C2KaZI3dQx/fSeeXCr/uEN4LNaNj7Ytw== - dependencies: - oniguruma "^7.2.0" +vscode-textmate@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-5.1.1.tgz#d88dbf271bee7cede455a21bd4894ba5724a4a7e" + integrity sha512-5VHjF+Fglf9d2JI5OyQ7FHutK6/29G0qYyD920K0SWO7uY8JTWbqyKAHEtfB/ZDk2fOe/E23n3wz9fHXKi63yg== vscode-windows-ca-certs@0.2.0: version "0.2.0" @@ -764,15 +755,15 @@ xterm-addon-web-links@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-webgl@0.7.0-beta.6: - version "0.7.0-beta.6" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.7.0-beta.6.tgz#340d728bae7456a1d67a7cb8996dfd51d96721b0" - integrity sha512-IoR0hPtG5qBrcLG1B7GSzo4W2hYocP8UgG5LlyXkEkT/0BqVcGnICgR8Ck7EfMmU8ci4jNFiHYjK/Bgc4m2S4g== +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@4.6.0-beta.15: - version "4.6.0-beta.15" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.6.0-beta.15.tgz#93a0cb1ff047a452b03070499e2ccd0ba53e0678" - integrity sha512-5G+3QSM/GKN/Tdq/IIU7FDivzh0eXsv3sNmZFDdtvNggnu2K56l4N5P0KZo0HdFztDfTyW/FLfqsPAwhrK4Khg== +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== yauzl@^2.9.2: version "2.10.0" diff --git a/scripts/code-cli.bat b/scripts/code-cli.bat index 5c9cdd1442..1e7f37712c 100644 --- a/scripts/code-cli.bat +++ b/scripts/code-cli.bat @@ -30,7 +30,6 @@ if not exist out yarn compile set ELECTRON_RUN_AS_NODE=1 set NODE_ENV=development set VSCODE_DEV=1 -set ELECTRON_ENABLE_SECURITY_WARNINGS=1 REM set ELECTRON_DEFAULT_ERROR_MODE=1 TODO@ben to investigate if this helps with builds reporting stacks if renderer crashes set ELECTRON_ENABLE_LOGGING=1 set ELECTRON_ENABLE_STACK_DUMPING=1 diff --git a/scripts/code-cli.sh b/scripts/code-cli.sh index 98d117c199..e4fa552e64 100755 --- a/scripts/code-cli.sh +++ b/scripts/code-cli.sh @@ -39,7 +39,6 @@ function code() { ELECTRON_RUN_AS_NODE=1 \ NODE_ENV=development \ VSCODE_DEV=1 \ - ELECTRON_ENABLE_SECURITY_WARNINGS=1 \ ELECTRON_ENABLE_LOGGING=1 \ ELECTRON_ENABLE_STACK_DUMPING=1 \ "$CODE" --inspect=5874 "$ROOT/out/cli.js" . "$@" diff --git a/scripts/code.bat b/scripts/code.bat index ca1853e3c5..d23724ef14 100644 --- a/scripts/code.bat +++ b/scripts/code.bat @@ -28,7 +28,6 @@ if not exist out yarn compile :: Configuration set NODE_ENV=development set VSCODE_DEV=1 -set ELECTRON_ENABLE_SECURITY_WARNINGS=1 set VSCODE_CLI=1 REM set ELECTRON_DEFAULT_ERROR_MODE=1 TODO@ben to investigate if this helps with builds reporting stacks if renderer crashes set ELECTRON_ENABLE_LOGGING=1 diff --git a/scripts/code.sh b/scripts/code.sh index 634dea0419..3bc09f54f4 100755 --- a/scripts/code.sh +++ b/scripts/code.sh @@ -44,7 +44,6 @@ function code() { # Configuration export NODE_ENV=development export VSCODE_DEV=1 - export ELECTRON_ENABLE_SECURITY_WARNINGS=1 export VSCODE_CLI=1 export ELECTRON_ENABLE_STACK_DUMPING=1 export ELECTRON_ENABLE_LOGGING=1 diff --git a/scripts/test-integration.sh b/scripts/test-integration.sh index dd650393dd..2a1b62a28a 100755 --- a/scripts/test-integration.sh +++ b/scripts/test-integration.sh @@ -25,6 +25,7 @@ else # and the build bundles extensions into .build webpacked yarn gulp compile-extension:vscode-api-tests \ compile-extension:vscode-colorize-tests \ + compile-extension:vscode-notebook-tests \ compile-extension:markdown-language-features \ compile-extension:emmet \ compile-extension:css-language-features-server \ @@ -44,14 +45,15 @@ fi ./scripts/test.sh --runGlob **/*.integrationTest.js "$@" # Tests in the extension host -# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-api-tests/testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/singlefolder-tests --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR -# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-api-tests/testworkspace.code-workspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/workspace-tests --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR -# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-colorize-tests/test --extensionDevelopmentPath=$ROOT/extensions/vscode-colorize-tests --extensionTestsPath=$ROOT/extensions/vscode-colorize-tests/out --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR -# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/markdown-language-features/out/test/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/markdown-language-features --extensionTestsPath=$ROOT/extensions/markdown-language-features/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR -# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/emmet/out/test/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR -# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $(mktemp -d 2>/dev/null) --enable-proposed-api=vscode.git --extensionDevelopmentPath=$ROOT/extensions/git --extensionTestsPath=$ROOT/extensions/git/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR +# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-notebook-tests/test --enable-proposed-api=vscode.vscode-notebook-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-notebook-tests --extensionTestsPath=$ROOT/extensions/vscode-notebook-tests/out/ --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR +# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-api-tests/testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/singlefolder-tests --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR +# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-api-tests/testworkspace.code-workspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/workspace-tests --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR +# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-colorize-tests/test --extensionDevelopmentPath=$ROOT/extensions/vscode-colorize-tests --extensionTestsPath=$ROOT/extensions/vscode-colorize-tests/out --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR +# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/markdown-language-features/out/test/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/markdown-language-features --extensionTestsPath=$ROOT/extensions/markdown-language-features/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR +# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/emmet/out/test/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR +# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $(mktemp -d 2>/dev/null) --enable-proposed-api=vscode.git --extensionDevelopmentPath=$ROOT/extensions/git --extensionTestsPath=$ROOT/extensions/git/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR -"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/azurecore/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/azurecore --extensionTestsPath=$ROOT/extensions/azurecore/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/azurecore/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/azurecore --extensionTestsPath=$ROOT/extensions/azurecore/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=$VSCODEUSERDATADIR # Tests in commonJS cd $ROOT/extensions/css-language-features/server && $ROOT/scripts/node-electron.sh test/index.js diff --git a/src/sql/base/browser/ui/scrollableSplitview/media/scrollableSplitview.css b/src/sql/base/browser/ui/scrollableSplitview/media/scrollableSplitview.css index 9e6b717dae..69156dacd8 100644 --- a/src/sql/base/browser/ui/scrollableSplitview/media/scrollableSplitview.css +++ b/src/sql/base/browser/ui/scrollableSplitview/media/scrollableSplitview.css @@ -10,4 +10,4 @@ .monaco-scroll-split-view { height: 100%; width: 100%; -} \ No newline at end of file +} diff --git a/src/sql/platform/dashboard/browser/insightRegistry.ts b/src/sql/platform/dashboard/browser/insightRegistry.ts index 2ddd77da9b..fbd009217b 100644 --- a/src/sql/platform/dashboard/browser/insightRegistry.ts +++ b/src/sql/platform/dashboard/browser/insightRegistry.ts @@ -67,7 +67,7 @@ export interface IInsightData { export interface IInsightsView { data: IInsightData; - setConfig?: (config: { [key: string]: any }) => void; + setConfig?: (config: any) => void; init?: () => void; } diff --git a/src/sql/workbench/browser/modelComponents/modelViewEditor.contribution.ts b/src/sql/workbench/browser/modelComponents/modelViewEditor.contribution.ts index 6408ee00ab..66bc4ca3c3 100644 --- a/src/sql/workbench/browser/modelComponents/modelViewEditor.contribution.ts +++ b/src/sql/workbench/browser/modelComponents/modelViewEditor.contribution.ts @@ -10,7 +10,7 @@ import { ModelViewInput } from 'sql/workbench/browser/modelComponents/modelViewI import { ModelViewEditor } from 'sql/workbench/browser/modelComponents/modelViewEditor'; // Model View editor registration -const viewModelEditorDescriptor = new EditorDescriptor( +const viewModelEditorDescriptor = EditorDescriptor.create( ModelViewEditor, ModelViewEditor.ID, 'ViewModel' diff --git a/src/sql/workbench/browser/parts/views/customView.ts b/src/sql/workbench/browser/parts/views/treeView.ts similarity index 66% rename from src/sql/workbench/browser/parts/views/customView.ts rename to src/sql/workbench/browser/parts/views/treeView.ts index 9af27b2419..aa73bbdc78 100644 --- a/src/sql/workbench/browser/parts/views/customView.ts +++ b/src/sql/workbench/browser/parts/views/treeView.ts @@ -3,73 +3,69 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./media/views'; import { Event, Emitter } from 'vs/base/common/event'; -import { IDisposable, Disposable, toDisposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IAction, IActionViewItem, ActionRunner, Action } from 'vs/base/common/actions'; +import { IAction, ActionRunner } from 'vs/base/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; -import { ContextAwareMenuEntryActionViewItem, createAndFillInActionBarActions, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeViewDescriptor, IViewsRegistry, ViewContainer, ITreeItemLabel, Extensions, IViewDescriptorService } from 'vs/workbench/common/views'; +import { IMenuService, MenuId, MenuItemAction, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; +import { ContextAwareMenuEntryActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IContextKeyService, ContextKeyExpr, ContextKeyEqualsExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeViewDescriptor, IViewsRegistry, ITreeItemLabel, Extensions, IViewDescriptorService, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IProgressService } from 'vs/platform/progress/common/progress'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import * as DOM from 'vs/base/browser/dom'; import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels'; import { ActionBar, IActionViewItemProvider, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { URI } from 'vs/base/common/uri'; import { dirname, basename } from 'vs/base/common/resources'; -import { LIGHT, FileThemeIcon, FolderThemeIcon, registerThemingParticipant, IThemeService } from 'vs/platform/theme/common/themeService'; +import { LIGHT, FileThemeIcon, FolderThemeIcon, registerThemingParticipant, ThemeIcon, IThemeService } from 'vs/platform/theme/common/themeService'; import { FileKind } from 'vs/platform/files/common/files'; import { WorkbenchAsyncDataTree, TreeResourceNavigator } from 'vs/platform/list/browser/listService'; +import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { localize } from 'vs/nls'; import { timeout } from 'vs/base/common/async'; -import { editorFindMatchHighlight, editorFindMatchHighlightBorder, textLinkForeground, textCodeBlockBackground, focusBorder } from 'vs/platform/theme/common/colorRegistry'; -import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { textLinkForeground, textCodeBlockBackground, focusBorder, listFilterMatchHighlight, listFilterMatchHighlightBorder } from 'vs/platform/theme/common/colorRegistry'; import { isString } from 'vs/base/common/types'; -import { renderMarkdown, MarkdownRenderOptions } from 'vs/base/browser/markdownRenderer'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IMarkdownRenderResult } from 'vs/editor/contrib/markdown/markdownRenderer'; import { ILabelService } from 'vs/platform/label/common/label'; import { Registry } from 'vs/platform/registry/common/platform'; import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list'; import { ITreeRenderer, ITreeNode, IAsyncDataSource, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { CollapseAllAction } from 'vs/base/browser/ui/tree/treeDefaults'; - +import { isFalsyOrWhitespace } from 'vs/base/common/strings'; +import { SIDE_BAR_BACKGROUND, PANEL_BACKGROUND } from 'vs/workbench/common/theme'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { firstIndex } from 'vs/base/common/arrays'; import { ITreeItem, ITreeView } from 'sql/workbench/common/views'; +import { UserCancelledConnectionError } from 'sql/base/common/errors'; import { IOEShimService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerViewTreeShim'; import { NodeContextKey } from 'sql/workbench/browser/parts/views/nodeContext'; -import { UserCancelledConnectionError } from 'sql/base/common/errors'; -import { firstIndex } from 'vs/base/common/arrays'; -import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -export class CustomTreeViewPane extends ViewPane { +export class TreeViewPane extends ViewPane { private treeView: ITreeView; constructor( options: IViewletViewOptions, - @INotificationService private readonly notificationService: INotificationService, @IKeybindingService keybindingService: IKeybindingService, @IContextMenuService contextMenuService: IContextMenuService, @IConfigurationService configurationService: IConfigurationService, @IContextKeyService contextKeyService: IContextKeyService, - @IInstantiationService instantiationService: IInstantiationService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, - @IOpenerService protected openerService: IOpenerService, - @IThemeService protected themeService: IThemeService, + @IInstantiationService instantiationService: IInstantiationService, + @IOpenerService openerService: IOpenerService, + @IThemeService themeService: IThemeService, @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions) }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + super({ ...(options as IViewPaneOptions), titleMenuId: MenuId.ViewTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); const { treeView } = (Registry.as(Extensions.ViewsRegistry).getView(options.id)); this.treeView = treeView as ITreeView; this._register(this.treeView.onDidChangeActions(() => this.updateActions(), this)); @@ -86,23 +82,22 @@ export class CustomTreeViewPane extends ViewPane { } renderBody(container: HTMLElement): void { - if (this.treeView instanceof CustomTreeView) { + super.renderBody(container); + + if (this.treeView instanceof TreeView) { this.treeView.show(container); } } shouldShowWelcome(): boolean { - return (this.treeView.dataProvider === undefined) && (this.treeView.message === undefined); + return ((this.treeView.dataProvider === undefined) || !!this.treeView.dataProvider.isTreeEmpty) && (this.treeView.message === undefined); } layoutBody(height: number, width: number): void { + super.layoutBody(height, width); this.treeView.layout(height, width); } - getActionViewItem(action: IAction): IActionViewItem | undefined { - return action instanceof MenuItemAction ? new ContextAwareMenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService) : undefined; - } - getOptimalWidth(): number { return this.treeView.getOptimalWidth(); } @@ -112,51 +107,6 @@ export class CustomTreeViewPane extends ViewPane { } } -class TitleMenus extends Disposable { - - private titleActions: IAction[] = []; - private readonly titleActionsDisposable = this._register(new MutableDisposable()); - private titleSecondaryActions: IAction[] = []; - - private _onDidChangeTitle = this._register(new Emitter()); - readonly onDidChangeTitle: Event = this._onDidChangeTitle.event; - - constructor( - id: string, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IMenuService private readonly menuService: IMenuService, - ) { - super(); - - const scopedContextKeyService = this._register(this.contextKeyService.createScoped()); - scopedContextKeyService.createKey('view', id); - - const titleMenu = this._register(this.menuService.createMenu(MenuId.ViewTitle, scopedContextKeyService)); - const updateActions = () => { - this.titleActions = []; - this.titleSecondaryActions = []; - this.titleActionsDisposable.value = createAndFillInActionBarActions(titleMenu, undefined, { primary: this.titleActions, secondary: this.titleSecondaryActions }); - this._onDidChangeTitle.fire(); - }; - - this._register(titleMenu.onDidChange(updateActions)); - updateActions(); - - this._register(toDisposable(() => { - this.titleActions = []; - this.titleSecondaryActions = []; - })); - } - - getTitleActions(): IAction[] { - return this.titleActions; - } - - getTitleSecondaryActions(): IAction[] { - return this.titleSecondaryActions; - } -} - class Root implements ITreeItem { label = { label: 'root' }; handle = '0'; @@ -167,29 +117,30 @@ class Root implements ITreeItem { const noDataProviderMessage = localize('no-dataprovider', "There is no data provider registered that can provide view data."); -export class CustomTreeView extends Disposable implements ITreeView { +class Tree extends WorkbenchAsyncDataTree { } + +export class TreeView extends Disposable implements ITreeView { private isVisible: boolean = false; - private activated: boolean = false; private _hasIconForParentNode = false; private _hasIconForLeafNode = false; - private _showCollapseAllAction = false; + + private readonly collapseAllContextKey: RawContextKey; + private readonly collapseAllContext: IContextKey; + private readonly refreshContextKey: RawContextKey; + private readonly refreshContext: IContextKey; private focused: boolean = false; - private domNode: HTMLElement; - private treeContainer: HTMLElement; - private _messageValue: string | IMarkdownString | undefined; + private domNode!: HTMLElement; + private treeContainer!: HTMLElement; + private _messageValue: string | undefined; private _canSelectMany: boolean = false; - private messageElement: HTMLDivElement; - private tree: WorkbenchAsyncDataTree; - private treeLabels: ResourceLabels; + private messageElement!: HTMLDivElement; + private tree: Tree | undefined; + private treeLabels: ResourceLabels | undefined; private root: ITreeItem; private elementsToRefresh: ITreeItem[] = []; - private menus: TitleMenus; - - private markdownRenderer: MarkdownRenderer; - private markdownResult: IMarkdownRenderResult | null; private readonly _onDidExpandItem: Emitter = this._register(new Emitter()); readonly onDidExpandItem: Event = this._onDidExpandItem.event; @@ -212,69 +163,109 @@ export class CustomTreeView extends Disposable implements ITreeView { private readonly _onDidChangeTitle: Emitter = this._register(new Emitter()); readonly onDidChangeTitle: Event = this._onDidChangeTitle.event; + private readonly _onDidCompleteRefresh: Emitter = this._register(new Emitter()); + private nodeContext: NodeContextKey; constructor( - private id: string, + protected readonly id: string, private _title: string, - private viewContainer: ViewContainer, - @IExtensionService private readonly extensionService: IExtensionService, - @IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService, + @IThemeService private readonly themeService: IThemeService, @IInstantiationService private readonly instantiationService: IInstantiationService, @ICommandService private readonly commandService: ICommandService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IProgressService private readonly progressService: IProgressService, + @IProgressService protected readonly progressService: IProgressService, @IContextMenuService private readonly contextMenuService: IContextMenuService, - @IKeybindingService private readonly keybindingService: IKeybindingService + @IKeybindingService private readonly keybindingService: IKeybindingService, + @INotificationService private readonly notificationService: INotificationService, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, + @IContextKeyService contextKeyService: IContextKeyService ) { super(); this.root = new Root(); - this.menus = this._register(instantiationService.createInstance(TitleMenus, this.id)); - this._register(this.menus.onDidChangeTitle(() => this._onDidChangeActions.fire())); + this.collapseAllContextKey = new RawContextKey(`treeView.${this.id}.enableCollapseAll`, false); + this.collapseAllContext = this.collapseAllContextKey.bindTo(contextKeyService); + this.refreshContextKey = new RawContextKey(`treeView.${this.id}.enableRefresh`, false); + this.refreshContext = this.refreshContextKey.bindTo(contextKeyService); + this._register(this.themeService.onDidFileIconThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/)); this._register(this.themeService.onDidColorThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/)); this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('explorer.decorations')) { - this.doRefresh([this.root]).catch(onUnexpectedError); /** soft refresh **/ + this.doRefresh([this.root]); /** soft refresh **/ } })); - this.markdownRenderer = instantiationService.createInstance(MarkdownRenderer); - this._register(toDisposable(() => { - if (this.markdownResult) { - this.markdownResult.dispose(); - } - })); - this._register(Registry.as(Extensions.ViewsRegistry).onDidChangeContainer(({ views, from, to }) => { - if (from === this.viewContainer && views.some(v => v.id === this.id)) { - this.viewContainer = to; + this._register(this.viewDescriptorService.onDidChangeLocation(({ views, from, to }) => { + if (views.some(v => v.id === this.id)) { + this.tree?.updateOptions({ overrideStyles: { listBackground: this.viewLocation === ViewContainerLocation.Sidebar ? SIDE_BAR_BACKGROUND : PANEL_BACKGROUND } }); } })); + this.registerActions(); - this.nodeContext = this._register(instantiationService.createInstance(NodeContextKey)); + this.nodeContext = this._register(instantiationService.createInstance(NodeContextKey)); // tracked change this.create(); } - private _dataProvider: ITreeViewDataProvider | null; - get dataProvider(): ITreeViewDataProvider | null { + collapse(element: ITreeItem): boolean { + if (this.tree) { + return this.tree.collapse(element); + } + return false; + } + + get viewContainer(): ViewContainer { + return this.viewDescriptorService.getViewContainerByViewId(this.id)!; + } + + get viewLocation(): ViewContainerLocation { + return this.viewDescriptorService.getViewLocationById(this.id)!; + } + + private _dataProvider: ITreeViewDataProvider | undefined; + get dataProvider(): ITreeViewDataProvider | undefined { return this._dataProvider; } - set dataProvider(dataProvider: ITreeViewDataProvider | null) { + set dataProvider(dataProvider: ITreeViewDataProvider | undefined) { + if (this.tree === undefined) { + this.createTree(); + } + if (dataProvider) { this._dataProvider = new class implements ITreeViewDataProvider { + private _isEmpty: boolean = true; + private _onDidChangeEmpty: Emitter = new Emitter(); + public onDidChangeEmpty: Event = this._onDidChangeEmpty.event; + + get isTreeEmpty(): boolean { + return this._isEmpty; + } + async getChildren(node: ITreeItem): Promise { + let children: ITreeItem[]; if (node && node.children) { - return Promise.resolve(node.children); + children = node.children; + } else { + children = await (node instanceof Root ? dataProvider.getChildren() : dataProvider.getChildren(node)); + node.children = children; + } + if (node instanceof Root) { + const oldEmpty = this._isEmpty; + this._isEmpty = children.length === 0; + if (oldEmpty !== this._isEmpty) { + this._onDidChangeEmpty.fire(); + } } - const children = await (node instanceof Root ? dataProvider.getChildren() : dataProvider.getChildren(node)); - node.children = children; return children; } }; + if (this._dataProvider.onDidChangeEmpty) { + this._register(this._dataProvider.onDidChangeEmpty(() => this._onDidChangeWelcomeState.fire())); + } this.updateMessage(); - this.refresh().catch(onUnexpectedError); + this.refresh(); } else { - this._dataProvider = null; + this._dataProvider = undefined; this.updateMessage(); } @@ -322,27 +313,61 @@ export class CustomTreeView extends Disposable implements ITreeView { } get showCollapseAllAction(): boolean { - return this._showCollapseAllAction; + return !!this.collapseAllContext.get(); } set showCollapseAllAction(showCollapseAllAction: boolean) { - if (this._showCollapseAllAction !== !!showCollapseAllAction) { - this._showCollapseAllAction = !!showCollapseAllAction; - this._onDidChangeActions.fire(); - } + this.collapseAllContext.set(showCollapseAllAction); } - getPrimaryActions(): IAction[] { - if (this.showCollapseAllAction) { - const collapseAllAction = new Action('vs.tree.collapse', localize('collapseAll', "Collapse All"), 'monaco-tree-action collapse-all', true, () => this.tree ? new CollapseAllAction(this.tree, true).run() : Promise.resolve()); - return [...this.menus.getTitleActions(), collapseAllAction]; - } else { - return this.menus.getTitleActions(); - } + get showRefreshAction(): boolean { + return !!this.refreshContext.get(); } - getSecondaryActions(): IAction[] { - return this.menus.getTitleSecondaryActions(); + set showRefreshAction(showRefreshAction: boolean) { + this.refreshContext.set(showRefreshAction); + } + + private registerActions() { + const that = this; + this._register(registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.actions.treeView.${that.id}.refresh`, + title: localize('refresh', "Refresh"), + menu: { + id: MenuId.ViewTitle, + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', that.id), that.refreshContextKey), + group: 'navigation', + order: Number.MAX_SAFE_INTEGER - 1, + }, + icon: { id: 'codicon/refresh' } + }); + } + async run(): Promise { + return that.refresh(); + } + })); + this._register(registerAction2(class extends Action2 { + constructor() { + super({ + id: `workbench.actions.treeView.${that.id}.collapseAll`, + title: localize('collapseAll', "Collapse All"), + menu: { + id: MenuId.ViewTitle, + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', that.id), that.collapseAllContextKey), + group: 'navigation', + order: Number.MAX_SAFE_INTEGER, + }, + icon: { id: 'codicon/collapse-all' } + }); + } + async run(): Promise { + if (that.tree) { + return new CollapseAllAction(that.tree, true).run(); + } + } + })); } setVisibility(isVisible: boolean): void { @@ -352,9 +377,6 @@ export class CustomTreeView extends Disposable implements ITreeView { } this.isVisible = isVisible; - if (this.isVisible) { - this.activate(); - } if (this.tree) { if (this.isVisible) { @@ -364,7 +386,7 @@ export class CustomTreeView extends Disposable implements ITreeView { } if (this.isVisible && this.elementsToRefresh.length) { - this.doRefresh(this.elementsToRefresh).catch(onUnexpectedError); + this.doRefresh(this.elementsToRefresh); this.elementsToRefresh = []; } } @@ -372,16 +394,18 @@ export class CustomTreeView extends Disposable implements ITreeView { this._onDidChangeVisibility.fire(this.isVisible); } - focus(): void { + focus(reveal: boolean = true): void { if (this.tree && this.root.children && this.root.children.length > 0) { // Make sure the current selected element is revealed const selectedElement = this.tree.getSelection()[0]; - if (selectedElement) { + if (selectedElement && reveal) { this.tree.reveal(selectedElement, 0.5); } // Pass Focus to Viewer this.tree.domFocus(); + } else if (this.tree) { + this.tree.domFocus(); } else { this.domNode.focus(); } @@ -406,18 +430,21 @@ export class CustomTreeView extends Disposable implements ITreeView { const actionViewItemProvider = (action: IAction) => action instanceof MenuItemAction ? this.instantiationService.createInstance(ContextAwareMenuEntryActionViewItem, action) : undefined; const treeMenus = this._register(this.instantiationService.createInstance(TreeMenus, this.id)); this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, this)); - const dataSource = this.instantiationService.createInstance(TreeDataSource, this, this.id, (task: Promise) => this.progressService.withProgress({ location: this.viewContainer.id }, () => task)); + const dataSource = this.instantiationService.createInstance(TreeDataSource, this, this.id, (task: Promise) => this.progressService.withProgress({ location: this.id }, () => task)); const aligner = new Aligner(this.themeService); const renderer = this.instantiationService.createInstance(TreeRenderer, this.id, treeMenus, this.treeLabels, actionViewItemProvider, aligner); + const widgetAriaLabel = this._title; - this.tree = this._register(this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'SqlCustomView', this.treeContainer, new CustomTreeDelegate(), [renderer], + this.tree = this._register(this.instantiationService.createInstance(Tree, this.id, this.treeContainer, new TreeViewDelegate(), [renderer], dataSource, { - identityProvider: new CustomViewIdentityProvider(), + identityProvider: new TreeViewIdentityProvider(), accessibilityProvider: { getAriaLabel(element: ITreeItem): string { - return element.label ? element.label.label : ''; + return element.tooltip ? element.tooltip : element.label ? element.label.label : ''; }, - getWidgetAriaLabel: () => this.title + getWidgetAriaLabel(): string { + return widgetAriaLabel; + } }, keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (item: ITreeItem) => { @@ -429,15 +456,22 @@ export class CustomTreeView extends Disposable implements ITreeView { return e.collapsibleState !== TreeItemCollapsibleState.Expanded; }, multipleSelectionSupport: this.canSelectMany, + overrideStyles: { + listBackground: this.viewLocation === ViewContainerLocation.Sidebar ? SIDE_BAR_BACKGROUND : PANEL_BACKGROUND + } }) as WorkbenchAsyncDataTree); aligner.tree = this.tree; - const actionRunner = new MultipleSelectionActionRunner(() => this.tree!.getSelection()); + const actionRunner = new MultipleSelectionActionRunner(this.notificationService, () => this.tree!.getSelection()); renderer.actionRunner = actionRunner; this.tree.contextKeyService.createKey(this.id, true); this._register(this.tree.onContextMenu(e => this.onContextMenu(treeMenus, e, actionRunner))); this._register(this.tree.onDidChangeSelection(e => this._onDidChangeSelection.fire(e.elements))); this._register(this.tree.onDidChangeCollapseState(e => { + if (!e.node.element) { + return; + } + const element: ITreeItem = Array.isArray(e.node.element.element) ? e.node.element.element[0] : e.node.element.element; if (e.node.collapsed) { this._onDidCollapseItem.fire(element); @@ -446,18 +480,18 @@ export class CustomTreeView extends Disposable implements ITreeView { } })); // Update resource context based on focused element - this._register(this.tree.onDidChangeFocus(e => { + this._register(this.tree.onDidChangeFocus(e => { // tracked change this.nodeContext.set({ node: e.elements[0], viewId: this.id }); })); this.tree.setInput(this.root).then(() => this.updateContentAreas()); - const customTreeNavigator = new TreeResourceNavigator(this.tree, { openOnFocus: false, openOnSelection: false }); - this._register(customTreeNavigator); - this._register(customTreeNavigator.onDidOpenResource(e => { + const treeNavigator = new TreeResourceNavigator(this.tree, { openOnFocus: false, openOnSelection: false }); + this._register(treeNavigator); + this._register(treeNavigator.onDidOpenResource(e => { if (!e.browserEvent) { return; } - const selection = this.tree.getSelection(); + const selection = this.tree!.getSelection(); if ((selection.length === 1) && selection[0].command) { this.commandService.executeCommand(selection[0].command.id, ...(selection[0].command.arguments || [])); } @@ -474,7 +508,7 @@ export class CustomTreeView extends Disposable implements ITreeView { event.preventDefault(); event.stopPropagation(); - this.tree.setFocus([node]); + this.tree!.setFocus([node]); const actions = treeMenus.getResourceContextActions(node); if (!actions.length) { return; @@ -494,17 +528,17 @@ export class CustomTreeView extends Disposable implements ITreeView { onHide: (wasCancelled?: boolean) => { if (wasCancelled) { - this.tree.domFocus(); + this.tree!.domFocus(); } }, - getActionsContext: () => ({ $treeViewId: this.id, $treeItemHandle: node.handle, $treeItem: node, $treeContainerId: this.treeContainer.id }), + getActionsContext: () => ({ $treeViewId: this.id, $treeItemHandle: node.handle }), actionRunner }); } - private updateMessage(): void { + protected updateMessage(): void { if (this._message) { this.showMessage(this._message); } else if (!this.dataProvider) { @@ -515,43 +549,36 @@ export class CustomTreeView extends Disposable implements ITreeView { this.updateContentAreas(); } - private showMessage(message: string | IMarkdownString): void { + private showMessage(message: string): void { DOM.removeClass(this.messageElement, 'hide'); - if (this._messageValue !== message) { - this.resetMessageElement(); - this._messageValue = message; - if (isString(this._messageValue)) { - this.messageElement.textContent = this._messageValue; - } else { - this.markdownResult = this.markdownRenderer.render(this._messageValue); - DOM.append(this.messageElement, this.markdownResult.element); - } - this.layout(this._size); + this.resetMessageElement(); + this._messageValue = message; + if (!isFalsyOrWhitespace(this._message)) { + this.messageElement.textContent = this._messageValue; } + this.layout(this._height, this._width); } private hideMessage(): void { this.resetMessageElement(); DOM.addClass(this.messageElement, 'hide'); - this.layout(this._size); + this.layout(this._height, this._width); } private resetMessageElement(): void { - if (this.markdownResult) { - this.markdownResult.dispose(); - this.markdownResult = null; - } DOM.clearNode(this.messageElement); } - private _size: number; - layout(size: number) { - if (size) { - this._size = size; - const treeSize = size - DOM.getTotalHeight(this.messageElement); - this.treeContainer.style.height = treeSize + 'px'; + private _height: number = 0; + private _width: number = 0; + layout(height: number, width: number) { + if (height && width) { + this._height = height; + this._width = width; + const treeHeight = height - DOM.getTotalHeight(this.messageElement); + this.treeContainer.style.height = treeHeight + 'px'; if (this.tree) { - this.tree.layout(treeSize); + this.tree.layout(treeHeight, width); } } } @@ -565,8 +592,11 @@ export class CustomTreeView extends Disposable implements ITreeView { return 0; } - refresh(elements?: ITreeItem[]): Promise { + async refresh(elements?: ITreeItem[]): Promise { if (this.dataProvider && this.tree) { + if (this.refreshing) { + await Event.toPromise(this._onDidCompleteRefresh.event); + } if (!elements) { elements = [this.root]; // remove all waiting elements to refresh if root is asked to refresh @@ -591,24 +621,17 @@ export class CustomTreeView extends Disposable implements ITreeView { } } } - return Promise.resolve(undefined); - } - - collapse(element: ITreeItem): boolean { - if (this.tree) { - return this.tree.collapse(element); - } - return Promise.arguments(null); + return undefined; } async expand(itemOrItems: ITreeItem | ITreeItem[]): Promise { - if (this.tree) { + const tree = this.tree; + if (tree) { itemOrItems = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems]; await Promise.all(itemOrItems.map(element => { - return this.tree.expand(element, false); + return tree.expand(element, false); })); } - return Promise.resolve(undefined); } setSelection(items: ITreeItem[]): void { @@ -624,34 +647,23 @@ export class CustomTreeView extends Disposable implements ITreeView { } } - reveal(item: ITreeItem): Promise { + async reveal(item: ITreeItem): Promise { if (this.tree) { - return Promise.resolve(this.tree.reveal(item)); - } - return Promise.resolve(); - } - - private activate() { - if (!this.activated) { - this.createTree(); - this.progressService.withProgress({ location: this.viewContainer.id }, () => this.extensionService.activateByEvent(`onView:${this.id}`)) - .then(() => timeout(2000)) - .then(() => { - this.updateMessage(); - }); - this.activated = true; + return this.tree.reveal(item); } } private refreshing: boolean = false; private async doRefresh(elements: ITreeItem[]): Promise { - if (this.tree) { + const tree = this.tree; + if (tree && this.visible) { this.refreshing = true; - await Promise.all(elements.map(element => this.tree.updateChildren(element, true))); + await Promise.all(elements.map(element => tree.updateChildren(element, true, true))); this.refreshing = false; + this._onDidCompleteRefresh.fire(); this.updateContentAreas(); if (this.focused) { - this.focus(); + this.focus(false); } } } @@ -669,13 +681,13 @@ export class CustomTreeView extends Disposable implements ITreeView { } } -class CustomViewIdentityProvider implements IIdentityProvider { +class TreeViewIdentityProvider implements IIdentityProvider { getId(element: ITreeItem): { toString(): string; } { return element.handle; } } -class CustomTreeDelegate implements IListVirtualDelegate { +class TreeViewDelegate implements IListVirtualDelegate { getHeight(element: ITreeItem): number { return TreeRenderer.ITEM_HEIGHT; @@ -704,7 +716,7 @@ class TreeDataSource implements IAsyncDataSource { } async getChildren(node: ITreeItem): Promise { - if (node.childProvider) { + if (node.childProvider) { // tracked change try { return await this.withProgress(this.objectExplorerService.getChildren(node, this.id)); } catch (err) { @@ -728,19 +740,18 @@ class TreeDataSource implements IAsyncDataSource { } } - // todo@joh,sandy make this proper and contributable from extensions registerThemingParticipant((theme, collector) => { - const findMatchHighlightColor = theme.getColor(editorFindMatchHighlight); - if (findMatchHighlightColor) { - collector.addRule(`.file-icon-themable-tree .monaco-list-row .content .monaco-highlighted-label .highlight { color: unset !important; background-color: ${findMatchHighlightColor}; }`); - collector.addRule(`.monaco-tl-contents .monaco-highlighted-label .highlight { color: unset !important; background-color: ${findMatchHighlightColor}; }`); + const matchBackgroundColor = theme.getColor(listFilterMatchHighlight); + if (matchBackgroundColor) { + collector.addRule(`.file-icon-themable-tree .monaco-list-row .content .monaco-highlighted-label .highlight { color: unset !important; background-color: ${matchBackgroundColor}; }`); + collector.addRule(`.monaco-tl-contents .monaco-highlighted-label .highlight { color: unset !important; background-color: ${matchBackgroundColor}; }`); } - const findMatchHighlightColorBorder = theme.getColor(editorFindMatchHighlightBorder); - if (findMatchHighlightColorBorder) { - collector.addRule(`.file-icon-themable-tree .monaco-list-row .content .monaco-highlighted-label .highlight { color: unset !important; border: 1px dotted ${findMatchHighlightColorBorder}; box-sizing: border-box; }`); - collector.addRule(`.monaco-tl-contents .monaco-highlighted-label .highlight { color: unset !important; border: 1px dotted ${findMatchHighlightColorBorder}; box-sizing: border-box; }`); + const matchBorderColor = theme.getColor(listFilterMatchHighlightBorder); + if (matchBorderColor) { + collector.addRule(`.file-icon-themable-tree .monaco-list-row .content .monaco-highlighted-label .highlight { color: unset !important; border: 1px dotted ${matchBorderColor}; box-sizing: border-box; }`); + collector.addRule(`.monaco-tl-contents .monaco-highlighted-label .highlight { color: unset !important; border: 1px dotted ${matchBorderColor}; box-sizing: border-box; }`); } const link = theme.getColor(textLinkForeground); if (link) { @@ -776,7 +787,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer { + if ((Math.abs(start) > label.length) || (Math.abs(end) >= label.length)) { + return ({ start: 0, end: 0 }); + } + if (start < 0) { + start = label.length + start; + } + if (end < 0) { + end = label.length + end; + } + if (start > end) { + const swap = start; + start = end; + end = swap; + } + return ({ start, end }); + }) : undefined; const icon = this.themeService.getColorTheme().type === LIGHT ? node.icon : node.iconDark; const iconUrl = icon ? URI.revive(icon) : null; const title = node.tooltip ? node.tooltip : resource ? undefined : label; @@ -820,18 +848,30 @@ class TreeRenderer extends Disposable implements ITreeRenderer('explorer.decorations'); - templateData.resourceLabel.setResource({ name: label, description, resource: resource ? resource : URI.parse('missing:_icon_resource') }, { fileKind: this.getFileKind(node), title, hideIcon: !!iconUrl, fileDecorations, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches: createMatches(element.filterData) }); + templateData.resourceLabel.setResource({ name: label, description, resource: resource ? resource : URI.parse('missing:_icon_resource') }, { fileKind: this.getFileKind(node), title, hideIcon: !!iconUrl, fileDecorations, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches: matches ? matches : createMatches(element.filterData) }); } else { - templateData.resourceLabel.setResource({ name: label, description }, { title, hideIcon: true, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches: createMatches(element.filterData) }); + templateData.resourceLabel.setResource({ name: label, description }, { title, hideIcon: true, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches: matches ? matches : createMatches(element.filterData) }); + } + + templateData.icon.title = title ? title : ''; + + if (iconUrl || sqlIcon) { + DOM.toggleClass(templateData.icon, sqlIcon, !!sqlIcon); // tracked change + DOM.toggleClass(templateData.icon, 'icon', !!sqlIcon); + templateData.icon.className = 'custom-view-tree-node-item-icon'; + templateData.icon.style.backgroundImage = iconUrl ? DOM.asCSSUrl(iconUrl) : ''; + + } else { + let iconClass: string | undefined; + if (node.themeIcon && !this.isFileKindThemeIcon(node.themeIcon)) { + iconClass = ThemeIcon.asClassName(node.themeIcon); + } + templateData.icon.className = iconClass ? `custom-view-tree-node-item-icon ${iconClass}` : ''; + templateData.icon.style.backgroundImage = ''; } - templateData.icon.className = ''; - templateData.icon.style.backgroundImage = iconUrl ? `url('${DOM.asDomUri(iconUrl).toString(true)}')` : ''; - DOM.toggleClass(templateData.icon, sqlIcon, !!sqlIcon); - DOM.toggleClass(templateData.icon, 'icon', !!sqlIcon); - DOM.toggleClass(templateData.icon, 'custom-view-tree-node-item-icon', !!iconUrl || !!sqlIcon); templateData.actionBar.context = { $treeViewId: this.treeViewId, $treeItemHandle: node.handle }; templateData.actionBar.push(this.menus.getResourceActions(node), { icon: true, label: false }); if (this._actionRunner) { @@ -845,6 +885,14 @@ class TreeRenderer extends Disposable implements ITreeRenderer; + private _tree: WorkbenchAsyncDataTree | undefined; - constructor(private themeService: IWorkbenchThemeService) { + constructor(private themeService: IThemeService) { super(); } @@ -887,11 +935,15 @@ class Aligner extends Disposable { return false; } - const parent: ITreeItem = this._tree.getParentElement(treeItem) || this._tree.getInput(); - if (this.hasIcon(parent)) { + if (this._tree) { + const parent: ITreeItem = this._tree.getParentElement(treeItem) || this._tree.getInput(); + if (this.hasIcon(parent)) { + return false; + } + return !!parent.children && parent.children.every(c => c.collapsibleState === TreeItemCollapsibleState.None || !this.hasIcon(c)); + } else { return false; } - return !!parent.children && parent.children.every(c => c.collapsibleState === TreeItemCollapsibleState.None || !this.hasIcon(c)); } private hasIcon(node: ITreeItem): boolean { @@ -913,22 +965,32 @@ class Aligner extends Disposable { class MultipleSelectionActionRunner extends ActionRunner { - constructor(private getSelectedResources: (() => ITreeItem[])) { + constructor(notificationService: INotificationService, private getSelectedResources: (() => ITreeItem[])) { super(); + this._register(this.onDidRun(e => { + if (e.error) { + notificationService.error(localize('command-error', 'Error running command {1}: {0}. This is likely caused by the extension that contributes {1}.', e.error.message, e.action.id)); + } + })); } - runAction(action: IAction, context: TreeViewItemHandleArg): Promise { + runAction(action: IAction, context: TreeViewItemHandleArg): Promise { const selection = this.getSelectedResources(); let selectionHandleArgs: TreeViewItemHandleArg[] | undefined = undefined; + let actionInSelected: boolean = false; if (selection.length > 1) { - selectionHandleArgs = []; - selection.forEach(selected => { - if (selected.handle !== context.$treeItemHandle) { - selectionHandleArgs!.push({ $treeViewId: context.$treeViewId, $treeItemHandle: selected.handle }); + selectionHandleArgs = selection.map(selected => { + if (selected.handle === context.$treeItemHandle) { + actionInSelected = true; } + return { $treeViewId: context.$treeViewId, $treeItemHandle: selected.handle }; }); } + if (!actionInSelected) { + selectionHandleArgs = undefined; + } + return action.run(...[context, selectionHandleArgs]); } } @@ -945,14 +1007,14 @@ class TreeMenus extends Disposable implements IDisposable { } getResourceActions(element: ITreeItem): IAction[] { - return this.mergeActions([ + return this.mergeActions([ // tracked change this.getActions(MenuId.ViewItemContext, { key: 'viewItem', value: element.contextValue }).primary, this.getActions(MenuId.DataExplorerContext, { key: 'viewItem', value: element.contextValue }).primary ]); } getResourceContextActions(element: ITreeItem): IAction[] { - return this.mergeActions([ + return this.mergeActions([ // tracked change this.getActions(MenuId.ViewItemContext, { key: 'viewItem', value: element.contextValue }).secondary, this.getActions(MenuId.DataExplorerContext, { key: 'viewItem', value: element.contextValue }).secondary ]); @@ -962,7 +1024,7 @@ class TreeMenus extends Disposable implements IDisposable { return actions.reduce((p, c) => p.concat(...c.filter(a => firstIndex(p, x => x.id === a.id) === -1)), [] as IAction[]); } - private getActions(menuId: MenuId, context: { key: string, value: string }): { primary: IAction[]; secondary: IAction[]; } { + private getActions(menuId: MenuId, context: { key: string, value?: string }): { primary: IAction[]; secondary: IAction[]; } { const contextKeyService = this.contextKeyService.createScoped(); contextKeyService.createKey('view', this.id); contextKeyService.createKey(context.key, context.value); @@ -980,38 +1042,43 @@ class TreeMenus extends Disposable implements IDisposable { } } -class MarkdownRenderer { +export class CustomTreeView extends TreeView { + + private activated: boolean = false; constructor( - @IOpenerService private readonly _openerService: IOpenerService + id: string, + title: string, + @IThemeService themeService: IThemeService, + @IInstantiationService instantiationService: IInstantiationService, + @ICommandService commandService: ICommandService, + @IConfigurationService configurationService: IConfigurationService, + @IProgressService progressService: IProgressService, + @IContextMenuService contextMenuService: IContextMenuService, + @IKeybindingService keybindingService: IKeybindingService, + @INotificationService notificationService: INotificationService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IContextKeyService contextKeyService: IContextKeyService, + @IExtensionService private readonly extensionService: IExtensionService, ) { + super(id, title, themeService, instantiationService, commandService, configurationService, progressService, contextMenuService, keybindingService, notificationService, viewDescriptorService, contextKeyService); } - private getOptions(disposeables: DisposableStore): MarkdownRenderOptions { - return { - actionHandler: { - callback: (content) => { - let uri: URI | undefined; - try { - uri = URI.parse(content); - } catch { - // ignore - } - if (uri && this._openerService) { - this._openerService.open(uri).catch(onUnexpectedError); - } - }, - disposeables - } - }; + setVisibility(isVisible: boolean): void { + super.setVisibility(isVisible); + if (this.visible) { + this.activate(); + } } - render(markdown: IMarkdownString): IMarkdownRenderResult { - const disposeables = new DisposableStore(); - const element: HTMLElement = markdown ? renderMarkdown(markdown, this.getOptions(disposeables)) : document.createElement('span'); - return { - element, - dispose: () => disposeables.dispose() - }; + private activate() { + if (!this.activated) { + this.progressService.withProgress({ location: this.id }, () => this.extensionService.activateByEvent(`onView:${this.id}`)) + .then(() => timeout(2000)) + .then(() => { + this.updateMessage(); + }); + this.activated = true; + } } } diff --git a/src/sql/workbench/common/views.ts b/src/sql/workbench/common/views.ts index 5f764b2c64..28f1138cb9 100644 --- a/src/sql/workbench/common/views.ts +++ b/src/sql/workbench/common/views.ts @@ -32,7 +32,7 @@ export interface ITreeItem extends vsITreeItem { export interface ITreeView extends vsITreeView { - collapse(itemOrItems: ITreeItem): boolean; + collapse(element: ITreeItem): boolean } diff --git a/src/sql/workbench/contrib/charts/browser/insight.ts b/src/sql/workbench/contrib/charts/browser/insight.ts index 08d4502ad8..52f21f7b8d 100644 --- a/src/sql/workbench/contrib/charts/browser/insight.ts +++ b/src/sql/workbench/contrib/charts/browser/insight.ts @@ -91,13 +91,13 @@ export class Insight { private findctor(type: ChartType | InsightType): IInsightCtor { if (find(Graph.types, x => x === type as ChartType)) { - return Graph; + return Graph as IInsightCtor; } else if (find(ImageInsight.types, x => x === type as InsightType)) { - return ImageInsight; + return ImageInsight as IInsightCtor; } else if (find(TableInsight.types, x => x === type as InsightType)) { - return TableInsight; + return TableInsight as IInsightCtor; } else if (find(CountInsight.types, x => x === type as InsightType)) { - return CountInsight; + return CountInsight as IInsightCtor; } return undefined; } diff --git a/src/sql/workbench/contrib/charts/browser/interfaces.ts b/src/sql/workbench/contrib/charts/browser/interfaces.ts index 967cc60ca4..1046c51a9f 100644 --- a/src/sql/workbench/contrib/charts/browser/interfaces.ts +++ b/src/sql/workbench/contrib/charts/browser/interfaces.ts @@ -8,6 +8,7 @@ import { mixin } from 'sql/base/common/objects'; import * as types from 'vs/base/common/types'; import { IInsightOptions, InsightType, ChartType } from 'sql/workbench/contrib/charts/common/interfaces'; import { IInsightData } from 'sql/platform/dashboard/browser/insightRegistry'; +import { BrandedService } from 'vs/platform/instantiation/common/instantiation'; export interface IPointDataSet { data: Array<{ x: number | string, y: number }>; @@ -42,6 +43,6 @@ export interface IInsight { } export interface IInsightCtor { - new(container: HTMLElement, options: IInsightOptions, ...services: { _serviceBrand: undefined; }[]): IInsight; + new (container: HTMLElement, options: IInsightOptions, ...services: Services): IInsight; readonly types: Array; } diff --git a/src/sql/workbench/contrib/dashboard/browser/containers/dashboardNavSection.component.ts b/src/sql/workbench/contrib/dashboard/browser/containers/dashboardNavSection.component.ts index 5560b198df..e6a0656960 100644 --- a/src/sql/workbench/contrib/dashboard/browser/containers/dashboardNavSection.component.ts +++ b/src/sql/workbench/contrib/dashboard/browser/containers/dashboardNavSection.component.ts @@ -48,7 +48,7 @@ export class DashboardNavSection extends DashboardTab implements OnDestroy, OnCh dashboardHelper.filterConfigs ]; - private readonly _gridModifiers: Array<(item: Array, originalConfig: Array) => Array> = [ + private readonly _gridModifiers: Array<(item: Array, originalConfig?: Array) => Array> = [ dashboardHelper.validateGridConfig ]; diff --git a/src/sql/workbench/contrib/dashboard/browser/core/dashboardHelper.ts b/src/sql/workbench/contrib/dashboard/browser/core/dashboardHelper.ts index 8923aae0db..3bdb55c72f 100644 --- a/src/sql/workbench/contrib/dashboard/browser/core/dashboardHelper.ts +++ b/src/sql/workbench/contrib/dashboard/browser/core/dashboardHelper.ts @@ -12,7 +12,6 @@ import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/commo import { WidgetConfig } from 'sql/workbench/contrib/dashboard/browser/core/dashboardWidget'; import { Extensions, IInsightRegistry } from 'sql/platform/dashboard/browser/insightRegistry'; import { ConnectionManagementInfo } from 'sql/platform/connection/common/connectionManagementInfo'; -import { DashboardServiceInterface } from 'sql/workbench/contrib/dashboard/browser/services/dashboardServiceInterface.service'; import { WIDGETS_CONTAINER } from 'sql/workbench/contrib/dashboard/browser/containers/dashboardWidgetContainer.contribution'; import { GRID_CONTAINER } from 'sql/workbench/contrib/dashboard/browser/containers/dashboardGridContainer.contribution'; import { WEBVIEW_CONTAINER } from 'sql/workbench/contrib/dashboard/browser/containers/dashboardWebviewContainer.contribution'; @@ -111,7 +110,7 @@ export function addProvider(config: WidgetConfig[], collection: DashboardServiceInterface): Array { +export function addEdition(config: WidgetConfig[], collection: T): Array { const connectionInfo: ConnectionManagementInfo = collection.connectionManagementService.connectionInfo; if (connectionInfo.serverInfo) { const edition = connectionInfo.serverInfo.engineEditionId; diff --git a/src/sql/workbench/contrib/dashboard/browser/core/dashboardPage.component.ts b/src/sql/workbench/contrib/dashboard/browser/core/dashboardPage.component.ts index 4c38484d29..6b2650672f 100644 --- a/src/sql/workbench/contrib/dashboard/browser/core/dashboardPage.component.ts +++ b/src/sql/workbench/contrib/dashboard/browser/core/dashboardPage.component.ts @@ -107,7 +107,7 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig return this.dashboardService.scopedContextKeyService; } - private readonly _gridModifiers: Array<(item: Array, originalConfig: Array) => Array> = [ + private readonly _gridModifiers: Array<(item: Array, originalConfig?: Array) => Array> = [ dashboardHelper.validateGridConfig ]; diff --git a/src/sql/workbench/contrib/dashboard/browser/dashboard.contribution.ts b/src/sql/workbench/contrib/dashboard/browser/dashboard.contribution.ts index b86d3936b5..55780c6191 100644 --- a/src/sql/workbench/contrib/dashboard/browser/dashboard.contribution.ts +++ b/src/sql/workbench/contrib/dashboard/browser/dashboard.contribution.ts @@ -57,7 +57,7 @@ MenuRegistry.appendMenuItem(MenuId.ObjectExplorerItemContext, { when: ContextKeyExpr.or(ContextKeyExpr.and(TreeNodeContextKey.Status.notEqualsTo('Unavailable'), TreeNodeContextKey.NodeType.isEqualTo('Server')), ContextKeyExpr.and(TreeNodeContextKey.Status.notEqualsTo('Unavailable'), TreeNodeContextKey.NodeType.isEqualTo('Database'))) }); -const dashboardEditorDescriptor = new EditorDescriptor( +const dashboardEditorDescriptor = EditorDescriptor.create( DashboardEditor, DashboardEditor.ID, localize('dashboard.editor.label', "Dashboard") diff --git a/src/sql/workbench/contrib/dataExplorer/browser/dataExplorerExtensionPoint.ts b/src/sql/workbench/contrib/dataExplorer/browser/dataExplorerExtensionPoint.ts index 48742f0313..e1857bc70f 100644 --- a/src/sql/workbench/contrib/dataExplorer/browser/dataExplorerExtensionPoint.ts +++ b/src/sql/workbench/contrib/dataExplorer/browser/dataExplorerExtensionPoint.ts @@ -7,16 +7,17 @@ import { localize } from 'vs/nls'; import { forEach } from 'vs/base/common/collections'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IViewContainersRegistry, ViewContainer, Extensions as ViewContainerExtensions, ITreeViewDescriptor, IViewsRegistry } from 'vs/workbench/common/views'; +import { IViewContainersRegistry, ViewContainer, Extensions as ViewContainerExtensions, IViewsRegistry } from 'vs/workbench/common/views'; import { IExtensionPoint, ExtensionsRegistry, ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { coalesce } from 'vs/base/common/arrays'; -import { CustomTreeViewPane, CustomTreeView } from 'sql/workbench/browser/parts/views/customView'; +import { CustomTreeView, TreeViewPane } from 'sql/workbench/browser/parts/views/treeView'; import { VIEWLET_ID } from 'sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { ICustomViewDescriptor } from 'vs/workbench/api/browser/viewsExtensionPoint'; interface IUserFriendlyViewDescriptor { id: string; @@ -103,14 +104,15 @@ export class DataExplorerContainerExtensionHandler implements IWorkbenchContribu return null; } - const viewDescriptor = { + const viewDescriptor = { id: item.id, name: item.name, - ctorDescriptor: new SyncDescriptor(CustomTreeViewPane), + ctorDescriptor: new SyncDescriptor(TreeViewPane), when: ContextKeyExpr.deserialize(item.when), canToggleVisibility: true, + canMoveView: true, + treeView: this.instantiationService.createInstance(CustomTreeView, item.id, item.name), collapsed: this.showCollapsed(container), - treeView: this.instantiationService.createInstance(CustomTreeView, item.id, item.name, container), extensionId: extension.description.identifier, originalContainerId: entry.key }; diff --git a/src/sql/workbench/contrib/editData/browser/editData.contribution.ts b/src/sql/workbench/contrib/editData/browser/editData.contribution.ts index 8be3021052..ed21405edb 100644 --- a/src/sql/workbench/contrib/editData/browser/editData.contribution.ts +++ b/src/sql/workbench/contrib/editData/browser/editData.contribution.ts @@ -12,7 +12,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; // Editor -const editDataEditorDescriptor = new EditorDescriptor( +const editDataEditorDescriptor = EditorDescriptor.create( EditDataEditor, EditDataEditor.ID, 'EditData' @@ -22,7 +22,7 @@ Registry.as(Extensions.Editors) .registerEditor(editDataEditorDescriptor, [new SyncDescriptor(EditDataInput)]); // Editor -const editDataResultsEditorDescriptor = new EditorDescriptor( +const editDataResultsEditorDescriptor = EditorDescriptor.create( EditDataResultsEditor, EditDataResultsEditor.ID, 'EditDataResults' diff --git a/src/sql/workbench/contrib/editData/browser/editDataEditor.ts b/src/sql/workbench/contrib/editData/browser/editDataEditor.ts index 1be8eca27b..2f5659e6bd 100644 --- a/src/sql/workbench/contrib/editData/browser/editDataEditor.ts +++ b/src/sql/workbench/contrib/editData/browser/editDataEditor.ts @@ -21,7 +21,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import * as queryContext from 'sql/workbench/contrib/query/common/queryContext'; import { Taskbar, ITaskbarContent } from 'sql/base/browser/ui/taskbar/taskbar'; import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; -import { Action } from 'vs/base/common/actions'; +import { IAction } from 'vs/base/common/actions'; import { IQueryModelService } from 'sql/workbench/services/query/common/queryModel'; import { IEditorDescriptorService } from 'sql/workbench/services/queryEditor/browser/editorDescriptorService'; import { @@ -321,7 +321,7 @@ export class EditDataEditor extends BaseEditor { // Create QueryTaskbar this._taskbarContainer = DOM.append(parentElement, DOM.$('div')); this._taskbar = new Taskbar(this._taskbarContainer, { - actionViewItemProvider: (action: Action) => this._getChangeMaxRowsAction(action) + actionViewItemProvider: (action: IAction) => this._getChangeMaxRowsAction(action) }); // Create Actions for the toolbar @@ -352,7 +352,7 @@ export class EditDataEditor extends BaseEditor { /** * Gets the IActionItem for the list of row number drop down */ - private _getChangeMaxRowsAction(action: Action): IActionViewItem { + private _getChangeMaxRowsAction(action: IAction): IActionViewItem { let actionID = ChangeMaxRowsAction.ID; if (action.id === actionID) { if (!this._changeMaxRowsActionItem) { diff --git a/src/sql/workbench/contrib/extensions/browser/extensionsActions.ts b/src/sql/workbench/contrib/extensions/browser/extensionsActions.ts index 5c061523c7..2667ece3de 100644 --- a/src/sql/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/sql/workbench/contrib/extensions/browser/extensionsActions.ts @@ -25,8 +25,8 @@ export class ShowRecommendedExtensionsByScenarioAction extends Action { run(): Promise { return this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet?.getViewPaneContainer()) - .then((viewlet: IExtensionsViewPaneContainer) => { + .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) + .then(viewlet => { viewlet.search('@' + this.scenarioType); viewlet.focus(); }); @@ -51,8 +51,8 @@ export class InstallRecommendedExtensionsByScenarioAction extends Action { run(): Promise { if (!this.recommendations.length) { return Promise.resolve(); } return this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet?.getViewPaneContainer()) - .then((viewlet: IExtensionsViewPaneContainer) => { + .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) + .then(viewlet => { viewlet.search('@' + this.scenarioType); viewlet.focus(); const names = this.recommendations.map(({ extensionId }) => extensionId); diff --git a/src/sql/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/sql/workbench/contrib/notebook/browser/notebook.contribution.ts index 42139adaba..71d90e3b37 100644 --- a/src/sql/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/sql/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -57,7 +57,7 @@ Registry.as(LanguageAssociationExtensions.Language .registerLanguageAssociation(NotebookEditorInputAssociation.languages, NotebookEditorInputAssociation); Registry.as(EditorExtensions.Editors) - .registerEditor(new EditorDescriptor(NotebookEditor, NotebookEditor.ID, localize('notebookEditor.name', "Notebook Editor")), [new SyncDescriptor(UntitledNotebookInput), new SyncDescriptor(FileNotebookInput)]); + .registerEditor(EditorDescriptor.create(NotebookEditor, NotebookEditor.ID, localize('notebookEditor.name', "Notebook Editor")), [new SyncDescriptor(UntitledNotebookInput), new SyncDescriptor(FileNotebookInput)]); Registry.as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(NotebookThemingContribution, LifecyclePhase.Restored); diff --git a/src/sql/workbench/contrib/profiler/browser/profiler.contribution.ts b/src/sql/workbench/contrib/profiler/browser/profiler.contribution.ts index 772f4c6232..48bc870424 100644 --- a/src/sql/workbench/contrib/profiler/browser/profiler.contribution.ts +++ b/src/sql/workbench/contrib/profiler/browser/profiler.contribution.ts @@ -14,7 +14,7 @@ import { ProfilerInput } from 'sql/workbench/browser/editor/profiler/profilerInp import { ProfilerEditor } from 'sql/workbench/contrib/profiler/browser/profilerEditor'; import { PROFILER_VIEW_TEMPLATE_SETTINGS, PROFILER_SESSION_TEMPLATE_SETTINGS, IProfilerViewTemplate, IProfilerSessionTemplate, EngineType, PROFILER_FILTER_SETTINGS } from 'sql/workbench/services/profiler/browser/interfaces'; -const profilerDescriptor = new EditorDescriptor( +const profilerDescriptor = EditorDescriptor.create( ProfilerEditor, ProfilerEditor.ID, 'ProfilerEditor' diff --git a/src/sql/workbench/contrib/profiler/browser/profilerEditor.ts b/src/sql/workbench/contrib/profiler/browser/profilerEditor.ts index b5b29cb09f..776c511978 100644 --- a/src/sql/workbench/contrib/profiler/browser/profilerEditor.ts +++ b/src/sql/workbench/contrib/profiler/browser/profilerEditor.ts @@ -395,7 +395,7 @@ export class ProfilerEditor extends BaseEditor { this._detailTable.updateRowCount(); }); - const detailTableCopyKeybind = new CopyKeybind(); + const detailTableCopyKeybind = new CopyKeybind(); detailTableCopyKeybind.onCopy((ranges: Slick.Range[]) => { // we always only get 1 item in the ranges if (ranges && ranges.length === 1) { diff --git a/src/sql/workbench/contrib/query/browser/gridPanel.ts b/src/sql/workbench/contrib/query/browser/gridPanel.ts index 5b3772b0c2..3fb394b3ad 100644 --- a/src/sql/workbench/contrib/query/browser/gridPanel.ts +++ b/src/sql/workbench/contrib/query/browser/gridPanel.ts @@ -323,7 +323,7 @@ export abstract class GridTableBase extends Disposable implements IView { private table: Table; private actionBar: ActionBar; private container = document.createElement('div'); - private selectionModel = new CellSelectionModel(); + private selectionModel = new CellSelectionModel(); private styles: ITableStyles; private currentHeight: number; private dataProvider: AsyncDataProvider; @@ -461,7 +461,7 @@ export abstract class GridTableBase extends Disposable implements IView { this.renderGridDataRowsRange(startIndex, count); }); this.rowNumberColumn = new RowNumberColumn({ numberOfRows: this.resultSet.rowCount }); - let copyHandler = new CopyKeybind(); + let copyHandler = new CopyKeybind(); copyHandler.onCopy(e => { new CopyResultAction(CopyResultAction.COPY_ID, CopyResultAction.COPY_LABEL, false).run(this.generateContext()); }); diff --git a/src/sql/workbench/contrib/query/browser/query.contribution.ts b/src/sql/workbench/contrib/query/browser/query.contribution.ts index 0ef2cbb1e3..06a2b5aa97 100644 --- a/src/sql/workbench/contrib/query/browser/query.contribution.ts +++ b/src/sql/workbench/contrib/query/browser/query.contribution.ts @@ -57,10 +57,10 @@ Registry.as(LanguageAssociationExtensions.Language .registerLanguageAssociation(QueryEditorLanguageAssociation.languages, QueryEditorLanguageAssociation, QueryEditorLanguageAssociation.isDefault); Registry.as(EditorExtensions.Editors) - .registerEditor(new EditorDescriptor(QueryResultsEditor, QueryResultsEditor.ID, localize('queryResultsEditor.name', "Query Results")), [new SyncDescriptor(QueryResultsInput)]); + .registerEditor(EditorDescriptor.create(QueryResultsEditor, QueryResultsEditor.ID, localize('queryResultsEditor.name', "Query Results")), [new SyncDescriptor(QueryResultsInput)]); Registry.as(EditorExtensions.Editors) - .registerEditor(new EditorDescriptor(QueryEditor, QueryEditor.ID, localize('queryEditor.name', "Query Editor")), [new SyncDescriptor(FileQueryEditorInput), new SyncDescriptor(UntitledQueryEditorInput)]); + .registerEditor(EditorDescriptor.create(QueryEditor, QueryEditor.ID, localize('queryEditor.name', "Query Editor")), [new SyncDescriptor(FileQueryEditorInput), new SyncDescriptor(UntitledQueryEditorInput)]); const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); diff --git a/src/sql/workbench/contrib/query/browser/queryEditor.ts b/src/sql/workbench/contrib/query/browser/queryEditor.ts index 08fdcafd63..79ffca210e 100644 --- a/src/sql/workbench/contrib/query/browser/queryEditor.ts +++ b/src/sql/workbench/contrib/query/browser/queryEditor.ts @@ -24,7 +24,7 @@ import { SplitView, Sizing } from 'vs/base/browser/ui/splitview/splitview'; import { Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ISelectionData } from 'azdata'; -import { Action, IActionViewItem } from 'vs/base/common/actions'; +import { IActionViewItem, IAction } from 'vs/base/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; @@ -171,7 +171,7 @@ export class QueryEditor extends BaseEditor { // Create QueryTaskbar let taskbarContainer = DOM.append(parentElement, DOM.$('div')); this.taskbar = this._register(new Taskbar(taskbarContainer, { - actionViewItemProvider: (action: Action) => this._getActionItemForAction(action), + actionViewItemProvider: action => this._getActionItemForAction(action), })); // Create Actions for the toolbar @@ -236,7 +236,7 @@ export class QueryEditor extends BaseEditor { * Gets the IActionItem for the List Databases dropdown if provided the associated Action. * Otherwise returns null. */ - private _getActionItemForAction(action: Action): IActionViewItem { + private _getActionItemForAction(action: IAction): IActionViewItem { if (action.id === actions.ListDatabasesAction.ID) { return this.listDatabasesActionItem; } diff --git a/src/sql/workbench/contrib/queryPlan/browser/queryPlan.contribution.ts b/src/sql/workbench/contrib/queryPlan/browser/queryPlan.contribution.ts index e66fa76a1d..94f966334f 100644 --- a/src/sql/workbench/contrib/queryPlan/browser/queryPlan.contribution.ts +++ b/src/sql/workbench/contrib/queryPlan/browser/queryPlan.contribution.ts @@ -12,7 +12,7 @@ import { ILanguageAssociationRegistry, Extensions as LanguageAssociationExtensio // Query Plan editor registration -const queryPlanEditorDescriptor = new EditorDescriptor( +const queryPlanEditorDescriptor = EditorDescriptor.create( QueryPlanEditor, QueryPlanEditor.ID, 'QueryPlan' diff --git a/src/sql/workbench/contrib/tasks/browser/tasks.contribution.ts b/src/sql/workbench/contrib/tasks/browser/tasks.contribution.ts index 66c005e895..4450d54a52 100644 --- a/src/sql/workbench/contrib/tasks/browser/tasks.contribution.ts +++ b/src/sql/workbench/contrib/tasks/browser/tasks.contribution.ts @@ -48,7 +48,7 @@ export class StatusUpdater extends lifecycle.Disposable implements ext.IWorkbenc lifecycle.dispose(this.badgeHandle); let numOfInProgressTask: number = this.taskService.getNumberOfInProgressTasks(); let badge: NumberBadge = new NumberBadge(numOfInProgressTask, n => localize('inProgressTasksChangesBadge', "{0} in progress tasks", n)); - this.badgeHandle = this.activityBarService.showActivity(TASKS_CONTAINER_ID, badge, 'taskhistory-viewlet-label'); + this.badgeHandle = this.activityBarService.showViewContainerActivity(TASKS_CONTAINER_ID, { badge, clazz: 'taskhistory-viewlet-label' }); } public getId(): string { diff --git a/src/sql/workbench/contrib/welcome/page/browser/welcomePage.ts b/src/sql/workbench/contrib/welcome/page/browser/welcomePage.ts index 3c3ada731a..b44e3a3e16 100644 --- a/src/sql/workbench/contrib/welcome/page/browser/welcomePage.ts +++ b/src/sql/workbench/contrib/welcome/page/browser/welcomePage.ts @@ -46,6 +46,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { KeyCode } from 'vs/base/common/keyCodes'; import { joinPath } from 'vs/base/common/resources'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { addStandardDisposableListener, EventHelper } from 'vs/base/browser/dom'; const configurationKey = 'workbench.startupEditor'; const oldConfigurationKey = 'workbench.welcome.enabled'; @@ -321,23 +322,21 @@ class WelcomePage extends Disposable { } private createWidePreviewToolTip() { - const previewLink = document.querySelector('#tool_tip_container_wide'); - const tooltip = document.querySelector('#tooltip_text_wide'); + const previewLink = document.querySelector('#tool_tip_container_wide') as HTMLElement; + const tooltip = document.querySelector('#tooltip_text_wide') as HTMLElement; const previewModalBody = document.querySelector('.preview_tooltip_body') as HTMLElement; const previewModalHeader = document.querySelector('.preview_tooltip_header') as HTMLElement; - previewLink.addEventListener('mouseover', () => { + addStandardDisposableListener(previewLink, 'mouseover', () => { tooltip.setAttribute('aria-hidden', 'true'); tooltip.classList.toggle('show'); }); - previewLink.addEventListener('mouseout', () => { + addStandardDisposableListener(previewLink, 'mouseout', () => { tooltip.setAttribute('aria-hidden', 'false'); tooltip.classList.remove('show'); }); - previewLink.addEventListener('keydown', (e: KeyboardEvent) => { - let event = new StandardKeyboardEvent(e); - + addStandardDisposableListener(previewLink, 'keydown', event => { if (event.equals(KeyCode.Escape)) { if (tooltip.classList.contains('show')) { tooltip.setAttribute('aria-hidden', 'true'); @@ -351,9 +350,7 @@ class WelcomePage extends Disposable { } }); - tooltip.addEventListener('keydown', (e: KeyboardEvent) => { - let event = new StandardKeyboardEvent(e); - + addStandardDisposableListener(tooltip, 'keydown', event => { if (event.equals(KeyCode.Escape)) { if (tooltip.classList.contains('show')) { tooltip.setAttribute('aria-hidden', 'true'); @@ -361,8 +358,8 @@ class WelcomePage extends Disposable { } } else if (event.equals(KeyCode.Tab)) { - e.preventDefault(); - if (e.target === previewModalBody) { + EventHelper.stop(event); + if (event.target === previewModalBody) { previewModalHeader.focus(); } else { previewModalBody.focus(); @@ -381,15 +378,14 @@ class WelcomePage extends Disposable { } private createDropDown() { - const dropdownBtn = document.querySelector('#dropdown_btn'); + const dropdownBtn = document.querySelector('#dropdown_btn') as HTMLElement; const dropdown = document.querySelector('#dropdown') as HTMLInputElement; - dropdownBtn.addEventListener('click', () => { + addStandardDisposableListener(dropdownBtn, 'click', () => { dropdown.classList.toggle('show'); }); - dropdownBtn.addEventListener('keydown', (e: KeyboardEvent) => { - let event = new StandardKeyboardEvent(e); + addStandardDisposableListener(dropdownBtn, 'keydown', event => { if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) { const dropdownFirstElement = document.querySelector('#dropdown').firstElementChild.children[0] as HTMLInputElement; dropdown.classList.toggle('show'); @@ -397,8 +393,7 @@ class WelcomePage extends Disposable { } }); - dropdown.addEventListener('keydown', (e: KeyboardEvent) => { - let event = new StandardKeyboardEvent(e); + addStandardDisposableListener(dropdown, 'keydown', event => { if (event.equals(KeyCode.Escape)) { if (dropdown.classList.contains('show')) { dropdown.classList.remove('show'); @@ -427,16 +422,15 @@ class WelcomePage extends Disposable { } }); - dropdown.addEventListener('keydown', function (e: KeyboardEvent) { + addStandardDisposableListener(dropdown, 'keydown', event => { const dropdownLastElement = document.querySelector('#dropdown').lastElementChild.children[0] as HTMLInputElement; const dropdownFirstElement = document.querySelector('#dropdown').firstElementChild.children[0] as HTMLInputElement; - let event = new StandardKeyboardEvent(e); if (event.equals(KeyCode.Tab)) { - e.preventDefault(); + EventHelper.stop(event); return; } else if (event.equals(KeyCode.UpArrow) || event.equals(KeyCode.LeftArrow)) { - if (e.target === dropdownFirstElement) { + if (event.target === dropdownFirstElement) { dropdownLastElement.focus(); } else { const movePrev = document.querySelector('.move:focus').parentElement.previousElementSibling.children[0] as HTMLElement; @@ -444,7 +438,7 @@ class WelcomePage extends Disposable { } } else if (event.equals(KeyCode.DownArrow) || event.equals(KeyCode.RightArrow)) { - if (e.target === dropdownLastElement) { + if (event.target === dropdownLastElement) { dropdownFirstElement.focus(); } else { const moveNext = document.querySelector('.move:focus').parentElement.nextElementSibling.children[0] as HTMLElement; diff --git a/src/sql/workbench/services/insights/browser/insightsDialogView.ts b/src/sql/workbench/services/insights/browser/insightsDialogView.ts index 5afbf4253e..163e912060 100644 --- a/src/sql/workbench/services/insights/browser/insightsDialogView.ts +++ b/src/sql/workbench/services/insights/browser/insightsDialogView.ts @@ -52,16 +52,16 @@ const labelDisplay = nls.localize("insights.item", "Item"); const valueDisplay = nls.localize("insights.value", "Value"); const iconClass = 'codicon'; -class InsightTableView extends ViewPane { - private _table: Table; - public get table(): Table { +class InsightTableView extends ViewPane { + private _table: Table; + public get table(): Table { return this._table; } constructor( - private columns: Slick.Column[], - private data: IDisposableDataProvider | Array, - private tableOptions: Slick.GridOptions, + private columns: Slick.Column[], + private data: IDisposableDataProvider | Array, + private tableOptions: Slick.GridOptions, options: IViewPaneOptions, @IKeybindingService keybindingService: IKeybindingService, @IContextMenuService contextMenuService: IContextMenuService, @@ -220,14 +220,14 @@ export class InsightsDialogView extends Modal { const itemsHeaderTitle = nls.localize("insights.dialog.items", "Items"); const itemsDetailHeaderTitle = nls.localize("insights.dialog.itemDetails", "Item Details"); - this._topTableData = new TableDataView(); - this._bottomTableData = new TableDataView(); - let topTableView = this._instantiationService.createInstance(InsightTableView, this._topColumns, this._topTableData, { forceFitColumns: true }, { id: 'insights.top', title: itemsHeaderTitle }) as InsightTableView; + this._topTableData = new TableDataView(); + this._bottomTableData = new TableDataView(); + let topTableView = this._instantiationService.createInstance(InsightTableView, this._topColumns, this._topTableData, { forceFitColumns: true }, { id: 'insights.top', title: itemsHeaderTitle }); topTableView.render(); attachPanelStyler(topTableView, this._themeService); this._topTable = topTableView.table; this._topTable.setSelectionModel(new RowSelectionModel()); - let bottomTableView = this._instantiationService.createInstance(InsightTableView, this._bottomColumns, this._bottomTableData, { forceFitColumns: true }, { id: 'insights.bottom', title: itemsDetailHeaderTitle }) as InsightTableView; + let bottomTableView = this._instantiationService.createInstance(InsightTableView, this._bottomColumns, this._bottomTableData, { forceFitColumns: true }, { id: 'insights.bottom', title: itemsDetailHeaderTitle }); bottomTableView.render(); attachPanelStyler(bottomTableView, this._themeService); this._bottomTable = bottomTableView.table; @@ -275,8 +275,8 @@ export class InsightsDialogView extends Modal { this._register(attachTableStyler(this._topTable, this._themeService)); this._register(attachTableStyler(this._bottomTable, this._themeService)); - this._topTable.grid.onKeyDown.subscribe((e: KeyboardEvent) => { - let event = new StandardKeyboardEvent(e); + this._topTable.grid.onKeyDown.subscribe(e => { + let event = new StandardKeyboardEvent(e as KeyboardEvent); if (event.equals(KeyMod.Shift | KeyCode.Tab)) { topTableView.focus(); e.stopImmediatePropagation(); @@ -286,8 +286,8 @@ export class InsightsDialogView extends Modal { } }); - this._bottomTable.grid.onKeyDown.subscribe((e: KeyboardEvent) => { - let event = new StandardKeyboardEvent(e); + this._bottomTable.grid.onKeyDown.subscribe(e => { + let event = new StandardKeyboardEvent(e as KeyboardEvent); if (event.equals(KeyMod.Shift | KeyCode.Tab)) { bottomTableView.focus(); e.stopImmediatePropagation(); diff --git a/src/sql/workbench/services/languageAssociation/common/languageAssociation.ts b/src/sql/workbench/services/languageAssociation/common/languageAssociation.ts index 33d75f9127..049b52659b 100644 --- a/src/sql/workbench/services/languageAssociation/common/languageAssociation.ts +++ b/src/sql/workbench/services/languageAssociation/common/languageAssociation.ts @@ -53,7 +53,7 @@ const languageAssociationRegistry = new class implements ILanguageAssociationReg } } - registerLanguageAssociation(languages: string[], contribution: ILanguageAssociationSignature, isDefault?: boolean): IDisposable { + registerLanguageAssociation(languages: string[], contribution: ILanguageAssociationSignature, isDefault?: boolean): IDisposable { for (const language of languages) { this.associationContructors.set(language, contribution); } diff --git a/src/sql/workbench/services/restore/browser/restoreDialog.ts b/src/sql/workbench/services/restore/browser/restoreDialog.ts index ac9249dafb..113dbbe494 100644 --- a/src/sql/workbench/services/restore/browser/restoreDialog.ts +++ b/src/sql/workbench/services/restore/browser/restoreDialog.ts @@ -379,8 +379,8 @@ export class RestoreDialog extends Modal { } }); - this._restorePlanTable.grid.onKeyDown.subscribe((e: KeyboardEvent) => { - let event = new StandardKeyboardEvent(e); + this._restorePlanTable.grid.onKeyDown.subscribe(e => { + let event = new StandardKeyboardEvent(e as KeyboardEvent); if (event.equals(KeyMod.Shift | KeyCode.Tab)) { this._destinationRestoreToInputBox.isEnabled() ? this._destinationRestoreToInputBox.focus() : this._databaseDropdown.focus(); e.stopImmediatePropagation(); @@ -390,8 +390,8 @@ export class RestoreDialog extends Modal { } }); - this._fileListTable.grid.onKeyDown.subscribe((e: KeyboardEvent) => { - let event = new StandardKeyboardEvent(e); + this._fileListTable.grid.onKeyDown.subscribe(e => { + let event = new StandardKeyboardEvent(e as KeyboardEvent); if (event.equals(KeyMod.Shift | KeyCode.Tab)) { if ((this._optionsMap[this._relocatedLogFileFolderOption]).isEnabled()) { (this._optionsMap[this._relocatedLogFileFolderOption]).focus(); diff --git a/src/typings/slickgrid.d.ts b/src/typings/slickgrid.d.ts index cdba93425c..69f5111da3 100644 --- a/src/typings/slickgrid.d.ts +++ b/src/typings/slickgrid.d.ts @@ -1554,7 +1554,7 @@ declare namespace Slick { } export interface Formatter { - (row: number, cell: number, value: any, columnDef: Column, dataContext: SlickData): string | undefined; + (row: number, cell: number, value: any, columnDef: Column, dataContext: T): string | undefined; } export module Formatters { diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts index 7b73ba0503..78dc3d2ea8 100644 --- a/src/vs/base/browser/markdownRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -209,7 +209,10 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende 'iframe': ['allowfullscreen', 'frameborder', 'src'], 'img': ['src', 'title', 'alt', 'width', 'height'], 'div': ['class', 'data-code'], - 'span': ['class'] + 'span': ['class'], + // https://github.com/microsoft/vscode/issues/95937 + 'th': ['align'], + 'td': ['align'] } }); diff --git a/src/vs/base/browser/ui/aria/aria.ts b/src/vs/base/browser/ui/aria/aria.ts index 1d1f0b0dd4..fa96f4fc4a 100644 --- a/src/vs/base/browser/ui/aria/aria.ts +++ b/src/vs/base/browser/ui/aria/aria.ts @@ -7,6 +7,8 @@ import 'vs/css!./aria'; import { isMacintosh } from 'vs/base/common/platform'; import * as dom from 'vs/base/browser/dom'; +// Use a max length since we are inserting the whole msg in the DOM and that can cause browsers to freeze for long messages #94233 +const MAX_MESSAGE_LENGTH = 20000; let ariaContainer: HTMLElement; let alertContainer: HTMLElement; let statusContainer: HTMLElement; @@ -54,6 +56,9 @@ function insertMessage(target: HTMLElement, msg: string): void { } dom.clearNode(target); + if (msg.length > MAX_MESSAGE_LENGTH) { + msg = msg.substr(0, MAX_MESSAGE_LENGTH); + } target.textContent = msg; // See https://www.paciellogroup.com/blog/2012/06/html5-accessibility-chops-aria-rolealert-browser-support/ diff --git a/src/vs/base/browser/ui/button/button.css b/src/vs/base/browser/ui/button/button.css index 104257ed9c..3053eae9fd 100644 --- a/src/vs/base/browser/ui/button/button.css +++ b/src/vs/base/browser/ui/button/button.css @@ -5,12 +5,14 @@ .monaco-text-button { box-sizing: border-box; - display: inline-block; + display: flex; width: 100%; padding: 4px; text-align: center; cursor: pointer; outline-offset: 2px !important; + justify-content: center; + align-items: center; } .monaco-text-button:hover { @@ -21,3 +23,8 @@ opacity: 0.4; cursor: default; } + +.monaco-button > .codicon { + margin: 0 0.2em; + color: inherit !important; +} diff --git a/src/vs/base/browser/ui/button/button.ts b/src/vs/base/browser/ui/button/button.ts index 05696da41b..86434491ef 100644 --- a/src/vs/base/browser/ui/button/button.ts +++ b/src/vs/base/browser/ui/button/button.ts @@ -12,9 +12,12 @@ import { mixin } from 'vs/base/common/objects'; import { Event as BaseEvent, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { Gesture, EventType } from 'vs/base/browser/touch'; +import { renderCodicons } from 'vs/base/common/codicons'; +import { escape } from 'vs/base/common/strings'; export interface IButtonOptions extends IButtonStyles { - title?: boolean | string; + readonly title?: boolean | string; + readonly supportCodicons?: boolean; } export interface IButtonStyles { @@ -150,7 +153,11 @@ export class Button extends Disposable { if (!DOM.hasClass(this._element, 'monaco-text-button')) { DOM.addClass(this._element, 'monaco-text-button'); } - this._element.textContent = value; + if (this.options.supportCodicons) { + this._element.innerHTML = renderCodicons(escape(value)); + } else { + this._element.textContent = value; + } this._element.setAttribute('aria-label', value); // {{SQL CARBON EDIT}} if (typeof this.options.title === 'string') { this._element.title = this.options.title; diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon-animations.css b/src/vs/base/browser/ui/codicons/codicon/codicon-animations.css index 6ba05a2b0d..2f15865606 100644 --- a/src/vs/base/browser/ui/codicons/codicon/codicon-animations.css +++ b/src/vs/base/browser/ui/codicons/codicon/codicon-animations.css @@ -10,5 +10,6 @@ } .codicon-animation-spin { - animation: codicon-spin 1.5s linear infinite; + /* Use steps to throttle FPS to reduce CPU usage */ + animation: codicon-spin 1.5s steps(30) infinite; } diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 3d49dc3b2b..7b6cef6131 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -59,6 +59,7 @@ export interface IListViewOptions { readonly horizontalScrolling?: boolean; readonly accessibilityProvider?: IListViewAccessibilityProvider; readonly additionalScrollHeight?: number; + readonly transformOptimization?: boolean; } const DefaultOptions = { @@ -74,7 +75,8 @@ const DefaultOptions = { onDragOver() { return false; }, drop() { } }, - horizontalScrolling: false + horizontalScrolling: false, + transformOptimization: true }; export class ElementsDragAndDropData implements IDragAndDropData { @@ -275,7 +277,11 @@ export class ListView implements ISpliceable, IDisposable { this.rowsContainer = document.createElement('div'); this.rowsContainer.className = 'monaco-list-rows'; - this.rowsContainer.style.transform = 'translate3d(0px, 0px, 0px)'; + + if (options.transformOptimization) { + this.rowsContainer.style.transform = 'translate3d(0px, 0px, 0px)'; + } + this.disposables.add(Gesture.addTarget(this.rowsContainer)); this.scrollableElement = this.disposables.add(new ScrollableElement(this.rowsContainer, { diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index d4ea989b6d..b03c0d8c3a 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -838,6 +838,7 @@ export interface IListOptions { readonly mouseSupport?: boolean; readonly horizontalScrolling?: boolean; readonly additionalScrollHeight?: number; + readonly transformOptimization?: boolean; } export interface IListStyles { diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index ca1f47f80a..a716e451a6 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -84,7 +84,7 @@ export class Menu extends ActionBar { context: options.context, actionRunner: options.actionRunner, ariaLabel: options.ariaLabel, - triggerKeys: { keys: [KeyCode.Enter, ...(isMacintosh ? [KeyCode.Space] : [])], keyDown: true } + triggerKeys: { keys: [KeyCode.Enter, ...(isMacintosh || isLinux ? [KeyCode.Space] : [])], keyDown: true } }); this.menuElement = menuElement; diff --git a/src/vs/base/browser/ui/splitview/paneview.ts b/src/vs/base/browser/ui/splitview/paneview.ts index 1d9b1b519b..35a2a7b023 100644 --- a/src/vs/base/browser/ui/splitview/paneview.ts +++ b/src/vs/base/browser/ui/splitview/paneview.ts @@ -184,13 +184,18 @@ export abstract class Pane extends Disposable implements IView { } this._orientation = orientation; + + if (this.header) { + this.updateHeader(); + } } render(): void { this.header = $('.pane-header'); append(this.element, this.header); this.header.setAttribute('tabindex', '0'); - this.header.setAttribute('role', 'toolbar'); + // Use role button so the aria-expanded state gets read https://github.com/microsoft/vscode/issues/95996 + this.header.setAttribute('role', 'button'); this.header.setAttribute('aria-label', this.ariaHeaderLabel); this.renderHeader(this.header); diff --git a/src/vs/base/browser/ui/tree/media/tree.css b/src/vs/base/browser/ui/tree/media/tree.css index cd5e1baad2..75074f008d 100644 --- a/src/vs/base/browser/ui/tree/media/tree.css +++ b/src/vs/base/browser/ui/tree/media/tree.css @@ -61,5 +61,6 @@ } .monaco-tl-twistie.codicon-tree-item-loading::before { - animation: codicon-spin 1.25s linear infinite; + /* Use steps to throttle FPS to reduce CPU usage */ + animation: codicon-spin 1.25s steps(30) infinite; } diff --git a/src/vs/base/common/filters.ts b/src/vs/base/common/filters.ts index df41047755..6e89b1b1f2 100644 --- a/src/vs/base/common/filters.ts +++ b/src/vs/base/common/filters.ts @@ -392,7 +392,7 @@ export function anyScore(pattern: string, lowPattern: string, _patternPos: numbe //#region --- fuzzyScore --- -export function createMatches(score: undefined | FuzzyScore, offset = 0): IMatch[] { +export function createMatches(score: undefined | FuzzyScore): IMatch[] { if (typeof score === 'undefined') { return []; } @@ -404,10 +404,10 @@ export function createMatches(score: undefined | FuzzyScore, offset = 0): IMatch for (let pos = wordStart; pos < _maxLen; pos++) { if (matches[matches.length - (pos + 1)] === '1') { const last = res[res.length - 1]; - if (last && last.end === pos + offset) { - last.end = pos + offset + 1; + if (last && last.end === pos) { + last.end = pos + 1; } else { - res.push({ start: pos + offset, end: pos + offset + 1 }); + res.push({ start: pos, end: pos + 1 }); } } } diff --git a/src/vs/base/common/fuzzyScorer.ts b/src/vs/base/common/fuzzyScorer.ts index b4098400e6..53a8fca091 100644 --- a/src/vs/base/common/fuzzyScorer.ts +++ b/src/vs/base/common/fuzzyScorer.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { compareAnything } from 'vs/base/common/comparers'; -import { matchesPrefix, IMatch, matchesCamelCase, isUpper, fuzzyScore, createMatches as createFuzzyMatches } from 'vs/base/common/filters'; +import { matchesPrefix, IMatch, matchesCamelCase, isUpper, fuzzyScore, createMatches as createFuzzyMatches, matchesStrictPrefix } from 'vs/base/common/filters'; import { sep } from 'vs/base/common/path'; import { isWindows, isLinux } from 'vs/base/common/platform'; import { stripWildcards, equalsIgnoreCase } from 'vs/base/common/strings'; @@ -281,24 +281,24 @@ export type FuzzyScore2 = [number | undefined /* score */, IMatch[]]; const NO_SCORE2: FuzzyScore2 = [undefined, []]; -export function scoreFuzzy2(target: string, query: IPreparedQuery | IPreparedQueryPiece, patternStart = 0, matchOffset = 0): FuzzyScore2 { +export function scoreFuzzy2(target: string, query: IPreparedQuery | IPreparedQueryPiece, patternStart = 0, wordStart = 0): FuzzyScore2 { // Score: multiple inputs const preparedQuery = query as IPreparedQuery; if (preparedQuery.values && preparedQuery.values.length > 1) { - return doScoreFuzzy2Multiple(target, preparedQuery.values, patternStart, matchOffset); + return doScoreFuzzy2Multiple(target, preparedQuery.values, patternStart, wordStart); } // Score: single input - return doScoreFuzzy2Single(target, query, patternStart, matchOffset); + return doScoreFuzzy2Single(target, query, patternStart, wordStart); } -function doScoreFuzzy2Multiple(target: string, query: IPreparedQueryPiece[], patternStart: number, matchOffset: number): FuzzyScore2 { +function doScoreFuzzy2Multiple(target: string, query: IPreparedQueryPiece[], patternStart: number, wordStart: number): FuzzyScore2 { let totalScore = 0; const totalMatches: IMatch[] = []; for (const queryPiece of query) { - const [score, matches] = doScoreFuzzy2Single(target, queryPiece, patternStart, matchOffset); + const [score, matches] = doScoreFuzzy2Single(target, queryPiece, patternStart, wordStart); if (typeof score !== 'number') { // if a single query value does not match, return with // no score entirely, we require all queries to match @@ -314,13 +314,13 @@ function doScoreFuzzy2Multiple(target: string, query: IPreparedQueryPiece[], pat return [totalScore, normalizeMatches(totalMatches)]; } -function doScoreFuzzy2Single(target: string, query: IPreparedQueryPiece, patternStart: number, matchOffset: number): FuzzyScore2 { - const score = fuzzyScore(query.original, query.originalLowercase, patternStart, target, target.toLowerCase(), 0, true); +function doScoreFuzzy2Single(target: string, query: IPreparedQueryPiece, patternStart: number, wordStart: number): FuzzyScore2 { + const score = fuzzyScore(query.original, query.originalLowercase, patternStart, target, target.toLowerCase(), wordStart, true); if (!score) { return NO_SCORE2; } - return [score[0], createFuzzyMatches(score, matchOffset)]; + return [score[0], createFuzzyMatches(score)]; } //#endregion @@ -370,9 +370,10 @@ export interface IItemAccessor { } const PATH_IDENTITY_SCORE = 1 << 18; -const LABEL_PREFIX_SCORE = 1 << 17; -const LABEL_CAMELCASE_SCORE = 1 << 16; -const LABEL_SCORE_THRESHOLD = 1 << 15; +const LABEL_PREFIX_SCORE_MATCHCASE = 1 << 17; +const LABEL_PREFIX_SCORE_IGNORECASE = 1 << 16; +const LABEL_CAMELCASE_SCORE = 1 << 15; +const LABEL_SCORE_THRESHOLD = 1 << 14; export function scoreItemFuzzy(item: T, query: IPreparedQuery, fuzzy: boolean, accessor: IItemAccessor, cache: FuzzyScorerCache): IItemScore { if (!item || !query.normalized) { @@ -458,13 +459,14 @@ function doScoreItemFuzzySingle(label: string, description: string | undefined, // Prefer label matches if told so if (preferLabelMatches) { - // Treat prefix matches on the label second highest - const prefixLabelMatch = matchesPrefix(query.normalized, label); - if (prefixLabelMatch) { - return { score: LABEL_PREFIX_SCORE, labelMatch: prefixLabelMatch }; + // Treat prefix matches on the label highest + const prefixLabelMatchIgnoreCase = matchesPrefix(query.normalized, label); + if (prefixLabelMatchIgnoreCase) { + const prefixLabelMatchStrictCase = matchesStrictPrefix(query.normalized, label); + return { score: prefixLabelMatchStrictCase ? LABEL_PREFIX_SCORE_MATCHCASE : LABEL_PREFIX_SCORE_IGNORECASE, labelMatch: prefixLabelMatchStrictCase || prefixLabelMatchIgnoreCase }; } - // Treat camelcase matches on the label third highest + // Treat camelcase matches on the label second highest const camelcaseLabelMatch = matchesCamelCase(query.normalized, label); if (camelcaseLabelMatch) { return { score: LABEL_CAMELCASE_SCORE, labelMatch: camelcaseLabelMatch }; @@ -600,10 +602,10 @@ export function compareItemsByFuzzyScore(itemA: T, itemB: T, query: IPrepared } } - // 2.) prefer label prefix matches - if (scoreA === LABEL_PREFIX_SCORE || scoreB === LABEL_PREFIX_SCORE) { + // 2.) prefer label prefix matches (match case) + if (scoreA === LABEL_PREFIX_SCORE_MATCHCASE || scoreB === LABEL_PREFIX_SCORE_MATCHCASE) { if (scoreA !== scoreB) { - return scoreA === LABEL_PREFIX_SCORE ? -1 : 1; + return scoreA === LABEL_PREFIX_SCORE_MATCHCASE ? -1 : 1; } const labelA = accessor.getItemLabel(itemA) || ''; @@ -615,7 +617,22 @@ export function compareItemsByFuzzyScore(itemA: T, itemB: T, query: IPrepared } } - // 3.) prefer camelcase matches + // 3.) prefer label prefix matches (ignore case) + if (scoreA === LABEL_PREFIX_SCORE_IGNORECASE || scoreB === LABEL_PREFIX_SCORE_IGNORECASE) { + if (scoreA !== scoreB) { + return scoreA === LABEL_PREFIX_SCORE_IGNORECASE ? -1 : 1; + } + + const labelA = accessor.getItemLabel(itemA) || ''; + const labelB = accessor.getItemLabel(itemB) || ''; + + // prefer shorter names when both match on label prefix + if (labelA.length !== labelB.length) { + return labelA.length - labelB.length; + } + } + + // 4.) prefer camelcase matches if (scoreA === LABEL_CAMELCASE_SCORE || scoreB === LABEL_CAMELCASE_SCORE) { if (scoreA !== scoreB) { return scoreA === LABEL_CAMELCASE_SCORE ? -1 : 1; @@ -636,7 +653,7 @@ export function compareItemsByFuzzyScore(itemA: T, itemB: T, query: IPrepared } } - // 4.) prefer label scores + // 5.) prefer label scores if (scoreA > LABEL_SCORE_THRESHOLD || scoreB > LABEL_SCORE_THRESHOLD) { if (scoreB < LABEL_SCORE_THRESHOLD) { return -1; @@ -647,12 +664,12 @@ export function compareItemsByFuzzyScore(itemA: T, itemB: T, query: IPrepared } } - // 5.) compare by score + // 6.) compare by score if (scoreA !== scoreB) { return scoreA > scoreB ? -1 : 1; } - // 6.) prefer matches in label over non-label matches + // 7.) prefer matches in label over non-label matches const itemAHasLabelMatches = Array.isArray(itemScoreA.labelMatch) && itemScoreA.labelMatch.length > 0; const itemBHasLabelMatches = Array.isArray(itemScoreB.labelMatch) && itemScoreB.labelMatch.length > 0; if (itemAHasLabelMatches && !itemBHasLabelMatches) { @@ -661,14 +678,14 @@ export function compareItemsByFuzzyScore(itemA: T, itemB: T, query: IPrepared return 1; } - // 7.) scores are identical, prefer more compact matches (label and description) + // 8.) scores are identical, prefer more compact matches (label and description) const itemAMatchDistance = computeLabelAndDescriptionMatchDistance(itemA, itemScoreA, accessor); const itemBMatchDistance = computeLabelAndDescriptionMatchDistance(itemB, itemScoreB, accessor); if (itemAMatchDistance && itemBMatchDistance && itemAMatchDistance !== itemBMatchDistance) { return itemBMatchDistance > itemAMatchDistance ? -1 : 1; } - // 7.) at this point, scores are identical and match compactness as well + // 9.) at this point, scores are identical and match compactness as well // for both items so we start to use the fallback compare return fallbackCompare(itemA, itemB, query, accessor); } diff --git a/src/vs/base/common/jsonSchema.ts b/src/vs/base/common/jsonSchema.ts index f91092e953..9f2c8519b8 100644 --- a/src/vs/base/common/jsonSchema.ts +++ b/src/vs/base/common/jsonSchema.ts @@ -53,17 +53,18 @@ export interface IJSONSchema { then?: IJSONSchema; else?: IJSONSchema; - // VSCode extensions - defaultSnippets?: IJSONSchemaSnippet[]; // VSCode extension - errorMessage?: string; // VSCode extension - patternErrorMessage?: string; // VSCode extension - deprecationMessage?: string; // VSCode extension - enumDescriptions?: string[]; // VSCode extension - markdownEnumDescriptions?: string[]; // VSCode extension - markdownDescription?: string; // VSCode extension - doNotSuggest?: boolean; // VSCode extension - allowComments?: boolean; // VSCode extension - allowTrailingCommas?: boolean; // VSCode extension + // VS Code extensions + defaultSnippets?: IJSONSchemaSnippet[]; + errorMessage?: string; + patternErrorMessage?: string; + deprecationMessage?: string; + markdownDeprecationMessage?: string; + enumDescriptions?: string[]; + markdownEnumDescriptions?: string[]; + markdownDescription?: string; + doNotSuggest?: boolean; + allowComments?: boolean; + allowTrailingCommas?: boolean; } export interface IJSONSchemaMap { diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts index 0c47a5b378..ebf6469919 100644 --- a/src/vs/base/parts/quickinput/browser/quickInput.ts +++ b/src/vs/base/parts/quickinput/browser/quickInput.ts @@ -853,7 +853,7 @@ class QuickPick extends QuickInput implements IQuickPi } dom.toggleClass(this.ui.container, 'hidden-input', hideInput); const visibilities: Visibilities = { - title: !!this.title || !!this.step, + title: !!this.title || !!this.step || !!this.buttons.length, description: !!this.description, checkAll: this.canSelectMany, inputBox: !hideInput, @@ -1048,7 +1048,12 @@ class InputBox extends QuickInput implements IInputBox { if (!this.visible) { return; } - this.ui.setVisibilities({ title: !!this.title || !!this.step, description: !!this.description || !!this.step, inputBox: true, message: true }); + const visibilities: Visibilities = { + title: !!this.title || !!this.step || !!this.buttons.length, + description: !!this.description || !!this.step, + inputBox: true, message: true + }; + this.ui.setVisibilities(visibilities); super.update(); if (this.ui.inputBox.value !== this.value) { this.ui.inputBox.value = this.value; diff --git a/src/vs/base/test/common/fuzzyScorer.test.ts b/src/vs/base/test/common/fuzzyScorer.test.ts index f12a1ed8b1..b8521cec72 100644 --- a/src/vs/base/test/common/fuzzyScorer.test.ts +++ b/src/vs/base/test/common/fuzzyScorer.test.ts @@ -912,6 +912,19 @@ suite('Fuzzy Scorer', () => { assert.equal(res[0], resourceB); }); + test('compareFilesByScore - prefer case match (bug #96122)', function () { + const resourceA = URI.file('lists.php'); + const resourceB = URI.file('lib/Lists.php'); + + let query = 'Lists.php'; + + let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); + assert.equal(res[0], resourceB); + + res = [resourceB, resourceA].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); + assert.equal(res[0], resourceB); + }); + test('prepareQuery', () => { assert.equal(scorer.prepareQuery(' f*a ').normalized, 'fa'); assert.equal(scorer.prepareQuery('model Tester.ts').original, 'model Tester.ts'); @@ -977,14 +990,14 @@ suite('Fuzzy Scorer', () => { const target = 'HeLlo-World'; for (const offset of [0, 3]) { - let [score, matches] = _doScore2(target, 'HeLlo-World', offset); + let [score, matches] = _doScore2(offset === 0 ? target : `123${target}`, 'HeLlo-World', offset); assert.ok(score); assert.equal(matches.length, 1); assert.equal(matches[0].start, 0 + offset); assert.equal(matches[0].end, target.length + offset); - [score, matches] = _doScore2(target, 'HW', offset); + [score, matches] = _doScore2(offset === 0 ? target : `123${target}`, 'HW', offset); assert.ok(score); assert.equal(matches.length, 2); diff --git a/src/vs/code/browser/workbench/workbench-dev.html b/src/vs/code/browser/workbench/workbench-dev.html index 1c45d94ece..1645bac22b 100644 --- a/src/vs/code/browser/workbench/workbench-dev.html +++ b/src/vs/code/browser/workbench/workbench-dev.html @@ -28,7 +28,7 @@ baseUrl: `${window.location.origin}/static/out`, paths: { 'vscode-textmate': `${window.location.origin}/static/remote/web/node_modules/vscode-textmate/release/main`, - 'onigasm-umd': `${window.location.origin}/static/remote/web/node_modules/onigasm-umd/release/main`, + 'vscode-oniguruma': `${window.location.origin}/static/remote/web/node_modules/vscode-oniguruma/release/main`, 'xterm': `${window.location.origin}/static/remote/web/node_modules/xterm/lib/xterm.js`, 'xterm-addon-search': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-search/lib/xterm-addon-search.js`, 'xterm-addon-unicode11': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-unicode11/lib/xterm-addon-unicode11.js`, diff --git a/src/vs/code/browser/workbench/workbench.html b/src/vs/code/browser/workbench/workbench.html index 6c5da5cd54..3deb653f0a 100644 --- a/src/vs/code/browser/workbench/workbench.html +++ b/src/vs/code/browser/workbench/workbench.html @@ -32,7 +32,7 @@ baseUrl: `${window.location.origin}/static/out`, paths: { 'vscode-textmate': `${window.location.origin}/static/node_modules/vscode-textmate/release/main`, - 'onigasm-umd': `${window.location.origin}/static/node_modules/onigasm-umd/release/main`, + 'vscode-oniguruma': `${window.location.origin}/static/node_modules/vscode-oniguruma/release/main`, 'xterm': `${window.location.origin}/static/node_modules/xterm/lib/xterm.js`, 'xterm-addon-search': `${window.location.origin}/static/node_modules/xterm-addon-search/lib/xterm-addon-search.js`, 'xterm-addon-unicode11': `${window.location.origin}/static/node_modules/xterm-addon-unicode11/lib/xterm-addon-unicode11.js`, diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index 7df6fbd3bc..d73e0da1c2 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -341,7 +341,7 @@ export class IssueReporter extends Disposable { const appender = combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(logService)); const commonProperties = resolveCommonProperties(product.commit || 'Commit unknown', product.version, configuration.machineId, product.msftInternalDomains, this.environmentService.installSourcePath); const piiPaths = this.environmentService.extensionsPath ? [this.environmentService.appRoot, this.environmentService.extensionsPath] : [this.environmentService.appRoot]; - const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths }; + const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths, sendErrorTelemetry: true }; const telemetryService = instantiationService.createInstance(TelemetryService, config); this._register(telemetryService); @@ -716,7 +716,7 @@ export class IssueReporter extends Disposable { type IssueReporterSearchError = { message: string; }; - this.telemetryService.publicLog2('issueReporterSearchError', { message: error.message }, true); + this.telemetryService.publicLogError2('issueReporterSearchError', { message: error.message }); } private setUpTypes(): void { diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 1bf5b414d3..a6090e54fc 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -176,6 +176,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat const config: ITelemetryServiceConfig = { appender: combinedAppender(appInsightsAppender, new LogAppender(logService)), commonProperties: resolveCommonProperties(product.commit, product.version, configuration.machineId, product.msftInternalDomains, installSourcePath), + sendErrorTelemetry: true, piiPaths: extensionsPath ? [appRoot, extensionsPath] : [appRoot] }; diff --git a/src/vs/code/electron-browser/workbench/workbench.html b/src/vs/code/electron-browser/workbench/workbench.html index e95fcdb9c5..4268647aab 100644 --- a/src/vs/code/electron-browser/workbench/workbench.html +++ b/src/vs/code/electron-browser/workbench/workbench.html @@ -3,7 +3,6 @@ - diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 157827fd0f..daaf11cb57 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -193,7 +193,7 @@ export class CodeApplication extends Disposable { return; } - delete (webPreferences as { preloadURL: string }).preloadURL; // https://github.com/electron/electron/issues/21553 + delete (webPreferences as { preloadURL: string | undefined }).preloadURL; // https://github.com/electron/electron/issues/21553 // Otherwise prevent loading this.logService.error('webContents#web-contents-created: Prevented webview attach'); @@ -489,7 +489,7 @@ export class CodeApplication extends Disposable { const appender = combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(this.logService)); const commonProperties = resolveCommonProperties(product.commit, product.version, machineId, product.msftInternalDomains, this.environmentService.installSourcePath); const piiPaths = this.environmentService.extensionsPath ? [this.environmentService.appRoot, this.environmentService.extensionsPath] : [this.environmentService.appRoot]; - const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths, trueMachineId }; + const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths, trueMachineId, sendErrorTelemetry: true }; services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config])); } else { diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 69fde9feda..31400ad7c2 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -746,11 +746,11 @@ export class CodeWindow extends Disposable implements ICodeWindow { // Config (combination of process.argv and window configuration) const environment = parseArgs(process.argv, OPTIONS); - const config = Object.assign(environment, windowConfiguration); + const config = Object.assign(environment, windowConfiguration) as unknown as { [key: string]: unknown }; for (const key in config) { - const configValue = (config as any)[key]; + const configValue = config[key]; if (configValue === undefined || configValue === null || configValue === '' || configValue === false) { - delete (config as any)[key]; // only send over properties that have a true value + delete config[key]; // only send over properties that have a true value } } diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index cd2a2aa44d..84fa97f9c0 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -345,6 +345,7 @@ export async function main(argv: ParsedArgs): Promise { const config: ITelemetryServiceConfig = { appender: combinedAppender(...appenders), + sendErrorTelemetry: false, commonProperties: resolveCommonProperties(product.commit, product.version, stateService.getItem('telemetry.machineId'), product.msftInternalDomains, installSourcePath), piiPaths: extensionsPath ? [appRoot, extensionsPath] : [appRoot] }; diff --git a/src/vs/editor/browser/core/editorState.ts b/src/vs/editor/browser/core/editorState.ts index ae39a4c666..4910247c1f 100644 --- a/src/vs/editor/browser/core/editorState.ts +++ b/src/vs/editor/browser/core/editorState.ts @@ -6,7 +6,7 @@ import * as strings from 'vs/base/common/strings'; import { ICodeEditor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; import { Position } from 'vs/editor/common/core/position'; -import { Range } from 'vs/editor/common/core/range'; +import { Range, IRange } from 'vs/editor/common/core/range'; import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ITextModel } from 'vs/editor/common/model'; @@ -87,19 +87,28 @@ export class EditorState { /** * A cancellation token source that cancels when the editor changes as expressed * by the provided flags + * @param range If provided, changes in position and selection within this range will not trigger cancellation */ export class EditorStateCancellationTokenSource extends EditorKeybindingCancellationTokenSource implements IDisposable { private readonly _listener = new DisposableStore(); - constructor(readonly editor: IActiveCodeEditor, flags: CodeEditorStateFlag, parent?: CancellationToken) { + constructor(readonly editor: IActiveCodeEditor, flags: CodeEditorStateFlag, range?: IRange, parent?: CancellationToken) { super(editor, parent); if (flags & CodeEditorStateFlag.Position) { - this._listener.add(editor.onDidChangeCursorPosition(_ => this.cancel())); + this._listener.add(editor.onDidChangeCursorPosition(e => { + if (!range || !Range.containsPosition(range, e.position)) { + this.cancel(); + } + })); } if (flags & CodeEditorStateFlag.Selection) { - this._listener.add(editor.onDidChangeCursorSelection(_ => this.cancel())); + this._listener.add(editor.onDidChangeCursorSelection(e => { + if (!range || !Range.containsRange(range, e.selection)) { + this.cancel(); + } + })); } if (flags & CodeEditorStateFlag.Scroll) { this._listener.add(editor.onDidScrollChange(_ => this.cancel())); diff --git a/src/vs/editor/browser/viewParts/lines/viewLines.css b/src/vs/editor/browser/viewParts/lines/viewLines.css index 94577d7651..47e4e96019 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLines.css +++ b/src/vs/editor/browser/viewParts/lines/viewLines.css @@ -27,11 +27,6 @@ white-space: nowrap; } -.monaco-editor.vs-dark.mac .view-lines, -.monaco-editor.hc-black.mac .view-lines { - cursor: -webkit-image-set(url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAL0lEQVQoz2NgCD3x//9/BhBYBWdhgFVAiVW4JBFKGIa4AqD0//9D3pt4I4tAdAMAHTQ/j5Zom30AAAAASUVORK5CYII=') 1x, url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAz0lEQVRIx2NgYGBY/R8I/vx5eelX3n82IJ9FxGf6tksvf/8FiTMQAcAGQMDvSwu09abffY8QYSAScNk45G198eX//yev73/4///701eh//kZSARckrNBRvz//+8+6ZohwCzjGNjdgQxkAg7B9WADeBjIBqtJCbhRA0YNoIkBSNmaPEMoNmA0FkYNoFKhapJ6FGyAH3nauaSmPfwI0v/3OukVi0CIZ+F25KrtYcx/CTIy0e+rC7R1Z4KMICVTQQ14feVXIbR695u14+Ir4gwAAD49E54wc1kWAAAAAElFTkSuQmCC') 2x) 5 8, text; -} - .monaco-editor .view-line { position: absolute; width: 100%; diff --git a/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts b/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts index 73cc1b15c5..e6e35a96a6 100644 --- a/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts +++ b/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts @@ -10,7 +10,7 @@ import { ViewPart } from 'vs/editor/browser/view/viewPart'; import { Position } from 'vs/editor/common/core/position'; import { IConfiguration } from 'vs/editor/common/editorCommon'; import { TokenizationRegistry } from 'vs/editor/common/modes'; -import { editorCursorForeground, editorOverviewRulerBorder } from 'vs/editor/common/view/editorColorRegistry'; +import { editorCursorForeground, editorOverviewRulerBorder, editorOverviewRulerBackground } from 'vs/editor/common/view/editorColorRegistry'; import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext'; import { ViewContext, EditorTheme } from 'vs/editor/common/view/viewContext'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; @@ -60,7 +60,10 @@ class Settings { const minimapOpts = options.get(EditorOption.minimap); const minimapEnabled = minimapOpts.enabled; const minimapSide = minimapOpts.side; - const backgroundColor = (minimapEnabled ? TokenizationRegistry.getDefaultBackground() : null); + const backgroundColor = minimapEnabled + ? theme.getColor(editorOverviewRulerBackground) || TokenizationRegistry.getDefaultBackground() + : null; + if (backgroundColor === null || minimapSide === 'left') { this.backgroundColor = null; } else { diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index e3595bb8fb..ecd626e0f0 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -38,7 +38,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { defaultInsertColor, defaultRemoveColor, diffBorder, diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, scrollbarShadow, scrollbarSliderBackground, scrollbarSliderHoverBackground, scrollbarSliderActiveBackground } from 'vs/platform/theme/common/colorRegistry'; +import { defaultInsertColor, defaultRemoveColor, diffBorder, diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, scrollbarShadow, scrollbarSliderBackground, scrollbarSliderHoverBackground, scrollbarSliderActiveBackground, diffDiagonalFill } from 'vs/platform/theme/common/colorRegistry'; import { IColorTheme, IThemeService, getThemeTypeSelector, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IDiffLinesChange, InlineDiffMargin } from 'vs/editor/browser/widget/inlineDiffMargin'; @@ -2262,4 +2262,17 @@ registerThemingParticipant((theme, collector) => { `); } + const diffDiagonalFillColor = theme.getColor(diffDiagonalFill); + collector.addRule(` + .monaco-editor .diagonal-fill { + background-image: linear-gradient( + -45deg, + ${diffDiagonalFillColor} 12.5%, + #0000 12.5%, #0000 50%, + ${diffDiagonalFillColor} 50%, ${diffDiagonalFillColor} 62.5%, + #0000 62.5%, #0000 100% + ); + background-size: 8px 8px; + } + `); }); diff --git a/src/vs/editor/browser/widget/diffReview.ts b/src/vs/editor/browser/widget/diffReview.ts index 2e1e1f20a4..adc509761e 100644 --- a/src/vs/editor/browser/widget/diffReview.ts +++ b/src/vs/editor/browser/widget/diffReview.ts @@ -114,6 +114,7 @@ export class DiffReview extends Disposable { this._content = createFastDomNode(document.createElement('div')); this._content.setClassName('diff-review-content'); + this._content.setAttribute('role', 'code'); this.scrollbar = this._register(new DomScrollableElement(this._content.domNode, {})); this.domNode.domNode.appendChild(this.scrollbar.getDomNode()); @@ -748,7 +749,11 @@ export class DiffReview extends Disposable { let ariaLabel: string = ''; switch (type) { case DiffEntryType.Equal: - ariaLabel = nls.localize('equalLine', "{0} original line {1} modified line {2}", lineContent, originalLine, modifiedLine); + if (originalLine === modifiedLine) { + ariaLabel = nls.localize('unchangedLine', "{0} unchanged line {1}", lineContent, originalLine); + } else { + ariaLabel = nls.localize('equalLine', "{0} original line {1} modified line {2}", lineContent, originalLine, modifiedLine); + } break; case DiffEntryType.Insert: ariaLabel = nls.localize('insertLine', "+ {0} modified line {1}", lineContent, modifiedLine); diff --git a/src/vs/editor/browser/widget/media/diagonal-fill.png b/src/vs/editor/browser/widget/media/diagonal-fill.png deleted file mode 100644 index 5cd481a8108c4ed683b1e4c6216342cd85d694e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 185 zcmeAS@N?(olHy`uVBq!ia0vp^oFL4>1|%O$WD@{VjKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCf`#@Q5sCVBk9p!i>lBSEK+1rAk~QN`mv#O3D+9QW*jgGxJLH{9Hp6%8d0) z^$ZORz7#D4s?zXuaSYK2PPVB3SHsNA$1rJk!NWuKF`sx0SS@%Z<+vRFv9~fW+`z<; Xq7XbuI;-M4P!EHrtDnm{r-UW|w0ALU diff --git a/src/vs/editor/browser/widget/media/diffEditor.css b/src/vs/editor/browser/widget/media/diffEditor.css index c4a81bfd50..8fd98bf81f 100644 --- a/src/vs/editor/browser/widget/media/diffEditor.css +++ b/src/vs/editor/browser/widget/media/diffEditor.css @@ -51,16 +51,6 @@ text-align: right; } -.monaco-editor .diagonal-fill { - background: url('diagonal-fill.png'); -} -.monaco-editor.vs-dark .diagonal-fill { - opacity: 0.2; -} -.monaco-editor.hc-black .diagonal-fill { - background: none; -} - /* ---------- Inline Diff ---------- */ .monaco-editor .view-zones .view-lines .view-line span { diff --git a/src/vs/editor/common/commands/shiftCommand.ts b/src/vs/editor/common/commands/shiftCommand.ts index 2063d37e13..95e4643a9a 100644 --- a/src/vs/editor/common/commands/shiftCommand.ts +++ b/src/vs/editor/common/commands/shiftCommand.ts @@ -103,14 +103,14 @@ export class ShiftCommand implements ICommand { const { tabSize, indentSize, insertSpaces } = this._opts; const shouldIndentEmptyLines = (startLine === endLine); - // if indenting or outdenting on a whitespace only line - if (this._selection.isEmpty()) { - if (/^\s*$/.test(model.getLineContent(startLine))) { - this._useLastEditRangeForCursorEndPosition = true; - } - } - if (this._opts.useTabStops) { + // if indenting or outdenting on a whitespace only line + if (this._selection.isEmpty()) { + if (/^\s*$/.test(model.getLineContent(startLine))) { + this._useLastEditRangeForCursorEndPosition = true; + } + } + // keep track of previous line's "miss-alignment" let previousLineExtraSpaces = 0, extraSpaces = 0; for (let lineNumber = startLine; lineNumber <= endLine; lineNumber++, previousLineExtraSpaces = extraSpaces) { @@ -188,6 +188,11 @@ export class ShiftCommand implements ICommand { } } else { + // if indenting or outdenting on a whitespace only line + if (!this._opts.isUnshift && this._selection.isEmpty() && model.getLineLength(startLine) === 0) { + this._useLastEditRangeForCursorEndPosition = true; + } + const oneIndent = (insertSpaces ? cachedStringRepeat(' ', indentSize) : '\t'); for (let lineNumber = startLine; lineNumber <= endLine; lineNumber++) { diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index ee87bcf586..2578b2a599 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -34,7 +34,6 @@ import { withUndefinedAsNull } from 'vs/base/common/types'; import { VSBufferReadableStream, VSBuffer } from 'vs/base/common/buffer'; import { TokensStore, MultilineTokens, countEOL, MultilineTokens2, TokensStore2 } from 'vs/editor/common/model/tokensStore'; import { Color } from 'vs/base/common/color'; -import { Constants } from 'vs/base/common/uint'; import { EditorTheme } from 'vs/editor/common/view/viewContext'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { TextChange } from 'vs/editor/common/model/textChange'; @@ -172,6 +171,21 @@ const enum StringOffsetValidationType { SurrogatePairs = 1, } +type ContinueBracketSearchPredicate = null | (() => boolean); + +class BracketSearchCanceled { + public static INSTANCE = new BracketSearchCanceled(); + _searchCanceledBrand = undefined; + private constructor() { } +} + +function stripBracketSearchCanceled(result: T | null | BracketSearchCanceled): T | null { + if (result instanceof BracketSearchCanceled) { + return null; + } + return result; +} + export class TextModel extends Disposable implements model.ITextModel { private static readonly MODEL_SYNC_LIMIT = 50 * 1024 * 1024; // 50 MB @@ -2014,7 +2028,7 @@ export class TextModel extends Disposable implements model.ITextModel { return null; } - return this._findMatchingBracketUp(data, position); + return stripBracketSearchCanceled(this._findMatchingBracketUp(data, position, null)); } public matchBracket(position: IPosition): [Range, Range] | null { @@ -2062,8 +2076,11 @@ export class TextModel extends Disposable implements model.ITextModel { // check that we didn't hit a bracket too far away from position if (foundBracket.startColumn <= position.column && position.column <= foundBracket.endColumn) { const foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1).toLowerCase(); - const r = this._matchFoundBracket(foundBracket, currentModeBrackets.textIsBracket[foundBracketText], currentModeBrackets.textIsOpenBracket[foundBracketText]); + const r = this._matchFoundBracket(foundBracket, currentModeBrackets.textIsBracket[foundBracketText], currentModeBrackets.textIsOpenBracket[foundBracketText], null); if (r) { + if (r instanceof BracketSearchCanceled) { + return null; + } bestResult = r; } } @@ -2100,8 +2117,11 @@ export class TextModel extends Disposable implements model.ITextModel { // check that we didn't hit a bracket too far away from position if (foundBracket && foundBracket.startColumn <= position.column && position.column <= foundBracket.endColumn) { const foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1).toLowerCase(); - const r = this._matchFoundBracket(foundBracket, prevModeBrackets.textIsBracket[foundBracketText], prevModeBrackets.textIsOpenBracket[foundBracketText]); + const r = this._matchFoundBracket(foundBracket, prevModeBrackets.textIsBracket[foundBracketText], prevModeBrackets.textIsOpenBracket[foundBracketText], null); if (r) { + if (r instanceof BracketSearchCanceled) { + return null; + } return r; } } @@ -2111,35 +2131,41 @@ export class TextModel extends Disposable implements model.ITextModel { return null; } - private _matchFoundBracket(foundBracket: Range, data: RichEditBracket, isOpen: boolean): [Range, Range] | null { + private _matchFoundBracket(foundBracket: Range, data: RichEditBracket, isOpen: boolean, continueSearchPredicate: ContinueBracketSearchPredicate): [Range, Range] | null | BracketSearchCanceled { if (!data) { return null; } - if (isOpen) { - let matched = this._findMatchingBracketDown(data, foundBracket.getEndPosition()); - if (matched) { - return [foundBracket, matched]; - } - } else { - let matched = this._findMatchingBracketUp(data, foundBracket.getStartPosition()); - if (matched) { - return [foundBracket, matched]; - } + const matched = ( + isOpen + ? this._findMatchingBracketDown(data, foundBracket.getEndPosition(), continueSearchPredicate) + : this._findMatchingBracketUp(data, foundBracket.getStartPosition(), continueSearchPredicate) + ); + + if (!matched) { + return null; } - return null; + if (matched instanceof BracketSearchCanceled) { + return matched; + } + + return [foundBracket, matched]; } - private _findMatchingBracketUp(bracket: RichEditBracket, position: Position): Range | null { + private _findMatchingBracketUp(bracket: RichEditBracket, position: Position, continueSearchPredicate: ContinueBracketSearchPredicate): Range | null | BracketSearchCanceled { // console.log('_findMatchingBracketUp: ', 'bracket: ', JSON.stringify(bracket), 'startPosition: ', String(position)); const languageId = bracket.languageIdentifier.id; const reversedBracketRegex = bracket.reversedRegex; let count = -1; - const searchPrevMatchingBracketInRange = (lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): Range | null => { + let totalCallCount = 0; + const searchPrevMatchingBracketInRange = (lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): Range | null | BracketSearchCanceled => { while (true) { + if (continueSearchPredicate && (++totalCallCount) % 100 === 0 && !continueSearchPredicate()) { + return BracketSearchCanceled.INSTANCE; + } const r = BracketsUtils.findPrevBracketInRange(reversedBracketRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); if (!r) { break; @@ -2214,15 +2240,19 @@ export class TextModel extends Disposable implements model.ITextModel { return null; } - private _findMatchingBracketDown(bracket: RichEditBracket, position: Position): Range | null { + private _findMatchingBracketDown(bracket: RichEditBracket, position: Position, continueSearchPredicate: ContinueBracketSearchPredicate): Range | null | BracketSearchCanceled { // console.log('_findMatchingBracketDown: ', 'bracket: ', JSON.stringify(bracket), 'startPosition: ', String(position)); const languageId = bracket.languageIdentifier.id; const bracketRegex = bracket.forwardRegex; let count = 1; - const searchNextMatchingBracketInRange = (lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): Range | null => { + let totalCallCount = 0; + const searchNextMatchingBracketInRange = (lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): Range | null | BracketSearchCanceled => { while (true) { + if (continueSearchPredicate && (++totalCallCount) % 100 === 0 && !continueSearchPredicate()) { + return BracketSearchCanceled.INSTANCE; + } const r = BracketsUtils.findNextBracketInRange(bracketRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); if (!r) { break; @@ -2452,7 +2482,16 @@ export class TextModel extends Disposable implements model.ITextModel { return null; } - public findEnclosingBrackets(_position: IPosition, maxDuration = Constants.MAX_SAFE_SMALL_INTEGER): [Range, Range] | null { + public findEnclosingBrackets(_position: IPosition, maxDuration?: number): [Range, Range] | null { + let continueSearchPredicate: ContinueBracketSearchPredicate; + if (typeof maxDuration === 'undefined') { + continueSearchPredicate = null; + } else { + const startTime = Date.now(); + continueSearchPredicate = () => { + return (Date.now() - startTime <= maxDuration); + }; + } const position = this.validatePosition(_position); const lineCount = this.getLineCount(); const savedCounts = new Map(); @@ -2468,8 +2507,13 @@ export class TextModel extends Disposable implements model.ITextModel { } counts = savedCounts.get(languageId)!; }; - const searchInRange = (modeBrackets: RichEditBrackets, lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): [Range, Range] | null => { + + let totalCallCount = 0; + const searchInRange = (modeBrackets: RichEditBrackets, lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): [Range, Range] | null | BracketSearchCanceled => { while (true) { + if (continueSearchPredicate && (++totalCallCount) % 100 === 0 && !continueSearchPredicate()) { + return BracketSearchCanceled.INSTANCE; + } const r = BracketsUtils.findNextBracketInRange(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); if (!r) { break; @@ -2485,7 +2529,7 @@ export class TextModel extends Disposable implements model.ITextModel { } if (counts[bracket.index] === -1) { - return this._matchFoundBracket(r, bracket, false); + return this._matchFoundBracket(r, bracket, false, continueSearchPredicate); } } @@ -2496,12 +2540,7 @@ export class TextModel extends Disposable implements model.ITextModel { let languageId: LanguageId = -1; let modeBrackets: RichEditBrackets | null = null; - const startTime = Date.now(); for (let lineNumber = position.lineNumber; lineNumber <= lineCount; lineNumber++) { - const elapsedTime = Date.now() - startTime; - if (elapsedTime > maxDuration) { - return null; - } const lineTokens = this._getLineTokens(lineNumber); const tokenCount = lineTokens.getCount(); const lineText = this._buffer.getLineContent(lineNumber); @@ -2530,7 +2569,7 @@ export class TextModel extends Disposable implements model.ITextModel { if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) { const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset); if (r) { - return r; + return stripBracketSearchCanceled(r); } prevSearchInToken = false; } @@ -2555,7 +2594,7 @@ export class TextModel extends Disposable implements model.ITextModel { if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) { const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset); if (r) { - return r; + return stripBracketSearchCanceled(r); } } } @@ -2566,7 +2605,7 @@ export class TextModel extends Disposable implements model.ITextModel { if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) { const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset); if (r) { - return r; + return stripBracketSearchCanceled(r); } } } diff --git a/src/vs/editor/common/model/tokensStore.ts b/src/vs/editor/common/model/tokensStore.ts index fc82d18636..bc8b169cc0 100644 --- a/src/vs/editor/common/model/tokensStore.ts +++ b/src/vs/editor/common/model/tokensStore.ts @@ -1034,10 +1034,12 @@ export class TokensStore2 { aIndex++; } - if (aIndex < aLen && aTokens.getEndOffset(aIndex) === bEndCharacter) { - // `a` ends exactly at the same spot as `b`! - emitToken(aTokens.getEndOffset(aIndex), (aTokens.getMetadata(aIndex) & aMask) | (bMetadata & bMask)); - aIndex++; + if (aIndex < aLen) { + emitToken(bEndCharacter, (aTokens.getMetadata(aIndex) & aMask) | (bMetadata & bMask)); + if (aTokens.getEndOffset(aIndex) === bEndCharacter) { + // `a` ends exactly at the same spot as `b`! + aIndex++; + } } else { const aMergeIndex = Math.min(Math.max(0, aIndex - 1), aLen - 1); diff --git a/src/vs/editor/common/model/wordHelper.ts b/src/vs/editor/common/model/wordHelper.ts index f7e4e72d23..52b2b24b57 100644 --- a/src/vs/editor/common/model/wordHelper.ts +++ b/src/vs/editor/common/model/wordHelper.ts @@ -94,13 +94,15 @@ export function getWordAtText(column: number, wordDefinition: RegExp, text: stri // should stop so that subsequent search don't repeat previous searches const regexIndex = pos - config.windowSize * i; wordDefinition.lastIndex = Math.max(0, regexIndex); - match = _findRegexMatchEnclosingPosition(wordDefinition, text, pos, prevRegexIndex); + const thisMatch = _findRegexMatchEnclosingPosition(wordDefinition, text, pos, prevRegexIndex); - // stop: found something - if (match) { + if (!thisMatch && match) { + // stop: we have something break; } + match = thisMatch; + // stop: searched at start if (regexIndex <= 0) { break; @@ -112,7 +114,7 @@ export function getWordAtText(column: number, wordDefinition: RegExp, text: stri let result = { word: match[0], startColumn: textOffset + 1 + match.index!, - endColumn: textOffset + 1 + wordDefinition.lastIndex + endColumn: textOffset + 1 + match.index! + match[0].length }; wordDefinition.lastIndex = 0; return result; diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 09082a9028..75c9c51e3f 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -1531,6 +1531,21 @@ export interface CommentReaction { readonly canEdit?: boolean; } +/** + * @internal + */ +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; +} + /** * @internal */ diff --git a/src/vs/editor/common/modes/modesRegistry.ts b/src/vs/editor/common/modes/modesRegistry.ts index f7a5526526..b667fb845c 100644 --- a/src/vs/editor/common/modes/modesRegistry.ts +++ b/src/vs/editor/common/modes/modesRegistry.ts @@ -9,6 +9,7 @@ import { LanguageId, LanguageIdentifier } from 'vs/editor/common/modes'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { ILanguageExtensionPoint } from 'vs/editor/common/services/modeService'; import { Registry } from 'vs/platform/registry/common/platform'; +import { IDisposable } from 'vs/base/common/lifecycle'; // Define extension point ids export const Extensions = { @@ -30,9 +31,19 @@ export class EditorModesRegistry { // --- languages - public registerLanguage(def: ILanguageExtensionPoint): void { + public registerLanguage(def: ILanguageExtensionPoint): IDisposable { this._languages.push(def); this._onDidChangeLanguages.fire(undefined); + return { + dispose: () => { + for (let i = 0, len = this._languages.length; i < len; i++) { + if (this._languages[i] === def) { + this._languages.splice(i, 1); + return; + } + } + } + }; } public setDynamicLanguages(def: ILanguageExtensionPoint[]): void { this._dynamicLanguages = def; diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index 73d894a28e..61b98310bd 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -3,7 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; @@ -28,13 +27,9 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IUndoRedoService, IUndoRedoElement, IPastFutureElements } from 'vs/platform/undoRedo/common/undoRedo'; import { StringSHA1 } from 'vs/base/common/hash'; import { SingleModelEditStackElement, MultiModelEditStackElement, EditStackElement } from 'vs/editor/common/model/editStack'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { Schemas } from 'vs/base/common/network'; -import Severity from 'vs/base/common/severity'; import { SemanticTokensProviderStyling, toMultilineTokens2 } from 'vs/editor/common/services/semanticTokensProviderStyling'; -export const MAINTAIN_UNDO_REDO_STACK = true; - export interface IEditorSemanticHighlightingOptions { enabled?: boolean; } @@ -143,6 +138,8 @@ function isEditStackElements(elements: IUndoRedoElement[]): elements is EditStac class DisposedModelInfo { constructor( public readonly uri: URI, + public readonly time: number, + public readonly heapSize: number, public readonly sha1: string, public readonly versionId: number, public readonly alternativeVersionId: number, @@ -151,8 +148,6 @@ class DisposedModelInfo { export class ModelServiceImpl extends Disposable implements IModelService { - private static _PROMPT_UNDO_REDO_SIZE_LIMIT = 10 * 1024 * 1024; // 10MB - public _serviceBrand: undefined; private readonly _onModelAdded: Emitter = this._register(new Emitter()); @@ -171,6 +166,7 @@ export class ModelServiceImpl extends Disposable implements IModelService { */ private readonly _models: { [modelId: string]: ModelData; }; private readonly _disposedModels: Map; + private _disposedModelsHeapSize: number; private readonly _semanticStyling: SemanticStyling; constructor( @@ -179,12 +175,12 @@ export class ModelServiceImpl extends Disposable implements IModelService { @IThemeService private readonly _themeService: IThemeService, @ILogService private readonly _logService: ILogService, @IUndoRedoService private readonly _undoRedoService: IUndoRedoService, - @IDialogService private readonly _dialogService: IDialogService, ) { super(); this._modelCreationOptionsByLanguageAndResource = Object.create(null); this._models = {}; this._disposedModels = new Map(); + this._disposedModelsHeapSize = 0; this._semanticStyling = this._register(new SemanticStyling(this._themeService, this._logService)); this._register(this._configurationService.onDidChangeConfiguration(() => this._updateModelOptions())); @@ -267,6 +263,14 @@ export class ModelServiceImpl extends Disposable implements IModelService { return platform.OS === platform.OperatingSystem.Linux || platform.OS === platform.OperatingSystem.Macintosh ? '\n' : '\r\n'; } + private _getMaxMemoryForClosedFilesUndoStack(): number { + const result = this._configurationService.getValue('files.maxMemoryForClosedFilesUndoStackMB'); + if (typeof result === 'number') { + return result * 1024 * 1024; + } + return 20 * 1024 * 1024; + } + public getCreationOptions(language: string, resource: URI | undefined, isForSimpleWidget: boolean): ITextModelCreationOptions { let creationOptions = this._modelCreationOptionsByLanguageAndResource[language + resource]; if (!creationOptions) { @@ -328,13 +332,40 @@ export class ModelServiceImpl extends Disposable implements IModelService { // --- begin IModelService + private _insertDisposedModel(disposedModelData: DisposedModelInfo): void { + this._disposedModels.set(MODEL_ID(disposedModelData.uri), disposedModelData); + this._disposedModelsHeapSize += disposedModelData.heapSize; + } + + private _removeDisposedModel(resource: URI): DisposedModelInfo | undefined { + const disposedModelData = this._disposedModels.get(MODEL_ID(resource)); + if (disposedModelData) { + this._disposedModelsHeapSize -= disposedModelData.heapSize; + } + this._disposedModels.delete(MODEL_ID(resource)); + return disposedModelData; + } + + private _ensureDisposedModelsHeapSize(maxModelsHeapSize: number): void { + if (this._disposedModelsHeapSize > maxModelsHeapSize) { + // we must remove some old undo stack elements to free up some memory + const disposedModels: DisposedModelInfo[] = []; + this._disposedModels.forEach(entry => disposedModels.push(entry)); + disposedModels.sort((a, b) => a.time - b.time); + while (disposedModels.length > 0 && this._disposedModelsHeapSize > maxModelsHeapSize) { + const disposedModel = disposedModels.shift()!; + this._removeDisposedModel(disposedModel.uri); + this._undoRedoService.removeElements(disposedModel.uri); + } + } + } + private _createModelData(value: string | ITextBufferFactory, languageIdentifier: LanguageIdentifier, resource: URI | undefined, isForSimpleWidget: boolean): ModelData { // create & save the model const options = this.getCreationOptions(languageIdentifier.language, resource, isForSimpleWidget); const model: TextModel = new TextModel(value, options, languageIdentifier, resource, this._undoRedoService); if (resource && this._disposedModels.has(MODEL_ID(resource))) { - const disposedModelData = this._disposedModels.get(MODEL_ID(resource))!; - this._disposedModels.delete(MODEL_ID(resource)); + const disposedModelData = this._removeDisposedModel(resource)!; const elements = this._undoRedoService.getElements(resource); if (computeModelSha1(model) === disposedModelData.sha1 && isEditStackPastFutureElements(elements)) { for (const element of elements.past) { @@ -473,7 +504,7 @@ export class ModelServiceImpl extends Disposable implements IModelService { const model = modelData.model; let maintainUndoRedoStack = false; let heapSize = 0; - if (MAINTAIN_UNDO_REDO_STACK && (resource.scheme === Schemas.file || resource.scheme === Schemas.vscodeRemote || resource.scheme === Schemas.userData)) { + if (resource.scheme === Schemas.file || resource.scheme === Schemas.vscodeRemote || resource.scheme === Schemas.userData) { const elements = this._undoRedoService.getElements(resource); if ((elements.past.length > 0 || elements.future.length > 0) && isEditStackPastFutureElements(elements)) { maintainUndoRedoStack = true; @@ -490,37 +521,27 @@ export class ModelServiceImpl extends Disposable implements IModelService { } } - if (maintainUndoRedoStack) { - // We only invalidate the elements, but they remain in the undo-redo service. - this._undoRedoService.setElementsIsValid(resource, false); - this._disposedModels.set(MODEL_ID(resource), new DisposedModelInfo(resource, computeModelSha1(model), model.getVersionId(), model.getAlternativeVersionId())); - } else { + if (!maintainUndoRedoStack) { this._undoRedoService.removeElements(resource); + modelData.model.dispose(); + return; } + const maxMemory = this._getMaxMemoryForClosedFilesUndoStack(); + if (heapSize > maxMemory) { + // the undo stack for this file would never fit in the configured memory, so don't bother with it. + this._undoRedoService.removeElements(resource); + modelData.model.dispose(); + return; + } + + this._ensureDisposedModelsHeapSize(maxMemory - heapSize); + + // We only invalidate the elements, but they remain in the undo-redo service. + this._undoRedoService.setElementsIsValid(resource, false); + this._insertDisposedModel(new DisposedModelInfo(resource, Date.now(), heapSize, computeModelSha1(model), model.getVersionId(), model.getAlternativeVersionId())); + modelData.model.dispose(); - - // After disposing the model, prompt and ask if we should keep the undo-redo stack - if (maintainUndoRedoStack && heapSize > ModelServiceImpl._PROMPT_UNDO_REDO_SIZE_LIMIT) { - const mbSize = (heapSize / 1024 / 1024).toFixed(1); - this._dialogService.show( - Severity.Info, - nls.localize('undoRedoConfirm', "Keep the undo-redo stack for {0} in memory ({1} MB)?", (resource.scheme === Schemas.file ? resource.fsPath : resource.path), mbSize), - [ - nls.localize('nok', "Discard"), - nls.localize('ok', "Keep"), - ], - { - cancelId: 2 - } - ).then((result) => { - const discard = (result.choice === 2 || result.choice === 0); - if (discard) { - this._disposedModels.delete(MODEL_ID(resource)); - this._undoRedoService.removeElements(resource); - } - }); - } } public getModels(): ITextModel[] { diff --git a/src/vs/editor/common/view/editorColorRegistry.ts b/src/vs/editor/common/view/editorColorRegistry.ts index 1edfd97668..52bbde9eef 100644 --- a/src/vs/editor/common/view/editorColorRegistry.ts +++ b/src/vs/editor/common/view/editorColorRegistry.ts @@ -36,6 +36,7 @@ export const editorBracketMatchBackground = registerColor('editorBracketMatch.ba export const editorBracketMatchBorder = registerColor('editorBracketMatch.border', { dark: '#888', light: '#B9B9B9', hc: contrastBorder }, nls.localize('editorBracketMatchBorder', 'Color for matching brackets boxes')); export const editorOverviewRulerBorder = registerColor('editorOverviewRuler.border', { dark: '#7f7f7f4d', light: '#7f7f7f4d', hc: '#7f7f7f4d' }, nls.localize('editorOverviewRulerBorder', 'Color of the overview ruler border.')); +export const editorOverviewRulerBackground = registerColor('editorOverviewRuler.background', null, nls.localize('editorOverviewRulerBackground', 'Background color of the editor overview ruler. Only used when the minimap is enabled and placed on the right side of the editor.')); export const editorGutter = registerColor('editorGutter.background', { dark: editorBackground, light: editorBackground, hc: editorBackground }, nls.localize('editorGutter', 'Background color of the editor gutter. The gutter contains the glyph margins and the line numbers.')); diff --git a/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts b/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts index d1d85739d1..eff45b85d4 100644 --- a/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts +++ b/src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts @@ -119,6 +119,8 @@ function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterCla let breakingOffsets: number[] = arrPool1; let breakingOffsetsVisibleColumn: number[] = arrPool2; let breakingOffsetsCount: number = 0; + let lastBreakingOffset = 0; + let lastBreakingOffsetVisibleColumn = 0; let breakingColumn = firstLineBreakColumn; const prevLen = prevBreakingOffsets.length; @@ -138,8 +140,12 @@ function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterCla while (prevIndex < prevLen) { // Allow for prevIndex to be -1 (for the case where we hit a tab when walking backwards from the first break) - const prevBreakOffset = prevIndex < 0 ? 0 : prevBreakingOffsets[prevIndex]; - const prevBreakoffsetVisibleColumn = prevIndex < 0 ? 0 : prevBreakingOffsetsVisibleColumn[prevIndex]; + let prevBreakOffset = prevIndex < 0 ? 0 : prevBreakingOffsets[prevIndex]; + let prevBreakOffsetVisibleColumn = prevIndex < 0 ? 0 : prevBreakingOffsetsVisibleColumn[prevIndex]; + if (lastBreakingOffset > prevBreakOffset) { + prevBreakOffset = lastBreakingOffset; + prevBreakOffsetVisibleColumn = lastBreakingOffsetVisibleColumn; + } let breakOffset = 0; let breakOffsetVisibleColumn = 0; @@ -148,10 +154,10 @@ function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterCla let forcedBreakOffsetVisibleColumn = 0; // initially, we search as much as possible to the right (if it fits) - if (prevBreakoffsetVisibleColumn <= breakingColumn) { - let visibleColumn = prevBreakoffsetVisibleColumn; - let prevCharCode = lineText.charCodeAt(prevBreakOffset - 1); - let prevCharCodeClass = classifier.get(prevCharCode); + if (prevBreakOffsetVisibleColumn <= breakingColumn) { + let visibleColumn = prevBreakOffsetVisibleColumn; + let prevCharCode = prevBreakOffset === 0 ? CharCode.Null : lineText.charCodeAt(prevBreakOffset - 1); + let prevCharCodeClass = prevBreakOffset === 0 ? CharacterClass.NONE : classifier.get(prevCharCode); let entireLineFits = true; for (let i = prevBreakOffset; i < len; i++) { const charStartOffset = i; @@ -169,7 +175,7 @@ function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterCla charWidth = computeCharWidth(charCode, visibleColumn, tabSize, columnsForFullWidthChar); } - if (canBreak(prevCharCode, prevCharCodeClass, charCode, charCodeClass)) { + if (charStartOffset > lastBreakingOffset && canBreak(prevCharCode, prevCharCodeClass, charCode, charCodeClass)) { breakOffset = charStartOffset; breakOffsetVisibleColumn = visibleColumn; } @@ -179,8 +185,14 @@ function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterCla // check if adding character at `i` will go over the breaking column if (visibleColumn > breakingColumn) { // We need to break at least before character at `i`: - forcedBreakOffset = charStartOffset; - forcedBreakOffsetVisibleColumn = visibleColumn - charWidth; + if (charStartOffset > lastBreakingOffset) { + forcedBreakOffset = charStartOffset; + forcedBreakOffsetVisibleColumn = visibleColumn - charWidth; + } else { + // we need to advance at least by one character + forcedBreakOffset = i + 1; + forcedBreakOffsetVisibleColumn = visibleColumn; + } if (visibleColumn - breakOffsetVisibleColumn > wrappedLineBreakColumn) { // Cannot break at `breakOffset` => reset it if it was set @@ -198,7 +210,7 @@ function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterCla if (entireLineFits) { // there is no more need to break => stop the outer loop! if (breakingOffsetsCount > 0) { - // Add last segment + // Add last segment, no need to assign to `lastBreakingOffset` and `lastBreakingOffsetVisibleColumn` breakingOffsets[breakingOffsetsCount] = prevBreakingOffsets[prevBreakingOffsets.length - 1]; breakingOffsetsVisibleColumn[breakingOffsetsCount] = prevBreakingOffsetsVisibleColumn[prevBreakingOffsets.length - 1]; breakingOffsetsCount++; @@ -209,11 +221,11 @@ function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterCla if (breakOffset === 0) { // must search left - let visibleColumn = prevBreakoffsetVisibleColumn; + let visibleColumn = prevBreakOffsetVisibleColumn; let charCode = lineText.charCodeAt(prevBreakOffset); let charCodeClass = classifier.get(charCode); let hitATabCharacter = false; - for (let i = prevBreakOffset - 1; i >= 0; i--) { + for (let i = prevBreakOffset - 1; i >= lastBreakingOffset; i--) { const charStartOffset = i + 1; const prevCharCode = lineText.charCodeAt(i); @@ -290,7 +302,9 @@ function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterCla breakOffsetVisibleColumn = forcedBreakOffsetVisibleColumn; } + lastBreakingOffset = breakOffset; breakingOffsets[breakingOffsetsCount] = breakOffset; + lastBreakingOffsetVisibleColumn = breakOffsetVisibleColumn; breakingOffsetsVisibleColumn[breakingOffsetsCount] = breakOffsetVisibleColumn; breakingOffsetsCount++; breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakColumn; diff --git a/src/vs/editor/contrib/clipboard/clipboard.css b/src/vs/editor/contrib/clipboard/clipboard.css deleted file mode 100644 index 4c398245f1..0000000000 --- a/src/vs/editor/contrib/clipboard/clipboard.css +++ /dev/null @@ -1,8 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-menu .monaco-action-bar.vertical .action-label.hover { - background-color: #EEE; -} \ No newline at end of file diff --git a/src/vs/editor/contrib/clipboard/clipboard.ts b/src/vs/editor/contrib/clipboard/clipboard.ts index 69ace7010b..6a476039f7 100644 --- a/src/vs/editor/contrib/clipboard/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/clipboard.ts @@ -3,7 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./clipboard'; import * as nls from 'vs/nls'; import * as browser from 'vs/base/browser/browser'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; diff --git a/src/vs/editor/contrib/codeAction/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/codeActionCommands.ts index 8df615a31a..f684c39bb9 100644 --- a/src/vs/editor/contrib/codeAction/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/codeActionCommands.ts @@ -39,7 +39,6 @@ function contextKeyForSupportedActions(kind: CodeActionKind) { const argsSchema: IJSONSchema = { type: 'object', - required: ['kind'], defaultSnippets: [{ body: { kind: '' } }], properties: { 'kind': { diff --git a/src/vs/editor/contrib/codeAction/lightBulbWidget.ts b/src/vs/editor/contrib/codeAction/lightBulbWidget.ts index 42f030e71c..c51d7b3162 100644 --- a/src/vs/editor/contrib/codeAction/lightBulbWidget.ts +++ b/src/vs/editor/contrib/codeAction/lightBulbWidget.ts @@ -153,12 +153,13 @@ export class LightBulbWidget extends Disposable implements IContentWidget { return this.hide(); } - const { lineNumber, column } = atPosition; const model = this._editor.getModel(); if (!model) { return this.hide(); } + const { lineNumber, column } = model.validatePosition(atPosition); + const tabSize = model.getOptions().tabSize; const fontInfo = options.get(EditorOption.fontInfo); const lineContent = model.getLineContent(lineNumber); diff --git a/src/vs/editor/contrib/format/format.ts b/src/vs/editor/contrib/format/format.ts index f186036985..a46ce2ec87 100644 --- a/src/vs/editor/contrib/format/format.ts +++ b/src/vs/editor/contrib/format/format.ts @@ -150,7 +150,7 @@ export async function formatDocumentRangeWithProvider( let cts: CancellationTokenSource; if (isCodeEditor(editorOrModel)) { model = editorOrModel.getModel(); - cts = new EditorStateCancellationTokenSource(editorOrModel, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position, token); + cts = new EditorStateCancellationTokenSource(editorOrModel, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position, undefined, token); } else { model = editorOrModel; cts = new TextModelCancellationTokenSource(editorOrModel, token); @@ -238,7 +238,7 @@ export async function formatDocumentWithProvider( let cts: CancellationTokenSource; if (isCodeEditor(editorOrModel)) { model = editorOrModel.getModel(); - cts = new EditorStateCancellationTokenSource(editorOrModel, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position, token); + cts = new EditorStateCancellationTokenSource(editorOrModel, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position, undefined, token); } else { model = editorOrModel; cts = new TextModelCancellationTokenSource(editorOrModel, token); diff --git a/src/vs/editor/contrib/gotoError/gotoError.ts b/src/vs/editor/contrib/gotoError/gotoError.ts index 480ae69d05..479e794eba 100644 --- a/src/vs/editor/contrib/gotoError/gotoError.ts +++ b/src/vs/editor/contrib/gotoError/gotoError.ts @@ -430,7 +430,7 @@ export class NextMarkerAction extends MarkerNavigationAction { id: NextMarkerAction.ID, label: NextMarkerAction.LABEL, alias: 'Go to Next Problem (Error, Warning, Info)', - precondition: EditorContextKeys.writable, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.focus, primary: KeyMod.Alt | KeyCode.F8, weight: KeybindingWeight.EditorContrib } }); } @@ -444,7 +444,7 @@ class PrevMarkerAction extends MarkerNavigationAction { id: PrevMarkerAction.ID, label: PrevMarkerAction.LABEL, alias: 'Go to Previous Problem (Error, Warning, Info)', - precondition: EditorContextKeys.writable, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.focus, primary: KeyMod.Shift | KeyMod.Alt | KeyCode.F8, weight: KeybindingWeight.EditorContrib } }); } @@ -456,7 +456,7 @@ class NextMarkerInFilesAction extends MarkerNavigationAction { id: 'editor.action.marker.nextInFiles', label: nls.localize('markerAction.nextInFiles.label', "Go to Next Problem in Files (Error, Warning, Info)"), alias: 'Go to Next Problem in Files (Error, Warning, Info)', - precondition: EditorContextKeys.writable, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.focus, primary: KeyCode.F8, @@ -472,7 +472,7 @@ class PrevMarkerInFilesAction extends MarkerNavigationAction { id: 'editor.action.marker.prevInFiles', label: nls.localize('markerAction.previousInFiles.label', "Go to Previous Problem in Files (Error, Warning, Info)"), alias: 'Go to Previous Problem in Files (Error, Warning, Info)', - precondition: EditorContextKeys.writable, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.focus, primary: KeyMod.Shift | KeyCode.F8, diff --git a/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts b/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts index 7e30db2e35..7ccaa55a7f 100644 --- a/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts +++ b/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts @@ -960,6 +960,25 @@ suite('Editor Contrib - Line Operations', () => { model.dispose(); }); + test('Indenting on empty line should move cursor', () => { + const model = createTextModel( + [ + '' + ].join('\n') + ); + + withTestCodeEditor(null, { model: model, useTabStops: false }, (editor) => { + const indentLinesAction = new IndentLinesAction(); + editor.setPosition(new Position(1, 1)); + + executeAction(indentLinesAction, editor); + assert.equal(model.getLineContent(1), ' '); + assert.deepEqual(editor.getSelection(), new Selection(1, 5, 1, 5)); + }); + + model.dispose(); + }); + test('issue #62112: Delete line does not work properly when multiple cursors are on line', () => { const TEXT = [ 'a', diff --git a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts index 2e453893ad..60ba6f5598 100644 --- a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts @@ -257,7 +257,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit // case we want to skip the container query altogether. let skipContainerQuery = false; if (symbolQuery !== query) { - [symbolScore, symbolMatches] = scoreFuzzy2(symbolLabel, { ...query, values: undefined /* disable multi-query support */ }, filterPos, symbolLabelIconOffset); + [symbolScore, symbolMatches] = scoreFuzzy2(symbolLabelWithIcon, { ...query, values: undefined /* disable multi-query support */ }, filterPos, symbolLabelIconOffset); if (typeof symbolScore === 'number') { skipContainerQuery = true; // since we consumed the query, skip any container matching } @@ -265,7 +265,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit // Otherwise: score on the symbol query and match on the container later if (typeof symbolScore !== 'number') { - [symbolScore, symbolMatches] = scoreFuzzy2(symbolLabel, symbolQuery, filterPos, symbolLabelIconOffset); + [symbolScore, symbolMatches] = scoreFuzzy2(symbolLabelWithIcon, symbolQuery, filterPos, symbolLabelIconOffset); if (typeof symbolScore !== 'number') { continue; } diff --git a/src/vs/editor/contrib/rename/rename.ts b/src/vs/editor/contrib/rename/rename.ts index 9411fdaac4..483bcbea77 100644 --- a/src/vs/editor/contrib/rename/rename.ts +++ b/src/vs/editor/contrib/rename/rename.ts @@ -168,6 +168,8 @@ class RenameController implements IEditorContribution { if (this._cts.token.isCancellationRequested) { return undefined; } + this._cts.dispose(); + this._cts = new EditorStateCancellationTokenSource(this.editor, CodeEditorStateFlag.Position | CodeEditorStateFlag.Value, loc.range); // do rename at location let selection = this.editor.getSelection(); @@ -180,7 +182,7 @@ class RenameController implements IEditorContribution { } const supportPreview = this._bulkEditService.hasPreviewHandler() && this._configService.getValue(this.editor.getModel().uri, 'editor.rename.enablePreview'); - const inputFieldResult = await this._renameInputField.getValue().getInput(loc.range, loc.text, selectionStart, selectionEnd, supportPreview); + const inputFieldResult = await this._renameInputField.getValue().getInput(loc.range, loc.text, selectionStart, selectionEnd, supportPreview, this._cts.token); // no result, only hint to focus the editor or not if (typeof inputFieldResult === 'boolean') { diff --git a/src/vs/editor/contrib/rename/renameInputField.ts b/src/vs/editor/contrib/rename/renameInputField.ts index 41834ad2cc..34c678d845 100644 --- a/src/vs/editor/contrib/rename/renameInputField.ts +++ b/src/vs/editor/contrib/rename/renameInputField.ts @@ -7,7 +7,7 @@ import 'vs/css!./renameInputField'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { Position } from 'vs/editor/common/core/position'; -import { IRange, Range } from 'vs/editor/common/core/range'; +import { IRange } from 'vs/editor/common/core/range'; import { ScrollType } from 'vs/editor/common/editorCommon'; import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -16,6 +16,7 @@ import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeServic 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); @@ -149,7 +150,7 @@ export class RenameInputField implements IContentWidget { } } - getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number, supportPreview: boolean): Promise { + getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number, supportPreview: boolean, token: CancellationToken): Promise { toggleClass(this._domNode!, 'preview', supportPreview); @@ -185,14 +186,7 @@ export class RenameInputField implements IContentWidget { }); }; - let onCursorChanged = () => { - const editorPosition = this._editor.getPosition(); - if (!editorPosition || !Range.containsPosition(where, editorPosition)) { - this.cancelInput(true); - } - }; - - disposeOnDone.add(this._editor.onDidChangeCursorSelection(onCursorChanged)); + token.onCancellationRequested(() => this.cancelInput(true)); disposeOnDone.add(this._editor.onDidBlurEditorWidget(() => this.cancelInput(false))); this._show(); diff --git a/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts b/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts index b8fa783737..b3b76fa425 100644 --- a/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts +++ b/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts @@ -51,7 +51,7 @@ suite('SmartSelect', () => { setup(() => { const configurationService = new TestConfigurationService(); const dialogService = new TestDialogService(); - modelService = new ModelServiceImpl(configurationService, new TestTextResourcePropertiesService(configurationService), new TestThemeService(), new NullLogService(), new UndoRedoService(dialogService, new TestNotificationService()), dialogService); + modelService = new ModelServiceImpl(configurationService, new TestTextResourcePropertiesService(configurationService), new TestThemeService(), new NullLogService(), new UndoRedoService(dialogService, new TestNotificationService())); mode = new MockJSMode(); }); diff --git a/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts b/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts index 69bf93cc24..a0949ffe26 100644 --- a/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts +++ b/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts @@ -18,7 +18,7 @@ import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { IModelDeltaDecoration, ITextModel, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { IModelDeltaDecoration, ITextModel, OverviewRulerLane, TrackedRangeStickiness, IWordAtPosition } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { DocumentHighlight, DocumentHighlightKind, DocumentHighlightProviderRegistry } from 'vs/editor/common/modes'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -27,6 +27,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { activeContrastBorder, editorSelectionHighlight, editorSelectionHighlightBorder, overviewRulerSelectionHighlightForeground, registerColor } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant, themeColorFromId } from 'vs/platform/theme/common/themeService'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { alert } from 'vs/base/browser/ui/aria/aria'; const editorWordHighlight = registerColor('editor.wordHighlightBackground', { dark: '#575757B8', light: '#57575740', hc: null }, nls.localize('wordHighlight', 'Background color of a symbol during read-access, like reading a variable. The color must not be opaque so as not to hide underlying decorations.'), true); const editorWordHighlightStrong = registerColor('editor.wordHighlightStrongBackground', { dark: '#004972B8', light: '#0e639c40', hc: null }, nls.localize('wordHighlightStrong', 'Background color of a symbol during write-access, like writing to a variable. The color must not be opaque so as not to hide underlying decorations.'), true); @@ -245,6 +246,11 @@ class WordHighlighter { this._ignorePositionChangeEvent = true; this.editor.setPosition(dest.getStartPosition()); this.editor.revealRangeInCenterIfOutsideViewport(dest); + const word = this._getWord(); + if (word) { + const lineContent = this.editor.getModel().getLineContent(dest.startLineNumber); + alert(`${lineContent}, ${newIndex + 1} of ${highlights.length} for '${word.word}'`); + } } finally { this._ignorePositionChangeEvent = false; } @@ -259,6 +265,11 @@ class WordHighlighter { this._ignorePositionChangeEvent = true; this.editor.setPosition(dest.getStartPosition()); this.editor.revealRangeInCenterIfOutsideViewport(dest); + const word = this._getWord(); + if (word) { + const lineContent = this.editor.getModel().getLineContent(dest.startLineNumber); + alert(`${lineContent}, ${newIndex + 1} of ${highlights.length} for '${word.word}'`); + } } finally { this._ignorePositionChangeEvent = false; } @@ -312,6 +323,17 @@ class WordHighlighter { this._run(); } + private _getWord(): IWordAtPosition | null { + let editorSelection = this.editor.getSelection(); + let lineNumber = editorSelection.startLineNumber; + let startColumn = editorSelection.startColumn; + + return this.model.getWordAtPosition({ + lineNumber: lineNumber, + column: startColumn + }); + } + private _run(): void { let editorSelection = this.editor.getSelection(); @@ -321,14 +343,10 @@ class WordHighlighter { return; } - let lineNumber = editorSelection.startLineNumber; let startColumn = editorSelection.startColumn; let endColumn = editorSelection.endColumn; - let word = this.model.getWordAtPosition({ - lineNumber: lineNumber, - column: startColumn - }); + const word = this._getWord(); // The selection must be inside a word or surround one word at most if (!word || word.startColumn > startColumn || word.endColumn < endColumn) { @@ -465,20 +483,20 @@ class WordHighlighterContribution extends Disposable implements IEditorContribut return editor.getContribution(WordHighlighterContribution.ID); } - private wordHighligher: WordHighlighter | null; + private wordHighlighter: WordHighlighter | null; constructor(editor: ICodeEditor, @IContextKeyService contextKeyService: IContextKeyService) { super(); - this.wordHighligher = null; + this.wordHighlighter = null; const createWordHighlighterIfPossible = () => { if (editor.hasModel()) { - this.wordHighligher = new WordHighlighter(editor, contextKeyService); + this.wordHighlighter = new WordHighlighter(editor, contextKeyService); } }; this._register(editor.onDidChangeModel((e) => { - if (this.wordHighligher) { - this.wordHighligher.dispose(); - this.wordHighligher = null; + if (this.wordHighlighter) { + this.wordHighlighter.dispose(); + this.wordHighlighter = null; } createWordHighlighterIfPossible(); })); @@ -486,34 +504,34 @@ class WordHighlighterContribution extends Disposable implements IEditorContribut } public saveViewState(): boolean { - if (this.wordHighligher && this.wordHighligher.hasDecorations()) { + if (this.wordHighlighter && this.wordHighlighter.hasDecorations()) { return true; } return false; } public moveNext() { - if (this.wordHighligher) { - this.wordHighligher.moveNext(); + if (this.wordHighlighter) { + this.wordHighlighter.moveNext(); } } public moveBack() { - if (this.wordHighligher) { - this.wordHighligher.moveBack(); + if (this.wordHighlighter) { + this.wordHighlighter.moveBack(); } } public restoreViewState(state: boolean | undefined): void { - if (this.wordHighligher && state) { - this.wordHighligher.restore(); + if (this.wordHighlighter && state) { + this.wordHighlighter.restore(); } } public dispose(): void { - if (this.wordHighligher) { - this.wordHighligher.dispose(); - this.wordHighligher = null; + if (this.wordHighlighter) { + this.wordHighlighter.dispose(); + this.wordHighlighter = null; } super.dispose(); } diff --git a/src/vs/editor/editor.api.ts b/src/vs/editor/editor.api.ts index c3453f796c..01efb11aef 100644 --- a/src/vs/editor/editor.api.ts +++ b/src/vs/editor/editor.api.ts @@ -41,6 +41,8 @@ if (typeof global.require !== 'undefined' && typeof global.require.config === 'f ignoreDuplicateModules: [ 'vscode-languageserver-types', 'vscode-languageserver-types/main', + 'vscode-languageserver-textdocument', + 'vscode-languageserver-textdocument/main', 'vscode-nls', 'vscode-nls/vscode-nls', 'jsonc-parser', diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index d06e66d61c..9cd119512b 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -560,6 +560,14 @@ export class StandaloneTelemetryService implements ITelemetryService { return this.publicLog(eventName, data as any); } + public publicLogError(eventName: string, data?: any): Promise { + return Promise.resolve(undefined); + } + + publicLogError2 = never, T extends GDPRClassification = never>(eventName: string, data?: StrictPropertyCheck) { + return this.publicLogError(eventName, data as any); + } + public getTelemetryInfo(): Promise { throw new Error(`Not available`); } diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index f8e0608e6c..ad6e4d1421 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -157,7 +157,7 @@ export module StaticServices { export const undoRedoService = define(IUndoRedoService, (o) => new UndoRedoService(dialogService.get(o), notificationService.get(o))); - export const modelService = define(IModelService, (o) => new ModelServiceImpl(configurationService.get(o), resourcePropertiesService.get(o), standaloneThemeService.get(o), logService.get(o), undoRedoService.get(o), dialogService.get(o))); + export const modelService = define(IModelService, (o) => new ModelServiceImpl(configurationService.get(o), resourcePropertiesService.get(o), standaloneThemeService.get(o), logService.get(o), undoRedoService.get(o))); export const markerDecorationsService = define(IMarkerDecorationsService, (o) => new MarkerDecorationsService(modelService.get(o), markerService.get(o))); diff --git a/src/vs/editor/standalone/common/monarch/monarchLexer.ts b/src/vs/editor/standalone/common/monarch/monarchLexer.ts index a5aa847402..cbbe9a1620 100644 --- a/src/vs/editor/standalone/common/monarch/monarchLexer.ts +++ b/src/vs/editor/standalone/common/monarch/monarchLexer.ts @@ -740,6 +740,24 @@ export class MonarchTokenizer implements modes.ITokenizationSupport { throw monarchCommon.createError(this._lexer, 'lexer rule has no well-defined action in rule: ' + this._safeRuleName(rule)); } + const computeNewStateForEmbeddedMode = (enteringEmbeddedMode: string) => { + // substitute language alias to known modes to support syntax highlighting + let enteringEmbeddedModeId = this._modeService.getModeIdForLanguageName(enteringEmbeddedMode); + if (enteringEmbeddedModeId) { + enteringEmbeddedMode = enteringEmbeddedModeId; + } + + const embeddedModeData = this._getNestedEmbeddedModeData(enteringEmbeddedMode); + + if (pos < lineLength) { + // there is content from the embedded mode on this line + const restOfLine = line.substr(pos); + return this._nestedTokenize(restOfLine, MonarchLineStateFactory.create(stack, embeddedModeData), offsetDelta + pos, tokensCollector); + } else { + return MonarchLineStateFactory.create(stack, embeddedModeData); + } + }; + // is the result a group match? if (Array.isArray(result)) { if (groupMatching && groupMatching.groups.length > 0) { @@ -780,6 +798,12 @@ export class MonarchTokenizer implements modes.ITokenizationSupport { matched = ''; // better set the next state too.. matches = null; result = ''; + + // Even though `@rematch` was specified, if `nextEmbedded` also specified, + // a state transition should occur. + if (enteringEmbeddedMode !== null) { + return computeNewStateForEmbeddedMode(enteringEmbeddedMode); + } } // check progress @@ -810,21 +834,7 @@ export class MonarchTokenizer implements modes.ITokenizationSupport { } if (enteringEmbeddedMode !== null) { - // substitute language alias to known modes to support syntax highlighting - let enteringEmbeddedModeId = this._modeService.getModeIdForLanguageName(enteringEmbeddedMode); - if (enteringEmbeddedModeId) { - enteringEmbeddedMode = enteringEmbeddedModeId; - } - - let embeddedModeData = this._getNestedEmbeddedModeData(enteringEmbeddedMode); - - if (pos < lineLength) { - // there is content from the embedded mode on this line - let restOfLine = line.substr(pos); - return this._nestedTokenize(restOfLine, MonarchLineStateFactory.create(stack, embeddedModeData), offsetDelta + pos, tokensCollector); - } else { - return MonarchLineStateFactory.create(stack, embeddedModeData); - } + return computeNewStateForEmbeddedMode(enteringEmbeddedMode); } } diff --git a/src/vs/editor/standalone/common/monarch/monarchTypes.ts b/src/vs/editor/standalone/common/monarch/monarchTypes.ts index fd61402f13..8fa873c72b 100644 --- a/src/vs/editor/standalone/common/monarch/monarchTypes.ts +++ b/src/vs/editor/standalone/common/monarch/monarchTypes.ts @@ -44,9 +44,9 @@ export interface IMonarchLanguage { * shorthands: [reg,act] == { regex: reg, action: act} * and : [reg,act,nxt] == { regex: reg, action: act{ next: nxt }} */ -export type IShortMonarchLanguageRule1 = [RegExp, IMonarchLanguageAction]; +export type IShortMonarchLanguageRule1 = [string | RegExp, IMonarchLanguageAction]; -export type IShortMonarchLanguageRule2 = [RegExp, IMonarchLanguageAction, string]; +export type IShortMonarchLanguageRule2 = [string | RegExp, IMonarchLanguageAction, string]; export interface IExpandedMonarchLanguageRule { /** @@ -135,4 +135,4 @@ export interface IMonarchLanguageBracket { * token class */ token: string; -} \ No newline at end of file +} diff --git a/src/vs/editor/standalone/test/monarch/monarch.test.ts b/src/vs/editor/standalone/test/monarch/monarch.test.ts new file mode 100644 index 0000000000..a6e9956dab --- /dev/null +++ b/src/vs/editor/standalone/test/monarch/monarch.test.ts @@ -0,0 +1,106 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { MonarchTokenizer } from 'vs/editor/standalone/common/monarch/monarchLexer'; +import { compile } from 'vs/editor/standalone/common/monarch/monarchCompile'; +import { Token } from 'vs/editor/common/core/token'; +import { TokenizationRegistry } from 'vs/editor/common/modes'; +import { IMonarchLanguage } from 'vs/editor/standalone/common/monarch/monarchTypes'; +import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; + +suite('Monarch', () => { + + function createMonarchTokenizer(modeService: IModeService, languageId: string, language: IMonarchLanguage): MonarchTokenizer { + return new MonarchTokenizer(modeService, null!, languageId, compile(languageId, language)); + } + + test('Ensure @rematch and nextEmbedded can be used together in Monarch grammar', () => { + const modeService = new ModeServiceImpl(); + const innerModeRegistration = ModesRegistry.registerLanguage({ + id: 'sql' + }); + const innerModeTokenizationRegistration = TokenizationRegistry.register('sql', createMonarchTokenizer(modeService, 'sql', { + tokenizer: { + root: [ + [/./, 'token'] + ] + } + })); + const SQL_QUERY_START = '(SELECT|INSERT|UPDATE|DELETE|CREATE|REPLACE|ALTER|WITH)'; + const tokenizer = createMonarchTokenizer(modeService, 'test1', { + tokenizer: { + root: [ + [`(\"\"\")${SQL_QUERY_START}`, [{ 'token': 'string.quote', }, { token: '@rematch', next: '@endStringWithSQL', nextEmbedded: 'sql', },]], + [/(""")$/, [{ token: 'string.quote', next: '@maybeStringIsSQL', },]], + ], + maybeStringIsSQL: [ + [/(.*)/, { + cases: { + [`${SQL_QUERY_START}\\b.*`]: { token: '@rematch', next: '@endStringWithSQL', nextEmbedded: 'sql', }, + '@default': { token: '@rematch', switchTo: '@endDblDocString', }, + } + }], + ], + endDblDocString: [ + ['[^\']+', 'string'], + ['\\\\\'', 'string'], + ['\'\'\'', 'string', '@popall'], + ['\'', 'string'] + ], + endStringWithSQL: [[/"""/, { token: 'string.quote', next: '@popall', nextEmbedded: '@pop', },]], + } + }); + + const lines = [ + `mysql_query("""SELECT * FROM table_name WHERE ds = ''""")`, + `mysql_query("""`, + `SELECT *`, + `FROM table_name`, + `WHERE ds = ''`, + `""")`, + ]; + + const actualTokens: Token[][] = []; + let state = tokenizer.getInitialState(); + for (const line of lines) { + const result = tokenizer.tokenize(line, state, 0); + actualTokens.push(result.tokens); + state = result.endState; + } + + assert.deepEqual(actualTokens, [ + [ + { 'offset': 0, 'type': 'source.test1', 'language': 'test1' }, + { 'offset': 12, 'type': 'string.quote.test1', 'language': 'test1' }, + { 'offset': 15, 'type': 'token.sql', 'language': 'sql' }, + { 'offset': 61, 'type': 'string.quote.test1', 'language': 'test1' }, + { 'offset': 64, 'type': 'source.test1', 'language': 'test1' } + ], + [ + { 'offset': 0, 'type': 'source.test1', 'language': 'test1' }, + { 'offset': 12, 'type': 'string.quote.test1', 'language': 'test1' } + ], + [ + { 'offset': 0, 'type': 'token.sql', 'language': 'sql' } + ], + [ + { 'offset': 0, 'type': 'token.sql', 'language': 'sql' } + ], + [ + { 'offset': 0, 'type': 'token.sql', 'language': 'sql' } + ], + [ + { 'offset': 0, 'type': 'string.quote.test1', 'language': 'test1' }, + { 'offset': 3, 'type': 'source.test1', 'language': 'test1' } + ] + ]); + innerModeTokenizationRegistration.dispose(); + innerModeRegistration.dispose(); + }); + +}); diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index 17598ea4dc..090afc5200 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -1428,6 +1428,28 @@ suite('Editor Controller - Regression tests', () => { model.dispose(); }); + test('issue #95591: Unindenting moves cursor to beginning of line', () => { + let model = createTextModel( + [ + ' ' + ].join('\n') + ); + + withTestCodeEditor(null, { + model: model, + useTabStops: false + }, (editor, cursor) => { + moveTo(cursor, 1, 9, false); + assertCursor(cursor, new Selection(1, 9, 1, 9)); + + CoreEditingCommands.Outdent.runEditorCommand(null, editor, null); + assert.equal(model.getLineContent(1), ' '); + assertCursor(cursor, new Selection(1, 5, 1, 5)); + }); + + model.dispose(); + }); + test('Bug #16657: [editor] Tab on empty line of zero indentation moves cursor to position (1,1)', () => { let model = createTextModel( [ diff --git a/src/vs/editor/test/common/model/tokensStore.test.ts b/src/vs/editor/test/common/model/tokensStore.test.ts index 68da0772a1..f7a21b707e 100644 --- a/src/vs/editor/test/common/model/tokensStore.test.ts +++ b/src/vs/editor/test/common/model/tokensStore.test.ts @@ -8,7 +8,7 @@ import { MultilineTokens2, SparseEncodedTokens, TokensStore2 } from 'vs/editor/c import { Range } from 'vs/editor/common/core/range'; import { TextModel } from 'vs/editor/common/model/textModel'; import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; -import { MetadataConsts, TokenMetadata } from 'vs/editor/common/modes'; +import { MetadataConsts, TokenMetadata, FontStyle } from 'vs/editor/common/modes'; import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { LineTokens } from 'vs/editor/common/core/lineTokens'; @@ -387,4 +387,50 @@ suite('TokensStore', () => { const lineTokens = store.addSemanticTokens(36451, new LineTokens(new Uint32Array([60, 1]), ` if (flags & ModifierFlags.Ambient) {`)); assert.equal(lineTokens.getCount(), 7); }); + + + test('issue #95949: Identifiers are colored in bold when targetting keywords', () => { + + function createTMMetadata(foreground: number, fontStyle: number, languageId: number): number { + return ( + (languageId << MetadataConsts.LANGUAGEID_OFFSET) + | (fontStyle << MetadataConsts.FONT_STYLE_OFFSET) + | (foreground << MetadataConsts.FOREGROUND_OFFSET) + ) >>> 0; + } + + function toArr(lineTokens: LineTokens): number[] { + let r: number[] = []; + for (let i = 0; i < lineTokens.getCount(); i++) { + r.push(lineTokens.getEndOffset(i)); + r.push(lineTokens.getMetadata(i)); + } + return r; + } + + const store = new TokensStore2(); + + store.set([ + new MultilineTokens2(1, new SparseEncodedTokens(new Uint32Array([ + 0, 6, 11, (1 << MetadataConsts.FOREGROUND_OFFSET) | MetadataConsts.SEMANTIC_USE_FOREGROUND, + ]))) + ], true); + + const lineTokens = store.addSemanticTokens(1, new LineTokens(new Uint32Array([ + 5, createTMMetadata(5, FontStyle.Bold, 53), + 14, createTMMetadata(1, FontStyle.None, 53), + 17, createTMMetadata(6, FontStyle.None, 53), + 18, createTMMetadata(1, FontStyle.None, 53), + ]), `const hello = 123;`)); + + const actual = toArr(lineTokens); + assert.deepEqual(actual, [ + 5, createTMMetadata(5, FontStyle.Bold, 53), + 6, createTMMetadata(1, FontStyle.None, 53), + 11, createTMMetadata(1, FontStyle.None, 53), + 14, createTMMetadata(1, FontStyle.None, 53), + 17, createTMMetadata(6, FontStyle.None, 53), + 18, createTMMetadata(1, FontStyle.None, 53) + ]); + }); }); diff --git a/src/vs/editor/test/common/services/modelService.test.ts b/src/vs/editor/test/common/services/modelService.test.ts index 9636386948..a3123447ca 100644 --- a/src/vs/editor/test/common/services/modelService.test.ts +++ b/src/vs/editor/test/common/services/modelService.test.ts @@ -13,7 +13,7 @@ import { Selection } from 'vs/editor/common/core/selection'; import { createStringBuilder } from 'vs/editor/common/core/stringBuilder'; import { DefaultEndOfLine } from 'vs/editor/common/model'; import { createTextBuffer } from 'vs/editor/common/model/textModel'; -import { ModelServiceImpl, MAINTAIN_UNDO_REDO_STACK } from 'vs/editor/common/services/modelServiceImpl'; +import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; @@ -35,7 +35,7 @@ suite('ModelService', () => { configService.setUserConfiguration('files', { 'eol': '\r\n' }, URI.file(platform.isWindows ? 'c:\\myroot' : '/myroot')); const dialogService = new TestDialogService(); - modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService(), new UndoRedoService(dialogService, new TestNotificationService()), dialogService); + modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService(), new UndoRedoService(dialogService, new TestNotificationService())); }); teardown(() => { @@ -310,44 +310,42 @@ suite('ModelService', () => { assertComputeEdits(file1, file2); }); - if (MAINTAIN_UNDO_REDO_STACK) { - test('maintains undo for same resource and same content', () => { - const resource = URI.parse('file://test.txt'); + test('maintains undo for same resource and same content', () => { + const resource = URI.parse('file://test.txt'); - // create a model - const model1 = modelService.createModel('text', null, resource); - // make an edit - model1.pushEditOperations(null, [{ range: new Range(1, 5, 1, 5), text: '1' }], () => [new Selection(1, 5, 1, 5)]); - assert.equal(model1.getValue(), 'text1'); - // dispose it - modelService.destroyModel(resource); + // create a model + const model1 = modelService.createModel('text', null, resource); + // make an edit + model1.pushEditOperations(null, [{ range: new Range(1, 5, 1, 5), text: '1' }], () => [new Selection(1, 5, 1, 5)]); + assert.equal(model1.getValue(), 'text1'); + // dispose it + modelService.destroyModel(resource); - // create a new model with the same content - const model2 = modelService.createModel('text1', null, resource); - // undo - model2.undo(); - assert.equal(model2.getValue(), 'text'); - }); + // create a new model with the same content + const model2 = modelService.createModel('text1', null, resource); + // undo + model2.undo(); + assert.equal(model2.getValue(), 'text'); + }); - test('maintains version id and alternative version id for same resource and same content', () => { - const resource = URI.parse('file://test.txt'); + test('maintains version id and alternative version id for same resource and same content', () => { + const resource = URI.parse('file://test.txt'); - // create a model - const model1 = modelService.createModel('text', null, resource); - // make an edit - model1.pushEditOperations(null, [{ range: new Range(1, 5, 1, 5), text: '1' }], () => [new Selection(1, 5, 1, 5)]); - assert.equal(model1.getValue(), 'text1'); - const versionId = model1.getVersionId(); - const alternativeVersionId = model1.getAlternativeVersionId(); - // dispose it - modelService.destroyModel(resource); + // create a model + const model1 = modelService.createModel('text', null, resource); + // make an edit + model1.pushEditOperations(null, [{ range: new Range(1, 5, 1, 5), text: '1' }], () => [new Selection(1, 5, 1, 5)]); + assert.equal(model1.getValue(), 'text1'); + const versionId = model1.getVersionId(); + const alternativeVersionId = model1.getAlternativeVersionId(); + // dispose it + modelService.destroyModel(resource); - // create a new model with the same content - const model2 = modelService.createModel('text1', null, resource); - assert.equal(model2.getVersionId(), versionId); - assert.equal(model2.getAlternativeVersionId(), alternativeVersionId); - }); - } + // create a new model with the same content + const model2 = modelService.createModel('text1', null, resource); + assert.equal(model2.getVersionId(), versionId); + assert.equal(model2.getAlternativeVersionId(), alternativeVersionId); + }); test('does not maintain undo for same resource and different content', () => { const resource = URI.parse('file://test.txt'); diff --git a/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts b/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts index db65ff8839..76f74464f4 100644 --- a/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts +++ b/src/vs/editor/test/common/viewModel/monospaceLineBreaksComputer.test.ts @@ -147,7 +147,7 @@ suite('Editor ViewModel - MonospaceLineBreaksComputer', () => { test('MonospaceLineBreaksComputer incremental 1', () => { - let factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue); + const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue); assertIncrementalLineBreaks( factory, 'just some text and more', 4, @@ -203,6 +203,17 @@ suite('Editor ViewModel - MonospaceLineBreaksComputer', () => { ); }); + test('issue #95686: CRITICAL: loop forever on the monospaceLineBreaksComputer', () => { + const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue); + assertIncrementalLineBreaks( + factory, + ' ', + 4, + 179, ' ', + 1, ' | | | | | |<|t|r| |d|m|x|-|c|l|a|s|s|:|t|a|b|l|e|-|d|a|n|g|e|r|=|"|(|a|l|t| |<|=| |5|0|)|"| |d|m|x|-|c|l|a|s|s|:|t|a|b|l|e|-|w|a|r|n|i|n|g|=|"|(|a|l|t| |<|=| |2|0|0|)|"| |d|m|x|-|c|l|a|s|s|:|t|a|b|l|e|-|p|r|i|m|a|r|y|=|"|(|a|l|t| |<|=| |4|0|0|)|"| |d|m|x|-|c|l|a|s|s|:|t|a|b|l|e|-|i|n|f|o|=|"|(|a|l|t| |<|=| |8|0|0|)|"| |d|m|x|-|c|l|a|s|s|:|t|a|b|l|e|-|s|u|c|c|e|s|s|=|"|(|a|l|t| |>|=| |4|0|0|)|"|>', + WrappingIndent.Same + ); + }); test('MonospaceLineBreaksComputer - CJK and Kinsoku Shori', () => { let factory = new MonospaceLineBreaksComputerFactory('(', '\t)'); diff --git a/src/vs/loader.js b/src/vs/loader.js index 94ce87feca..3766be1e0b 100644 --- a/src/vs/loader.js +++ b/src/vs/loader.js @@ -1491,6 +1491,10 @@ var AMDLoader; result.getStats = function () { return _this.getLoaderEvents(); }; + result.config = function (params, shouldOverwrite) { + if (shouldOverwrite === void 0) { shouldOverwrite = false; } + _this.configure(params, shouldOverwrite); + }; result.__$__nodeRequire = AMDLoader.global.nodeRequire; return result; }; diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 9b911d0415..05777b49f2 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -6302,9 +6302,9 @@ declare namespace monaco.languages { * shorthands: [reg,act] == { regex: reg, action: act} * and : [reg,act,nxt] == { regex: reg, action: act{ next: nxt }} */ - export type IShortMonarchLanguageRule1 = [RegExp, IMonarchLanguageAction]; + export type IShortMonarchLanguageRule1 = [string | RegExp, IMonarchLanguageAction]; - export type IShortMonarchLanguageRule2 = [RegExp, IMonarchLanguageAction, string]; + export type IShortMonarchLanguageRule2 = [string | RegExp, IMonarchLanguageAction, string]; export interface IExpandedMonarchLanguageRule { /** diff --git a/src/vs/platform/authentication/common/authentication.ts b/src/vs/platform/authentication/common/authentication.ts index 33a1c08b1d..b36338dda1 100644 --- a/src/vs/platform/authentication/common/authentication.ts +++ b/src/vs/platform/authentication/common/authentication.ts @@ -53,6 +53,7 @@ export class AuthenticationTokenService extends Disposable implements IAuthentic } sendTokenFailed(): void { + this.setToken(undefined); this._onTokenFailed.fire(); } } diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index 20ae960d1c..d7f53d740a 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -328,6 +328,11 @@ class ConfigurationRegistry implements IConfigurationRegistry { this.configurationProperties[key] = properties[key]; } + if (!properties[key].deprecationMessage && properties[key].markdownDeprecationMessage) { + // If not set, default deprecationMessage to the markdown source + properties[key].deprecationMessage = properties[key].markdownDeprecationMessage; + } + propertyKeys.push(key); } } diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index a5f9fa3c20..4f9f165b9a 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -36,6 +36,7 @@ export interface ParsedArgs { log?: string; logExtensionHostCommunication?: boolean; 'extensions-dir'?: string; + 'extensions-download-dir'?: string; 'builtin-extensions-dir'?: string; extensionDevelopmentPath?: string[]; // // undefined or array of 1 or more local paths or URIs extensionTestsPath?: string; // either a local path or a URI @@ -54,7 +55,7 @@ export interface ParsedArgs { 'locate-extension'?: string[]; // undefined or array of 1 or more 'enable-proposed-api'?: string[]; // undefined or array of 1 or more 'open-url'?: boolean; - 'skip-getting-started'?: boolean; + 'skip-release-notes'?: boolean; 'disable-restore-windows'?: boolean; 'disable-telemetry'?: boolean; 'export-default-configuration'?: string; @@ -143,6 +144,7 @@ export const OPTIONS: OptionDescriptions> = { 'help': { type: 'boolean', cat: 'o', alias: 'h', description: localize('help', "Print usage.") }, 'extensions-dir': { type: 'string', deprecates: 'extensionHomePath', cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") }, + 'extensions-download-dir': { type: 'string' }, 'builtin-extensions-dir': { type: 'string' }, 'list-extensions': { type: 'boolean', cat: 'e', description: localize('listExtensions', "List the installed extensions.") }, 'show-versions': { type: 'boolean', cat: 'e', description: localize('showVersions', "Show versions of installed extensions, when using --list-extension.") }, @@ -179,7 +181,7 @@ export const OPTIONS: OptionDescriptions> = { 'install-source': { type: 'string' }, 'driver': { type: 'string' }, 'logExtensionHostCommunication': { type: 'boolean' }, - 'skip-getting-started': { type: 'boolean' }, + 'skip-release-notes': { type: 'boolean' }, 'disable-restore-windows': { type: 'boolean' }, 'disable-telemetry': { type: 'boolean' }, 'disable-updates': { type: 'boolean' }, @@ -265,23 +267,25 @@ export function parseArgs(args: string[], options: OptionDescriptions, err const parsedArgs = minimist(args, { string, boolean, alias }); const cleanedArgs: any = {}; + const remainingArgs: any = parsedArgs; // https://github.com/microsoft/vscode/issues/58177 cleanedArgs._ = parsedArgs._.filter(arg => arg.length > 0); - delete parsedArgs._; + + delete remainingArgs._; for (let optionId in options) { const o = options[optionId]; if (o.alias) { - delete parsedArgs[o.alias]; + delete remainingArgs[o.alias]; } - let val = parsedArgs[optionId]; - if (o.deprecates && parsedArgs.hasOwnProperty(o.deprecates)) { + let val = remainingArgs[optionId]; + if (o.deprecates && remainingArgs.hasOwnProperty(o.deprecates)) { if (!val) { - val = parsedArgs[o.deprecates]; + val = remainingArgs[o.deprecates]; } - delete parsedArgs[o.deprecates]; + delete remainingArgs[o.deprecates]; } if (typeof val !== 'undefined') { @@ -297,10 +301,10 @@ export function parseArgs(args: string[], options: OptionDescriptions, err } cleanedArgs[optionId] = val; } - delete parsedArgs[optionId]; + delete remainingArgs[optionId]; } - for (let key in parsedArgs) { + for (let key in remainingArgs) { errorReporter.onUnknownOption(key); } diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index d53cd396cb..0a28995113 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -36,6 +36,7 @@ export interface INativeEnvironmentService extends IEnvironmentService { installSourcePath: string; extensionsPath?: string; + extensionsDownloadPath?: string; builtinExtensionsPath: string; globalStorageHome: string; @@ -150,6 +151,10 @@ export class EnvironmentService implements INativeEnvironmentService { } } + get extensionsDownloadPath(): string | undefined { + return parsePathArg(this._args['extensions-download-dir'], process); + } + @memoize get extensionsPath(): string { const fromArgs = parsePathArg(this._args['extensions-dir'], process); diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 80cb767ffb..6e8a85622b 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -13,7 +13,6 @@ import { IRequestService, asJson, asText } from 'vs/platform/request/common/requ import { IRequestOptions, IRequestContext, IHeaders } from 'vs/base/parts/request/common/request'; import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { generateUuid } from 'vs/base/common/uuid'; import { values } from 'vs/base/common/map'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; // {{SQL CARBON EDIT}} import { CancellationToken } from 'vs/base/common/cancellation'; @@ -21,7 +20,6 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IExtensionManifest, ExtensionsPolicy, ExtensionsPolicyKey } from 'vs/platform/extensions/common/extensions'; // {{SQL CARBON EDIT}} add imports import { IFileService } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; -import { joinPath } from 'vs/base/common/resources'; import { IProductService } from 'vs/platform/product/common/productService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { find } from 'vs/base/common/arrays'; @@ -704,9 +702,8 @@ export class ExtensionGalleryService implements IExtensionGalleryService { }); } - download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise { + download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise { this.logService.trace('ExtensionGalleryService#download', extension.identifier.id); - const zip = joinPath(location, generateUuid()); const data = getGalleryExtensionTelemetryData(extension); const startTime = new Date().getTime(); /* __GDPR__ @@ -728,9 +725,8 @@ export class ExtensionGalleryService implements IExtensionGalleryService { } : extension.assets.download; return this.getAsset(downloadAsset) - .then(context => this.fileService.writeFile(zip, context.stream)) - .then(() => log(new Date().getTime() - startTime)) - .then(() => zip); + .then(context => this.fileService.writeFile(location, context.stream)) + .then(() => log(new Date().getTime() - startTime)); } getReadme(extension: IGalleryExtension, token: CancellationToken): Promise { diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index b2460eb3fd..0984d702fb 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -155,7 +155,7 @@ export interface IExtensionGalleryService { isEnabled(): boolean; query(token: CancellationToken): Promise>; query(options: IQueryOptions, token: CancellationToken): Promise>; - download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise; + download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise; reportStatistic(publisher: string, name: string, version: string, type: StatisticType): Promise; getReadme(extension: IGalleryExtension, token: CancellationToken): Promise; getManifest(extension: IGalleryExtension, token: CancellationToken): Promise; diff --git a/src/vs/platform/extensionManagement/node/extensionDownloader.ts b/src/vs/platform/extensionManagement/node/extensionDownloader.ts new file mode 100644 index 0000000000..f7e05a2f0d --- /dev/null +++ b/src/vs/platform/extensionManagement/node/extensionDownloader.ts @@ -0,0 +1,104 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { tmpdir } from 'os'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IFileService, IFileStatWithMetadata } from 'vs/platform/files/common/files'; +import { IExtensionGalleryService, IGalleryExtension, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { URI } from 'vs/base/common/uri'; +import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { joinPath } from 'vs/base/common/resources'; +import { ExtensionIdentifierWithVersion, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { ILogService } from 'vs/platform/log/common/log'; +import { generateUuid } from 'vs/base/common/uuid'; +import * as semver from 'semver-umd'; + +const ExtensionIdVersionRegex = /^([^.]+\..+)-(\d+\.\d+\.\d+)$/; + +export class ExtensionsDownloader extends Disposable { + + private readonly extensionsDownloadDir: URI = URI.file(tmpdir()); + private readonly cache: number = 0; + private readonly cleanUpPromise: Promise = Promise.resolve(); + + constructor( + @IEnvironmentService environmentService: INativeEnvironmentService, + @IFileService private readonly fileService: IFileService, + @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, + @ILogService private readonly logService: ILogService, + ) { + super(); + if (environmentService.extensionsDownloadPath) { + this.extensionsDownloadDir = URI.file(environmentService.extensionsDownloadPath); + this.cache = 20; // Cache 20 downloads + this.cleanUpPromise = this.cleanUp(); + } + } + + async downloadExtension(extension: IGalleryExtension, operation: InstallOperation): Promise { + await this.cleanUpPromise; + const location = joinPath(this.extensionsDownloadDir, this.getName(extension)); + await this.download(extension, location, operation); + return location; + } + + async delete(location: URI): Promise { + // Delete immediately if caching is disabled + if (!this.cache) { + await this.fileService.del(location); + } + } + + private async download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise { + if (!await this.fileService.exists(location)) { + await this.extensionGalleryService.download(extension, location, operation); + } + } + + private async cleanUp(): Promise { + try { + if (!(await this.fileService.exists(this.extensionsDownloadDir))) { + this.logService.trace('Extension VSIX downlads cache dir does not exist'); + return; + } + const folderStat = await this.fileService.resolve(this.extensionsDownloadDir, { resolveMetadata: true }); + if (folderStat.children) { + const toDelete: URI[] = []; + const all: [ExtensionIdentifierWithVersion, IFileStatWithMetadata][] = []; + for (const stat of folderStat.children) { + const extension = this.parse(stat.name); + if (extension) { + all.push([extension, stat]); + } + } + const byExtension = groupByExtension(all, ([extension]) => extension.identifier); + const distinct: IFileStatWithMetadata[] = []; + for (const p of byExtension) { + p.sort((a, b) => semver.rcompare(a[0].version, b[0].version)); + toDelete.push(...p.slice(1).map(e => e[1].resource)); // Delete outdated extensions + distinct.push(p[0][1]); + } + distinct.sort((a, b) => a.mtime - b.mtime); // sort by modified time + toDelete.push(...distinct.slice(0, Math.max(0, distinct.length - this.cache)).map(s => s.resource)); // Retain minimum cacheSize and delete the rest + await Promise.all(toDelete.map(resource => { + this.logService.trace('Deleting vsix from cache', resource.path); + return this.fileService.del(resource); + })); + } + } catch (e) { + this.logService.error(e); + } + } + + private getName(extension: IGalleryExtension): string { + return this.cache ? new ExtensionIdentifierWithVersion(extension.identifier, extension.version).key().toLowerCase() : generateUuid(); + } + + private parse(name: string): ExtensionIdentifierWithVersion | null { + const matches = ExtensionIdVersionRegex.exec(name); + return matches && matches[1] && matches[2] ? new ExtensionIdentifierWithVersion({ id: matches[1] }, matches[2]) : null; + } +} diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 9917280d55..9d60e842b9 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -40,12 +40,13 @@ import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator' import { tmpdir } from 'os'; import { generateUuid } from 'vs/base/common/uuid'; import { IDownloadService } from 'vs/platform/download/common/download'; -import { optional } from 'vs/platform/instantiation/common/instantiation'; +import { optional, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Schemas } from 'vs/base/common/network'; import { CancellationToken } from 'vs/base/common/cancellation'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil'; import { IExtensionManifest, ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { ExtensionsDownloader } from 'vs/platform/extensionManagement/node/extensionDownloader'; const ERROR_SCANNING_SYS_EXTENSIONS = 'scanningSystem'; const ERROR_SCANNING_USER_EXTENSIONS = 'scanningUser'; @@ -113,6 +114,7 @@ export class ExtensionManagementService extends Disposable implements IExtension private readonly installingExtensions: Map> = new Map>(); private readonly uninstallingExtensions: Map> = new Map>(); private readonly manifestCache: ExtensionsManifestCache; + private readonly extensionsDownloader: ExtensionsDownloader; private readonly extensionLifecycle: ExtensionsLifecycle; private readonly _onInstallExtension = this._register(new Emitter()); @@ -133,6 +135,7 @@ export class ExtensionManagementService extends Disposable implements IExtension @ILogService private readonly logService: ILogService, @optional(IDownloadService) private downloadService: IDownloadService, @ITelemetryService private readonly telemetryService: ITelemetryService, + @IInstantiationService instantiationService: IInstantiationService, ) { super(); this.systemExtensionsPath = environmentService.builtinExtensionsPath; @@ -140,6 +143,7 @@ export class ExtensionManagementService extends Disposable implements IExtension this.uninstalledPath = path.join(this.extensionsPath, '.obsolete'); this.uninstalledFileLimiter = new Queue(); this.manifestCache = this._register(new ExtensionsManifestCache(environmentService, this)); + this.extensionsDownloader = this._register(instantiationService.createInstance(ExtensionsDownloader)); this.extensionLifecycle = this._register(new ExtensionsLifecycle(environmentService, this.logService)); this._register(toDisposable(() => { @@ -347,7 +351,7 @@ export class ExtensionManagementService extends Disposable implements IExtension this.downloadInstallableExtension(extension, operation) .then(installableExtension => this.installExtension(installableExtension, ExtensionType.User, cancellationToken) - .then(local => pfs.rimraf(installableExtension.zipPath).finally(() => { }).then(() => local))) + .then(local => this.extensionsDownloader.delete(URI.file(installableExtension.zipPath)).finally(() => { }).then(() => local))) .then(local => this.installDependenciesAndPackExtensions(local, existingExtension) .then(() => local, error => this.uninstall(local, true).then(() => Promise.reject(error), () => Promise.reject(error)))) .then( @@ -425,7 +429,7 @@ export class ExtensionManagementService extends Disposable implements IExtension }; this.logService.trace('Started downloading extension:', extension.identifier.id); - return this.galleryService.download(extension, URI.file(tmpdir()), operation) + return this.extensionsDownloader.downloadExtension(extension, operation) .then( zip => { const zipPath = zip.fsPath; @@ -1003,6 +1007,6 @@ export class ExtensionManagementService extends Disposable implements IExtension ] } */ - this.telemetryService.publicLog(eventName, assign(extensionData, { success: !error, duration, errorcode })); + this.telemetryService.publicLogError(eventName, assign(extensionData, { success: !error, duration, errorcode })); } } diff --git a/src/vs/platform/notification/common/notification.ts b/src/vs/platform/notification/common/notification.ts index cc95e403d7..b505d9e185 100644 --- a/src/vs/platform/notification/common/notification.ts +++ b/src/vs/platform/notification/common/notification.ts @@ -320,8 +320,10 @@ export interface INotificationService { * Shows a prompt in the notification area with the provided choices. The prompt * is non-modal. If you want to show a modal dialog instead, use `IDialogService`. * - * @param onCancel will be called if the user closed the notification without picking - * any of the provided choices. + * @param severity the severity of the notification. Either `Info`, `Warning` or `Error`. + * @param message the message to show as status. + * @param choices options to be choosen from. + * @param options provides some optional configuration options. * * @returns a handle on the notification to e.g. hide it or update message, buttons, etc. */ diff --git a/src/vs/platform/telemetry/common/errorTelemetry.ts b/src/vs/platform/telemetry/common/errorTelemetry.ts index a3d4803780..6edf52d034 100644 --- a/src/vs/platform/telemetry/common/errorTelemetry.ts +++ b/src/vs/platform/telemetry/common/errorTelemetry.ts @@ -120,7 +120,7 @@ export default abstract class BaseErrorTelemetry { private _flushBuffer(): void { /*for (let error of this._buffer) { {{SQL CARBON EDIT}} don't log errors type UnhandledErrorClassification = {} & ErrorEventFragment; - this._telemetryService.publicLog2('UnhandledError', error, true); + this._telemetryService.publicLogError2('UnhandledError', error); }*/ this._buffer.length = 0; } diff --git a/src/vs/platform/telemetry/common/telemetry.ts b/src/vs/platform/telemetry/common/telemetry.ts index 6043228b0d..2cad870139 100644 --- a/src/vs/platform/telemetry/common/telemetry.ts +++ b/src/vs/platform/telemetry/common/telemetry.ts @@ -33,6 +33,10 @@ export interface ITelemetryService { publicLog2 = never, T extends GDPRClassification = never>(eventName: string, data?: StrictPropertyCheck, anonymizeFilePaths?: boolean): Promise; + publicLogError(errorEventName: string, data?: ITelemetryData): Promise; + + publicLogError2 = never, T extends GDPRClassification = never>(eventName: string, data?: StrictPropertyCheck): Promise; + setEnabled(value: boolean): void; getTelemetryInfo(): Promise; diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts index 9a459bfbcf..815230c12d 100644 --- a/src/vs/platform/telemetry/common/telemetryService.ts +++ b/src/vs/platform/telemetry/common/telemetryService.ts @@ -17,6 +17,7 @@ import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/pla export interface ITelemetryServiceConfig { appender: ITelemetryAppender; + sendErrorTelemetry?: boolean; commonProperties?: Promise<{ [name: string]: any }>; piiPaths?: string[]; trueMachineId?: string; @@ -34,6 +35,7 @@ export class TelemetryService implements ITelemetryService { private _piiPaths: string[]; private _userOptIn: boolean; private _enabled: boolean; + private _sendErrorTelemetry: boolean; private readonly _disposables = new DisposableStore(); private _cleanupPatterns: RegExp[] = []; @@ -47,6 +49,7 @@ export class TelemetryService implements ITelemetryService { this._piiPaths = config.piiPaths || []; this._userOptIn = true; this._enabled = true; + this._sendErrorTelemetry = !!config.sendErrorTelemetry; // static cleanup pattern for: `file:///DANGEROUS/PATH/resources/app/Useful/Information` this._cleanupPatterns = [/file:\/\/\/.*?\/resources\/app\//gi]; @@ -144,6 +147,19 @@ export class TelemetryService implements ITelemetryService { return this.publicLog(eventName, data as ITelemetryData, anonymizeFilePaths); } + publicLogError(errorEventName: string, data?: ITelemetryData): Promise { + if (!this._sendErrorTelemetry) { + return Promise.resolve(undefined); + } + + // Send error event and anonymize paths + return this.publicLog(errorEventName, data, true); + } + + publicLogError2 = never, T extends GDPRClassification = never>(eventName: string, data?: StrictPropertyCheck): Promise { + return this.publicLogError(eventName, data as ITelemetryData); + } + private _cleanupInfo(stack: string, anonymizeFilePaths?: boolean): string { let updatedStack = stack; diff --git a/src/vs/platform/telemetry/common/telemetryUtils.ts b/src/vs/platform/telemetry/common/telemetryUtils.ts index 227a44f7bf..27c5bf747d 100644 --- a/src/vs/platform/telemetry/common/telemetryUtils.ts +++ b/src/vs/platform/telemetry/common/telemetryUtils.ts @@ -19,6 +19,13 @@ export const NullTelemetryService = new class implements ITelemetryService { publicLog2 = never, T extends GDPRClassification = never>(eventName: string, data?: StrictPropertyCheck) { return this.publicLog(eventName, data as ITelemetryData); } + publicLogError(eventName: string, data?: ITelemetryData) { + return Promise.resolve(undefined); + } + publicLogError2 = never, T extends GDPRClassification = never>(eventName: string, data?: StrictPropertyCheck) { + return this.publicLogError(eventName, data as ITelemetryData); + } + setEnabled() { } isOptedIn = true; getTelemetryInfo(): Promise { diff --git a/src/vs/platform/telemetry/test/browser/telemetryService.test.ts b/src/vs/platform/telemetry/test/browser/telemetryService.test.ts index e9eba03ee4..096add4dbe 100644 --- a/src/vs/platform/telemetry/test/browser/telemetryService.test.ts +++ b/src/vs/platform/telemetry/test/browser/telemetryService.test.ts @@ -4,13 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { Emitter } from 'vs/base/common/event'; -import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; +import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; import ErrorTelemetry from 'vs/platform/telemetry/browser/errorTelemetry'; import { NullAppender, ITelemetryAppender } from 'vs/platform/telemetry/common/telemetryUtils'; import * as Errors from 'vs/base/common/errors'; import * as sinon from 'sinon'; import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; class TestTelemetryAppender implements ITelemetryAppender { @@ -208,6 +209,10 @@ suite('TelemetryService', () => { private readonly promises: Promise[] = []; + constructor(config: ITelemetryServiceConfig, configurationService: IConfigurationService) { + super({ ...config, sendErrorTelemetry: true }, configurationService); + } + join(): Promise { return Promise.all(this.promises); } diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 66813e7b11..7fe08ceddf 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -343,6 +343,7 @@ export const diffInsertedOutline = registerColor('diffEditor.insertedTextBorder' export const diffRemovedOutline = registerColor('diffEditor.removedTextBorder', { dark: null, light: null, hc: '#FF008F' }, nls.localize('diffEditorRemovedOutline', 'Outline color for text that got removed.')); export const diffBorder = registerColor('diffEditor.border', { dark: null, light: null, hc: contrastBorder }, nls.localize('diffEditorBorder', 'Border color between the two text editors.')); +export const diffDiagonalFill = registerColor('diffEditor.diagonalFill', { dark: '#cccccc33', light: '#22222233', hc: null }, nls.localize('diffDiagonalFill', "Color of the diff editor's diagonal fill. The diagonal fill is used in side-by-side diff views.")); /** * List and tree colors diff --git a/src/vs/platform/theme/common/iconRegistry.ts b/src/vs/platform/theme/common/iconRegistry.ts index ddba8c7fca..893819041e 100644 --- a/src/vs/platform/theme/common/iconRegistry.ts +++ b/src/vs/platform/theme/common/iconRegistry.ts @@ -10,6 +10,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { localize } from 'vs/nls'; import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { RunOnceScheduler } from 'vs/base/common/async'; +import * as Codicons from 'vs/base/common/codicons'; // ------ API types @@ -96,7 +97,7 @@ class IconRegistry implements IIconRegistry { public registerIcon(id: string, defaults: IconDefaults, description?: string, deprecationMessage?: string): ThemeIcon { if (!description) { - description = localize('icon.defaultDescription', 'Icon with identifier {0}', id); + description = localize('icon.defaultDescription', 'Icon with identifier \'{0}\'', id); } let iconContribution: IconContribution = { id, description, defaults, deprecationMessage }; this.iconsById[id] = iconContribution; @@ -163,8 +164,13 @@ export function getIconRegistry(): IIconRegistry { return iconRegistry; } - - +function initialize() { + for (const icon of Codicons.iconRegistry.all) { + registerIcon(icon.id, icon.definition); + } + Codicons.iconRegistry.onDidRegister(icon => registerIcon(icon.id, icon.definition)); +} +initialize(); export const iconsSchemaId = 'vscode://schemas/icons'; diff --git a/src/vs/platform/theme/common/tokenClassificationRegistry.ts b/src/vs/platform/theme/common/tokenClassificationRegistry.ts index 123b0aff26..121f5197bd 100644 --- a/src/vs/platform/theme/common/tokenClassificationRegistry.ts +++ b/src/vs/platform/theme/common/tokenClassificationRegistry.ts @@ -24,7 +24,7 @@ export const typeAndModifierIdPattern = `^${idPattern}$`; export const selectorPattern = `^(${idPattern}|\\*)(\\${CLASSIFIER_MODIFIER_SEPARATOR}${idPattern})*(\\${TOKEN_CLASSIFIER_LANGUAGE_SEPARATOR}${idPattern})?$`; -export const fontStylePattern = '^(\\s*(-?italic|-?bold|-?underline))*\\s*$'; +export const fontStylePattern = '^(\\s*(italic|bold|underline))*\\s*$'; export interface TokenSelector { match(type: string, modifiers: string[], language: string): number; @@ -90,30 +90,24 @@ export namespace TokenStyle { export function fromData(data: { foreground?: Color, bold?: boolean, underline?: boolean, italic?: boolean }): TokenStyle { return new TokenStyle(data.foreground, data.bold, data.underline, data.italic); } - export function fromSettings(foreground: string | undefined, fontStyle: string | undefined): TokenStyle { + export function fromSettings(foreground: string | undefined, fontStyle: string | undefined, bold?: boolean, underline?: boolean, italic?: boolean): TokenStyle { let foregroundColor = undefined; if (foreground !== undefined) { foregroundColor = Color.fromHex(foreground); } - let bold, underline, italic; if (fontStyle !== undefined) { - fontStyle = fontStyle.trim(); - if (fontStyle.length === 0) { - bold = italic = underline = false; - } else { - const expression = /-?italic|-?bold|-?underline/g; - let match; - while ((match = expression.exec(fontStyle))) { - switch (match[0]) { - case 'bold': bold = true; break; - case 'italic': italic = true; break; - case 'underline': underline = true; break; - } + bold = italic = underline = false; + const expression = /italic|bold|underline/g; + let match; + while ((match = expression.exec(fontStyle))) { + switch (match[0]) { + case 'bold': bold = true; break; + case 'italic': italic = true; break; + case 'underline': underline = true; break; } } } return new TokenStyle(foregroundColor, bold, underline, italic); - } } @@ -130,18 +124,18 @@ export interface TokenStyleDefaults { hc?: TokenStyleValue; } -export interface TokenStylingDefaultRule { +export interface SemanticTokenDefaultRule { selector: TokenSelector; defaults: TokenStyleDefaults; } -export interface TokenStylingRule { +export interface SemanticTokenRule { style: TokenStyle; selector: TokenSelector; } -export namespace TokenStylingRule { - export function fromJSONObject(registry: ITokenClassificationRegistry, o: any): TokenStylingRule | undefined { +export namespace SemanticTokenRule { + export function fromJSONObject(registry: ITokenClassificationRegistry, o: any): SemanticTokenRule | undefined { if (o && typeof o._selector === 'string' && o._style) { const style = TokenStyle.fromJSONObject(o._style); if (style) { @@ -153,13 +147,13 @@ export namespace TokenStylingRule { } return undefined; } - export function toJSONObject(rule: TokenStylingRule): any { + export function toJSONObject(rule: SemanticTokenRule): any { return { _selector: rule.selector.id, _style: TokenStyle.toJSONObject(rule.style) }; } - export function equals(r1: TokenStylingRule | undefined, r2: TokenStylingRule | undefined) { + export function equals(r1: SemanticTokenRule | undefined, r2: SemanticTokenRule | undefined) { if (r1 === r2) { return true; } @@ -167,7 +161,7 @@ export namespace TokenStylingRule { && r1.selector && r2.selector && r1.selector.id === r2.selector.id && TokenStyle.equals(r1.style, r2.style); } - export function is(r: any): r is TokenStylingRule { + export function is(r: any): r is SemanticTokenRule { return r && r.selector && typeof r.selector.selectorString === 'string' && TokenStyle.is(r.style); } } @@ -245,7 +239,7 @@ export interface ITokenClassificationRegistry { /** * The styling rules to used when a schema does not define any styling rules. */ - getTokenStylingDefaultRules(): TokenStylingDefaultRule[]; + getTokenStylingDefaultRules(): SemanticTokenDefaultRule[]; /** * JSON schema for an object to assign styling to token classifications @@ -264,14 +258,18 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { private tokenTypeById: { [key: string]: TokenTypeOrModifierContribution }; private tokenModifierById: { [key: string]: TokenTypeOrModifierContribution }; - private tokenStylingDefaultRules: TokenStylingDefaultRule[] = []; + private tokenStylingDefaultRules: SemanticTokenDefaultRule[] = []; private typeHierarchy: { [id: string]: string[] }; - private tokenStylingSchema: IJSONSchema & { properties: IJSONSchemaMap } = { + private tokenStylingSchema: IJSONSchema & { properties: IJSONSchemaMap, patternProperties: IJSONSchemaMap } = { type: 'object', properties: {}, - additionalProperties: getStylingSchemeEntry(), + patternProperties: { + [selectorPattern]: getStylingSchemeEntry() + }, + //errorMessage: nls.localize('schema.token.errors', 'Valid token selectors have the form (*|tokenType)(.tokenModifier)*(:tokenLanguage)?.'), + additionalProperties: false, definitions: { style: { type: 'object', @@ -289,11 +287,24 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { }, fontStyle: { type: 'string', - description: nls.localize('schema.token.fontStyle', 'Font style of the rule: \'italic\', \'bold\' or \'underline\' or a combination. The empty string unsets inherited settings.'), + description: nls.localize('schema.token.fontStyle', 'Sets the all font styles of the rule: \'italic\', \'bold\' or \'underline\' or a combination. All styles that are not listed are unset. The empty string unsets all styles.'), pattern: fontStylePattern, patternErrorMessage: nls.localize('schema.fontStyle.error', 'Font style must be \'italic\', \'bold\' or \'underline\' or a combination. The empty string unsets all styles.'), defaultSnippets: [{ label: nls.localize('schema.token.fontStyle.none', 'None (clear inherited style)'), bodyText: '""' }, { body: 'italic' }, { body: 'bold' }, { body: 'underline' }, { body: 'italic underline' }, { body: 'bold underline' }, { body: 'italic bold underline' }] + }, + bold: { + type: 'boolean', + description: nls.localize('schema.token.bold', 'Sets or unsets the font style to bold. Note, the presence of \'fontStyle\' overrides this setting.'), + }, + italic: { + type: 'boolean', + description: nls.localize('schema.token.italic', 'Sets or unsets the font style to italic. Note, the presence of \'fontStyle\' overrides this setting.'), + }, + underline: { + type: 'boolean', + description: nls.localize('schema.token.underline', 'Sets or unsets the font style to underline. Note, the presence of \'fontStyle\' overrides this setting.'), } + }, defaultSnippets: [{ body: { foreground: '${1:#FF0000}', fontStyle: '${2:bold}' } }] } @@ -318,7 +329,8 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { let tokenStyleContribution: TokenTypeOrModifierContribution = { num, id, superType, description, deprecationMessage }; this.tokenTypeById[id] = tokenStyleContribution; - this.tokenStylingSchema.properties[id] = getStylingSchemeEntry(description, deprecationMessage); + const stylingSchemeEntry = getStylingSchemeEntry(description, deprecationMessage); + this.tokenStylingSchema.properties[id] = stylingSchemeEntry; this.typeHierarchy = {}; } @@ -406,7 +418,7 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { return this.tokenStylingSchema; } - public getTokenStylingDefaultRules(): TokenStylingDefaultRule[] { + public getTokenStylingDefaultRules(): SemanticTokenDefaultRule[] { return this.tokenStylingDefaultRules; } diff --git a/src/vs/platform/userDataSync/common/settingsMerge.ts b/src/vs/platform/userDataSync/common/settingsMerge.ts index 29e1c69cf1..c8b837e36d 100644 --- a/src/vs/platform/userDataSync/common/settingsMerge.ts +++ b/src/vs/platform/userDataSync/common/settingsMerge.ts @@ -98,8 +98,13 @@ export function merge(originalLocalContent: string, originalRemoteContent: strin return { conflictsSettings: [], localContent: updateIgnoredSettings(originalRemoteContent, originalLocalContent, ignoredSettings, formattingOptions), remoteContent: null, hasConflicts: false }; } - /* remote and local has changed */ + /* local is empty and not synced before */ + if (baseContent === null && isEmpty(originalLocalContent)) { + const localContent = areSame(originalLocalContent, originalRemoteContent, ignoredSettings) ? null : updateIgnoredSettings(originalRemoteContent, originalLocalContent, ignoredSettings, formattingOptions); + return { conflictsSettings: [], localContent, remoteContent: null, hasConflicts: false }; + } + /* remote and local has changed */ let localContent = originalLocalContent; let remoteContent = originalRemoteContent; const local = parse(originalLocalContent); @@ -258,6 +263,11 @@ export function areSame(localContent: string, remoteContent: string, ignoredSett return true; } +export function isEmpty(content: string): boolean { + const nodes = parseSettings(content); + return nodes.length === 0; +} + function compare(from: IStringDictionary | null, to: IStringDictionary, ignored: Set): { added: Set, removed: Set, updated: Set } { const fromKeys = from ? Object.keys(from).filter(key => !ignored.has(key)) : []; const toKeys = Object.keys(to).filter(key => !ignored.has(key)); diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 1a56d7cc81..2a246f8a03 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -6,15 +6,13 @@ import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, CONFIGURATION_SYNC_STORE_KEY, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, USER_DATA_SYNC_SCHEME, PREVIEW_DIR_NAME, ISyncResourceHandle } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; -import { parse } from 'vs/base/common/json'; import { localize } from 'vs/nls'; import { Event } from 'vs/base/common/event'; import { createCancelablePromise } from 'vs/base/common/async'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { updateIgnoredSettings, merge, getIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge'; -import { isEmptyObject } from 'vs/base/common/types'; +import { updateIgnoredSettings, merge, getIgnoredSettings, isEmpty } from 'vs/platform/userDataSync/common/settingsMerge'; import { edit } from 'vs/platform/userDataSync/common/content'; import { IFileSyncPreviewResult, AbstractJsonFileSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -160,10 +158,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser { if (localFileContent) { const formatUtils = await this.getFormattingOptions(); const content = edit(localFileContent.value.toString(), [CONFIGURATION_SYNC_STORE_KEY], undefined, formatUtils); - const settings = parse(content); - if (!isEmptyObject(settings)) { - return true; - } + return !isEmpty(content); } } catch (error) { if ((error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) { diff --git a/src/vs/platform/userDataSync/common/snippetsSync.ts b/src/vs/platform/userDataSync/common/snippetsSync.ts index 6fdc52e2e4..5835afd0e4 100644 --- a/src/vs/platform/userDataSync/common/snippetsSync.ts +++ b/src/vs/platform/userDataSync/common/snippetsSync.ts @@ -417,7 +417,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD for (const entry of stat.children || []) { const resource = entry.resource; const extension = extname(resource); - if (extension === '.json' || extension === '.code-snippet') { + if (extension === '.json' || extension === '.code-snippets') { const key = relativePath(this.snippetsFolder, resource)!; const content = await this.fileService.readFile(resource); snippets[key] = content; diff --git a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts index d05ddd7a25..38bdfb5382 100644 --- a/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataAutoSyncService.ts @@ -42,7 +42,7 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto } private async updateEnablement(stopIfDisabled: boolean, auto: boolean): Promise { - const enabled = await this.isAutoSyncEnabled(); + const { enabled, reason } = await this.isAutoSyncEnabled(); if (this.enabled === enabled) { return; } @@ -56,7 +56,7 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto this.resetFailures(); if (stopIfDisabled) { this.userDataSyncService.stop(); - this.logService.info('Auto Sync: stopped.'); + this.logService.info('Auto Sync: stopped because', reason); } } @@ -95,10 +95,18 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto } } - private async isAutoSyncEnabled(): Promise { - return this.userDataSyncEnablementService.isEnabled() - && this.userDataSyncService.status !== SyncStatus.Uninitialized - && !!(await this.authTokenService.getToken()); + private async isAutoSyncEnabled(): Promise<{ enabled: boolean, reason?: string }> { + if (!this.userDataSyncEnablementService.isEnabled()) { + return { enabled: false, reason: 'sync is disabled' }; + } + if (this.userDataSyncService.status === SyncStatus.Uninitialized) { + return { enabled: false, reason: 'sync is not initialized' }; + } + const token = await this.authTokenService.getToken(); + if (!token) { + return { enabled: false, reason: 'token is not avaialable' }; + } + return { enabled: true }; } private resetFailures(): void { diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index b923a9beed..c1dd8e61b3 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -26,19 +26,6 @@ import { isArray, isString, isObject } from 'vs/base/common/types'; export const CONFIGURATION_SYNC_STORE_KEY = 'configurationSync.store'; -export interface ISyncConfiguration { - sync: { - enable: boolean, - enableSettings: boolean, - enableKeybindings: boolean, - enableUIState: boolean, - enableExtensions: boolean, - keybindingsPerPlatform: boolean, - ignoredExtensions: string[], - ignoredSettings: string[] - } -} - export function getDisallowedIgnoredSettings(): string[] { const allSettings = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); return Object.keys(allSettings).filter(setting => !!allSettings[setting].disallowSyncIgnore); diff --git a/src/vs/platform/userDataSync/test/common/settingsMerge.test.ts b/src/vs/platform/userDataSync/test/common/settingsMerge.test.ts index 0d78573a8f..4ed45d829e 100644 --- a/src/vs/platform/userDataSync/test/common/settingsMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/settingsMerge.test.ts @@ -723,6 +723,23 @@ suite('SettingsMerge - Merge', () => { assert.deepEqual(actual.conflictsSettings, expectedConflicts); assert.ok(actual.hasConflicts); }); + + test('merge when remote has comments and local is empty', async () => { + const localContent = ` +{ + +}`; + const remoteContent = stringify` +{ + // this is a comment + "a": 1, +}`; + const actual = merge(localContent, remoteContent, null, [], [], formattingOptions); + assert.equal(actual.localContent, remoteContent); + assert.equal(actual.remoteContent, null); + assert.equal(actual.conflictsSettings.length, 0); + assert.ok(!actual.hasConflicts); + }); }); suite('SettingsMerge - Compute Remote Content', () => { diff --git a/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts b/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts index 5a85b1d29c..976537bff1 100644 --- a/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/snippetsSync.test.ts @@ -597,7 +597,7 @@ suite('SnippetsSync', () => { }); test('sync global and language snippet', async () => { - await updateSnippet('global.code-snippet', globalSnippet, client2); + await updateSnippet('global.code-snippets', globalSnippet, client2); await updateSnippet('html.json', htmlSnippet1, client2); await client2.sync(); @@ -607,17 +607,17 @@ suite('SnippetsSync', () => { const actual1 = await readSnippet('html.json', testClient); assert.equal(actual1, htmlSnippet1); - const actual2 = await readSnippet('global.code-snippet', testClient); + const actual2 = await readSnippet('global.code-snippets', testClient); assert.equal(actual2, globalSnippet); const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); const actual = parseSnippets(content!); - assert.deepEqual(actual, { 'html.json': htmlSnippet1, 'global.code-snippet': globalSnippet }); + assert.deepEqual(actual, { 'html.json': htmlSnippet1, 'global.code-snippets': globalSnippet }); }); test('sync should ignore non snippets', async () => { - await updateSnippet('global.code-snippet', globalSnippet, client2); + await updateSnippet('global.code-snippets', globalSnippet, client2); await updateSnippet('html.html', htmlSnippet1, client2); await updateSnippet('typescript.json', tsSnippet1, client2); await client2.sync(); @@ -628,7 +628,7 @@ suite('SnippetsSync', () => { const actual1 = await readSnippet('typescript.json', testClient); assert.equal(actual1, tsSnippet1); - const actual2 = await readSnippet('global.code-snippet', testClient); + const actual2 = await readSnippet('global.code-snippets', testClient); assert.equal(actual2, globalSnippet); const actual3 = await readSnippet('html.html', testClient); assert.equal(actual3, null); @@ -636,7 +636,7 @@ suite('SnippetsSync', () => { const { content } = await testClient.read(testObject.resource); assert.ok(content !== null); const actual = parseSnippets(content!); - assert.deepEqual(actual, { 'typescript.json': tsSnippet1, 'global.code-snippet': globalSnippet }); + assert.deepEqual(actual, { 'typescript.json': tsSnippet1, 'global.code-snippets': globalSnippet }); }); function parseSnippets(content: string): IStringDictionary { diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index a9b6cd0098..d2c8a18d1d 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -8173,6 +8173,7 @@ declare module 'vscode' { * } * }; * vscode.window.createTerminal({ name: 'Exit example', pty }); + * ``` */ onDidClose?: Event; diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 4f807c4b4e..4905189e58 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -112,6 +112,9 @@ declare module 'vscode' { * Get existing authentication sessions. Rejects if a provider with providerId is not * registered, or if the user does not consent to sharing authentication information with * the extension. + * @param providerId The id of the provider to use + * @param scopes A list of scopes representing the permissions requested. These are dependent on the authentication + * provider */ export function getSessions(providerId: string, scopes: string[]): Thenable; @@ -119,9 +122,20 @@ declare module 'vscode' { * Prompt a user to login to create a new authenticaiton session. Rejects if a provider with * providerId is not registered, or if the user does not consent to sharing authentication * information with the extension. + * @param providerId The id of the provider to use + * @param scopes A list of scopes representing the permissions requested. These are dependent on the authentication + * provider */ export function login(providerId: string, scopes: string[]): Thenable; + /** + * Logout of a specific session. + * @param providerId The id of the provider to use + * @param sessionId The session id to remove + * provider + */ + export function logout(providerId: string, sessionId: string): Thenable; + /** * An [event](#Event) which fires when the array of sessions has changed, or data * within a session has changed for a provider. Fires with the ids of the providers @@ -720,17 +734,18 @@ declare module 'vscode' { //#region debug: https://github.com/microsoft/vscode/issues/88230 /** - * VS Code can call the `provideDebugConfigurations` method of a `DebugConfigurationProvider` in two situations (aka 'scopes'): - * to provide the initial debug configurations for a newly created launch.json or to provide debug configurations dynamically based on context. - * A scope can be used when registering a `DebugConfigurationProvider` with #debug.registerDebugConfigurationProvider. + * 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 DebugConfigurationProviderScope { + export enum DebugConfigurationProviderTriggerKind { /** - * The 'initial' scope is used to ask for debug configurations to be copied into a newly created launch.json. + * `DebugConfigurationProvider.provideDebugConfigurations` is called to provide the initial debug configurations for a newly created launch.json. */ Initial = 1, /** - * The 'dynamic' scope is used to ask for additional dynamic debug configurations to be presented to the user (in addition to the static configurations from the launch.json). + * `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 } @@ -738,19 +753,19 @@ declare module 'vscode' { export namespace debug { /** * Register a [debug configuration provider](#DebugConfigurationProvider) for a specific debug type. - * The optional [scope](#DebugConfigurationProviderScope) argument can be used to bind the `provideDebugConfigurations` method of the provider to a specific context (aka scope). - * Currently two scopes are possible: with the value `Initial` (or if no scope argument is given) the `provideDebugConfigurations` method is used to find the initial debug configurations to be copied into a newly created launch.json. - * With a scope value `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 scope 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 scopes, results in the same resolve methods called multiple times. + * 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 scope The [scope](#DebugConfigurationProviderScope) for which the 'provideDebugConfiguration' method of the provider is registered. + * @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, scope?: DebugConfigurationProviderScope): Disposable; + export function registerDebugConfigurationProvider(debugType: string, provider: DebugConfigurationProvider, triggerKind?: DebugConfigurationProviderTriggerKind): Disposable; } // deprecated debug API @@ -983,6 +998,15 @@ declare module 'vscode' { * A collection of mutations that an extension can apply to a process environment. */ export interface EnvironmentVariableCollection { + /** + * Whether the collection should be cached for the workspace and applied to the terminal + * across window reloads. When true the collection will be active immediately such when the + * window reloads. Additionally, this API will return the cached version if it exists. The + * collection will be invalidated when the extension is uninstalled or when the collection + * is cleared. Defaults to true. + */ + persistent: boolean; + /** * Replace an environment variable with a value. * @@ -1026,26 +1050,14 @@ declare module 'vscode' { * Clears all mutators from this collection. */ clear(): void; - - /** - * Disposes the collection, if the collection was persisted it will no longer be retained - * across reloads. - */ - dispose(): void; } - export namespace window { + export interface ExtensionContext { /** - * Creates or returns the extension's environment variable collection for this workspace, - * enabling changes to be applied to terminal environment variables. - * - * @param persistent Whether the collection should be cached for the workspace and applied - * to the terminal across window reloads. When true the collection will be active - * immediately such when the window reloads. Additionally, this API will return the cached - * version if it exists. The collection will be invalidated when the extension is - * uninstalled or when the collection is disposed. Defaults to false. + * Gets the extension's environment variable collection for this workspace, enabling changes + * to be applied to terminal environment variables. */ - export function getEnvironmentVariableCollection(persistent?: boolean): EnvironmentVariableCollection; + readonly environmentVariableCollection: EnvironmentVariableCollection; } //#endregion @@ -1239,22 +1251,32 @@ declare module 'vscode' { } /** - * Event triggered by extensions to signal to VS Code that an edit has occurred on an [`EditableCustomDocument`](#EditableCustomDocument). + * Event triggered by extensions to signal to VS Code that an edit has occurred on an [`CustomDocument`](#CustomDocument). * - * @see [`EditableCustomDocument.onDidChange`](#EditableCustomDocument.onDidChange). + * @see [`CustomDocumentProvider.onDidChangeCustomDocument`](#CustomDocumentProvider.onDidChangeCustomDocument). */ - interface CustomDocumentEditEvent { + interface CustomDocumentEditEvent { + + /** + * The document that the edit is for. + */ + readonly document: T; + /** * Undo the edit operation. * - * This is invoked by VS Code when the user triggers an undo. + * This is invoked by VS Code when the user undoes this edit. To implement `undo`, your + * extension should restore the document and editor to the state they were in just before this + * edit was added to VS Code's internal edit stack by `onDidChangeCustomDocument`. */ undo(): Thenable | void; /** * Redo the edit operation. * - * This is invoked by VS Code when the user triggers a redo. + * This is invoked by VS Code when the user redoes this edit. To implement `redo`, your + * extension should restore the document and editor to the state they were in just after this + * edit was added to VS Code's internal edit stack by `onDidChangeCustomDocument`. */ redo(): Thenable | void; @@ -1267,17 +1289,20 @@ declare module 'vscode' { } /** - * Event triggered by extensions to signal to VS Code that the content of a [`EditableCustomDocument`](#EditableCustomDocument) + * Event triggered by extensions to signal to VS Code that the content of a [`CustomDocument`](#CustomDocument) * has changed. * - * @see [`EditableCustomDocument.onDidChange`](#EditableCustomDocument.onDidChange). + * @see [`CustomDocumentProvider.onDidChangeCustomDocument`](#CustomDocumentProvider.onDidChangeCustomDocument). */ - interface CustomDocumentContentChangeEvent { - // marker interface + interface CustomDocumentContentChangeEvent { + /** + * The document that the change is for. + */ + readonly document: T; } /** - * A backup for an [`EditableCustomDocument`](#EditableCustomDocument). + * A backup for an [`CustomDocument`](#CustomDocument). */ interface CustomDocumentBackup { /** @@ -1285,55 +1310,102 @@ declare module 'vscode' { * * This id is passed back to your extension in `openCustomDocument` when opening a custom editor from a backup. */ - readonly backupId: string; + readonly id: string; /** - * Dispose of the current backup. + * Delete the current backup. * - * This is called by VS Code when it is clear the current backup, such as when a new backup is made or when the - * file is saved. + * This is called by VS Code when it is clear the current backup is no longer needed, such as when a new backup + * is made or when the file is saved. */ - dispose(): void; + delete(): void; } /** - * Represents an editable custom document used by a [`CustomEditorProvider`](#CustomEditorProvider). - * - * `EditableCustomDocument` is how custom editors hook into standard VS Code operations such as save and undo. The - * document is also how custom editors notify VS Code that an edit has taken place. + * Additional information used to implement [`CustomEditableDocument.backup`](#CustomEditableDocument.backup). */ - interface EditableCustomDocument extends CustomDocument { + interface CustomDocumentBackupContext { /** - * Save the resource for a custom editor. + * Suggested file location to write the new backup. * - * This method is invoked by VS Code when the user saves a custom editor. This can happen when the user - * triggers save while the custom editor is active, by commands such as `save all`, or by auto save if enabled. + * Note that your extension is free to ignore this and use its own strategy for backup. * - * To implement `save`, the implementer must persist the custom editor. This usually means writing the - * file data for the custom document to disk. After `save` completes, any associated editor instances will - * no longer be marked as dirty. - * - * @param cancellation Token that signals the save is no longer required (for example, if another save was triggered). - * - * @return Thenable signaling that saving has completed. + * For editors for workspace resource, this destination will be in the workspace storage. The path may not */ - save(cancellation: CancellationToken): Thenable; + readonly destination: Uri; + } + + /** + * Additional information about the opening custom document. + */ + interface CustomDocumentOpenContext { + /** + * The id of the backup to restore the document from or `undefined` if there is no backup. + * + * If this is provided, your extension should restore the editor from the backup instead of reading the file + * the user's workspace. + */ + readonly backupId?: string; + } + + /** + * Provider for readonly custom editors that use a custom document model. + * + * Custom editors use [`CustomDocument`](#CustomDocument) as their document model instead of a [`TextDocument`](#TextDocument). + * + * You should use this type of custom editor when dealing with binary files or more complex scenarios. For simple + * text based documents, use [`CustomTextEditorProvider`](#CustomTextEditorProvider) instead. + * + * @param T Type of the custom document returned by this provider. + */ + export interface CustomReadonlyEditorProvider { /** - * Save the resource for a custom editor to a different location. + * Create a new document for a given resource. * - * This method is invoked by VS Code when the user triggers `save as` on a custom editor. + * `openCustomDocument` is called when the first editor for a given resource is opened, and the resolve document + * is passed to `resolveCustomEditor`. The resolved `CustomDocument` is re-used for subsequent editor opens. + * If all editors for a given resource are closed, the `CustomDocument` is disposed of. Opening an editor at + * this point will trigger another call to `openCustomDocument`. * - * To implement `saveAs`, the implementer must persist the custom editor to `targetResource`. The - * existing editor will remain open after `saveAs` completes. + * @param uri Uri of the document to open. + * @param openContext Additional information about the opening custom document. + * @param token A cancellation token that indicates the result is no longer needed. * - * @param targetResource Location to save to. - * @param cancellation Token that signals the save is no longer required. - * - * @return Thenable signaling that saving has completed. + * @return The custom document. */ - saveAs(targetResource: Uri, cancellation: CancellationToken): Thenable; + openCustomDocument(uri: Uri, openContext: CustomDocumentOpenContext, token: CancellationToken): Thenable | T; + /** + * Resolve a custom editor for a given resource. + * + * This is called whenever the user opens a new editor for this `CustomEditorProvider`. + * + * To resolve a custom editor, the provider must fill in its initial html content and hook up all + * the event listeners it is interested it. The provider can also hold onto the `WebviewPanel` to use later, + * for example in a command. See [`WebviewPanel`](#WebviewPanel) for additional details. + * + * @param document Document for the resource being resolved. + * @param webviewPanel Webview to resolve. + * @param token A cancellation token that indicates the result is no longer needed. + * + * @return Optional thenable indicating that the custom editor has been resolved. + */ + resolveCustomEditor(document: T, webviewPanel: WebviewPanel, token: CancellationToken): Thenable | void; + } + + /** + * Provider for editiable custom editors that use a custom document model. + * + * Custom editors use [`CustomDocument`](#CustomDocument) as their document model instead of a [`TextDocument`](#TextDocument). + * This gives extensions full control over actions such as edit, save, and backup. + * + * You should use this type of custom editor when dealing with binary files or more complex scenarios. For simple + * text based documents, use [`CustomTextEditorProvider`](#CustomTextEditorProvider) instead. + * + * @param T Type of the custom document returned by this provider. + */ + export interface CustomEditorProvider extends CustomReadonlyEditorProvider { /** * Signal that an edit has occurred inside a custom editor. * @@ -1354,10 +1426,43 @@ declare module 'vscode' { * * An editor should only ever fire `CustomDocumentEditEvent` events, or only ever fire `CustomDocumentContentChangeEvent` events. */ - readonly onDidChange: Event | Event; + readonly onDidChangeCustomDocument: Event> | Event>; /** - * Revert a custom editor to its last saved state. + * Save a custom document. + * + * This method is invoked by VS Code when the user saves a custom editor. This can happen when the user + * triggers save while the custom editor is active, by commands such as `save all`, or by auto save if enabled. + * + * To implement `save`, the implementer must persist the custom editor. This usually means writing the + * file data for the custom document to disk. After `save` completes, any associated editor instances will + * no longer be marked as dirty. + * + * @param document Document to save. + * @param cancellation Token that signals the save is no longer required (for example, if another save was triggered). + * + * @return Thenable signaling that saving has completed. + */ + saveCustomDocument(document: T, cancellation: CancellationToken): Thenable; + + /** + * Save a custom document to a different location. + * + * This method is invoked by VS Code when the user triggers 'save as' on a custom editor. The implementer must + * persist the custom editor to `destination`. + * + * When the user accepts save as, the current editor is be replaced by an non-dirty editor for the newly saved file. + * + * @param document Document to save. + * @param destination Location to save to. + * @param cancellation Token that signals the save is no longer required. + * + * @return Thenable signaling that saving has completed. + */ + saveCustomDocumentAs(document: T, destination: Uri, cancellation: CancellationToken): Thenable; + + /** + * Revert a custom document to its last saved state. * * This method is invoked by VS Code when the user triggers `File: Revert File` in a custom editor. (Note that * this is only used using VS Code's `File: Revert File` command and not on a `git revert` of the file). @@ -1366,17 +1471,15 @@ declare module 'vscode' { * are displaying the document in the same state is saved in. This usually means reloading the file from the * workspace. * - * During `revert`, your extension should also clear any backups for the custom editor. Backups are only needed - * when there is a difference between an editor's state in VS Code and its save state on disk. - * + * @param document Document to revert. * @param cancellation Token that signals the revert is no longer required. * * @return Thenable signaling that the change has completed. */ - revert(cancellation: CancellationToken): Thenable; + revertCustomDocument(document: T, cancellation: CancellationToken): Thenable; /** - * Back up the resource in its current state. + * Back up a dirty custom document. * * Backups are used for hot exit and to prevent data loss. Your `backup` method should persist the resource in * its current state, i.e. with the edits applied. Most commonly this means saving the resource to disk in @@ -1388,72 +1491,14 @@ declare module 'vscode' { * made in quick succession, `backup` is only triggered after the last one. `backup` is not invoked when * `auto save` is enabled (since auto save already persists resource ). * + * @param document Document to backup. + * @param context Information that can be used to backup the document. * @param cancellation Token that signals the current backup since a new backup is coming in. It is up to your * extension to decided how to respond to cancellation. If for example your extension is backing up a large file * in an operation that takes time to complete, your extension may decide to finish the ongoing backup rather * than cancelling it to ensure that VS Code has some valid backup. */ - backup(cancellation: CancellationToken): Thenable; - } - - /** - * Additional information about the opening custom document. - */ - interface OpenCustomDocumentContext { - /** - * The id of the backup to restore the document from or `undefined` if there is no backup. - * - * If this is provided, your extension should restore the editor from the backup instead of reading the file - * the user's workspace. - */ - readonly backupId?: string; - } - - /** - * Provider for custom editors that use a custom document model. - * - * Custom editors use [`CustomDocument`](#CustomDocument) as their document model instead of a [`TextDocument`](#TextDocument). - * This gives extensions full control over actions such as edit, save, and backup. - * - * You should use this type of custom editor when dealing with binary files or more complex scenarios. For simple - * text based documents, use [`CustomTextEditorProvider`](#CustomTextEditorProvider) instead. - * - * @param DocumentType Type of the custom document returned by this provider. - */ - export interface CustomEditorProvider { - - /** - * Create a new document for a given resource. - * - * `openCustomDocument` is called when the first editor for a given resource is opened, and the resolve document - * is passed to `resolveCustomEditor`. The resolved `CustomDocument` is re-used for subsequent editor opens. - * If all editors for a given resource are closed, the `CustomDocument` is disposed of. Opening an editor at - * this point will trigger another call to `openCustomDocument`. - * - * @param uri Uri of the document to open. - * @param openContext Additional information about the opening custom document. - * @param token A cancellation token that indicates the result is no longer needed. - * - * @return The custom document. - */ - openCustomDocument(uri: Uri, openContext: OpenCustomDocumentContext, token: CancellationToken): Thenable | DocumentType; - - /** - * Resolve a custom editor for a given resource. - * - * This is called whenever the user opens a new editor for this `CustomEditorProvider`. - * - * To resolve a custom editor, the provider must fill in its initial html content and hook up all - * the event listeners it is interested it. The provider can also hold onto the `WebviewPanel` to use later, - * for example in a command. See [`WebviewPanel`](#WebviewPanel) for additional details. - * - * @param document Document for the resource being resolved. - * @param webviewPanel Webview to resolve. - * @param token A cancellation token that indicates the result is no longer needed. - * - * @return Optional thenable indicating that the custom editor has been resolved. - */ - resolveCustomEditor(document: DocumentType, webviewPanel: WebviewPanel, token: CancellationToken): Thenable | void; + backupCustomDocument(document: T, context: CustomDocumentBackupContext, cancellation: CancellationToken): Thenable; } namespace window { @@ -1462,11 +1507,13 @@ declare module 'vscode' { */ export function registerCustomEditorProvider2( viewType: string, - provider: CustomEditorProvider, + provider: CustomReadonlyEditorProvider | CustomEditorProvider, options?: { readonly webviewOptions?: WebviewPanelOptions; /** + * Only applies to `CustomReadonlyEditorProvider | CustomEditorProvider`. + * * Indicates that the provider allows multiple editor instances to be open at the same time for * the same resource. * @@ -1477,7 +1524,7 @@ declare module 'vscode' { * When set, users can split and create copies of the custom editor. The custom editor must make sure it * can properly synchronize the states of all editor instances for a resource so that they are consistent. */ - readonly supportsMultipleEditorsPerResource?: boolean; + readonly supportsMultipleEditorsPerDocument?: boolean; } ): Disposable; } @@ -1643,6 +1690,12 @@ declare module 'vscode' { */ editable?: boolean; + /** + * Controls whether the full notebook can be run at once. + * Defaults to true + */ + runnable?: boolean; + /** * Default value for [cell editable metadata](#NotebookCellMetadata.editable). * Defaults to true. @@ -1759,11 +1812,12 @@ declare module 'vscode' { export function registerNotebookOutputRenderer(type: string, outputSelector: NotebookOutputSelector, renderer: NotebookOutputRenderer): Disposable; + // remove activeNotebookDocument, now that there is activeNotebookEditor.document export let activeNotebookDocument: NotebookDocument | undefined; export let activeNotebookEditor: NotebookEditor | undefined; - // export const onDidChangeNotebookDocument: Event; + export const onDidChangeNotebookDocument: Event; /** * Create a document that is the concatenation of all notebook cells. By default all code-cells are included @@ -2019,4 +2073,25 @@ 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 9422978136..b85fe2d1a5 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -16,6 +16,7 @@ import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; interface AllowedExtension { id: string; @@ -62,7 +63,8 @@ export class MainThreadAuthenticationProvider extends Disposable { private readonly _proxy: ExtHostAuthenticationShape, public readonly id: string, public readonly displayName: string, - private readonly notificationService: INotificationService + private readonly notificationService: INotificationService, + private readonly storageKeysSyncRegistryService: IStorageKeysSyncRegistryService ) { super(); } @@ -148,6 +150,8 @@ export class MainThreadAuthenticationProvider extends Disposable { order: 3 }); + this.storageKeysSyncRegistryService.registerStorageKey({ key: `${this.id}-${session.account.displayName}`, version: 1 }); + const manageCommand = CommandsRegistry.registerCommand({ id: `configureSessions${session.id}`, handler: (accessor, args) => { @@ -285,14 +289,15 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu @IAuthenticationService private readonly authenticationService: IAuthenticationService, @IDialogService private readonly dialogService: IDialogService, @IStorageService private readonly storageService: IStorageService, - @INotificationService private readonly notificationService: INotificationService + @INotificationService private readonly notificationService: INotificationService, + @IStorageKeysSyncRegistryService private readonly storageKeysSyncRegistryService: IStorageKeysSyncRegistryService ) { super(); this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostAuthentication); } async $registerAuthenticationProvider(id: string, displayName: string): Promise { - const provider = new MainThreadAuthenticationProvider(this._proxy, id, displayName, this.notificationService); + const provider = new MainThreadAuthenticationProvider(this._proxy, id, displayName, this.notificationService, this.storageKeysSyncRegistryService); await provider.initialize(); this.authenticationService.registerAuthenticationProvider(id, provider); } @@ -316,14 +321,14 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu const { choice } = await this.dialogService.show( Severity.Info, - nls.localize('confirmAuthenticationAccess', "The extension '{0}' is trying to access authentication information for the {1} account '{2}'.", extensionName, providerName, accountName), - [nls.localize('cancel', "Cancel"), nls.localize('allow', "Allow")], + nls.localize('confirmAuthenticationAccess', "The extension '{0}' wants to access the {1} account '{2}'.", extensionName, providerName, accountName), + [nls.localize('allow', "Allow"), nls.localize('cancel', "Cancel")], { - cancelId: 0 + cancelId: 1 } ); - const allow = choice === 1; + const allow = choice === 0; if (allow) { allowList.push({ id: extensionId, name: extensionName }); this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL); @@ -336,13 +341,13 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu const { choice } = await this.dialogService.show( Severity.Info, nls.localize('confirmLogin', "The extension '{0}' wants to sign in using {1}.", extensionName, providerName), - [nls.localize('cancel', "Cancel"), nls.localize('allow', "Allow")], + [nls.localize('allow', "Allow"), nls.localize('cancel', "Cancel")], { - cancelId: 0 + cancelId: 1 } ); - return choice === 1; + return choice === 0; } async $setTrustedExtension(providerId: string, accountName: string, extensionId: string, extensionName: string): Promise { diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index 93235737b6..2858a5784a 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -177,6 +177,10 @@ export class MainThreadCommentController { this._reactions = reactions; } + get options() { + return this._features.options; + } + private readonly _threads: Map = new Map(); public activeCommentThread?: MainThreadCommentThread; diff --git a/src/vs/workbench/api/browser/mainThreadDebugService.ts b/src/vs/workbench/api/browser/mainThreadDebugService.ts index 548ae880ca..a5d9a748ab 100644 --- a/src/vs/workbench/api/browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/browser/mainThreadDebugService.ts @@ -15,7 +15,7 @@ import severity from 'vs/base/common/severity'; import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { convertToVSCPaths, convertToDAPaths } from 'vs/workbench/contrib/debug/common/debugUtils'; -import { DebugConfigurationProviderScope } from 'vs/workbench/api/common/extHostTypes'; +import { DebugConfigurationProviderTriggerKind } from 'vs/workbench/api/common/extHostTypes'; @extHostNamedCustomer(MainContext.MainThreadDebugService) export class MainThreadDebugService implements MainThreadDebugServiceShape, IDebugAdapterFactory { @@ -155,11 +155,11 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb return Promise.resolve(); } - public $registerDebugConfigurationProvider(debugType: string, providerScope: DebugConfigurationProviderScope, hasProvide: boolean, hasResolve: boolean, hasResolve2: boolean, hasProvideDebugAdapter: boolean, handle: number): Promise { + public $registerDebugConfigurationProvider(debugType: string, providerTriggerKind: DebugConfigurationProviderTriggerKind, hasProvide: boolean, hasResolve: boolean, hasResolve2: boolean, hasProvideDebugAdapter: boolean, handle: number): Promise { const provider = { type: debugType, - scope: providerScope + triggerKind: providerTriggerKind }; if (hasProvide) { provider.provideDebugConfigurations = (folder, token) => { diff --git a/src/vs/workbench/api/browser/mainThreadEditors.ts b/src/vs/workbench/api/browser/mainThreadEditors.ts index 92ce02f77e..78a532b8c6 100644 --- a/src/vs/workbench/api/browser/mainThreadEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadEditors.ts @@ -24,7 +24,9 @@ import { ExtHostContext, ExtHostEditorsShape, IApplyEditsOptions, IExtHostContex import { EditorViewColumn, editorGroupToViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { DEFAULT_EDITOR_ID } from 'vs/workbench/contrib/files/common/files'; +import { openEditorWith } from 'vs/workbench/contrib/files/common/openWith'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; export class MainThreadTextEditors implements MainThreadTextEditorsShape { @@ -296,26 +298,17 @@ CommandsRegistry.registerCommand('_workbench.open', function (accessor: Services CommandsRegistry.registerCommand('_workbench.openWith', (accessor: ServicesAccessor, args: [URI, string, ITextEditorOptions | undefined, EditorViewColumn | undefined]) => { const editorService = accessor.get(IEditorService); - const editorGroupService = accessor.get(IEditorGroupsService); + const editorGroupsService = accessor.get(IEditorGroupsService); + const configurationService = accessor.get(IConfigurationService); + const quickInputService = accessor.get(IQuickInputService); const [resource, id, options, position] = args; - const group = editorGroupService.getGroup(viewColumnToEditorGroup(editorGroupService, position)) ?? editorGroupService.activeGroup; + const group = editorGroupsService.getGroup(viewColumnToEditorGroup(editorGroupsService, position)) ?? editorGroupsService.activeGroup; const textOptions = options ? { ...options, ignoreOverrides: true } : { ignoreOverrides: true }; - const fileEditorInput = editorService.createEditorInput({ resource, forceFile: true }); - if (id === DEFAULT_EDITOR_ID) { - return editorService.openEditor(fileEditorInput, textOptions, position); - } - - const editors = editorService.getEditorOverrides(fileEditorInput, undefined, group); - for (const [handler, data] of editors) { - if (data.id === id) { - return handler.open(fileEditorInput, options, group, id); - } - } - - return undefined; + const input = editorService.createEditorInput({ resource }); + return openEditorWith(input, id, textOptions, group, editorService, configurationService, quickInputService); }); diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index 67e7013e3a..ff6166f668 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -8,12 +8,13 @@ import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IEx 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 { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, CellKind, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +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'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; export class MainThreadNotebookDocument extends Disposable { private _textModel: NotebookTextModel; @@ -63,6 +64,7 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo @INotebookService private _notebookService: INotebookService, @IConfigurationService private readonly configurationService: IConfigurationService, @IEditorService private readonly editorService: IEditorService, + @IAccessibilityService private readonly accessibilityService: IAccessibilityService ) { super(); @@ -85,22 +87,25 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo this._proxy.$updateActiveEditor(e.viewType, e.uri); })); - let userOrder = this.configurationService.getValue('notebook.displayOrder'); - this._proxy.$acceptDisplayOrder({ - defaultOrder: NOTEBOOK_DISPLAY_ORDER, - userOrder: userOrder - }); + const updateOrder = () => { + let userOrder = this.configurationService.getValue('notebook.displayOrder'); + this._proxy.$acceptDisplayOrder({ + defaultOrder: this.accessibilityService.isScreenReaderOptimized() ? ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER : NOTEBOOK_DISPLAY_ORDER, + userOrder: userOrder + }); + }; - this.configurationService.onDidChangeConfiguration(e => { + updateOrder(); + + this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectedKeys.indexOf('notebook.displayOrder') >= 0) { - let userOrder = this.configurationService.getValue('notebook.displayOrder'); - - this._proxy.$acceptDisplayOrder({ - defaultOrder: NOTEBOOK_DISPLAY_ORDER, - userOrder: userOrder - }); + updateOrder(); } - }); + })); + + this._register(this.accessibilityService.onDidChangeScreenReaderOptimized(() => { + updateOrder(); + })); } async $registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, handle: number, preloads: UriComponents[]): Promise { diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index 8278f5e3b4..97f9cc2df3 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -309,8 +309,8 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma this.registerEditorProvider(ModelType.Text, extensionData, viewType, options, capabilities, true); } - public $registerCustomEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, supportsMultipleEditorsPerResource: boolean): void { - this.registerEditorProvider(ModelType.Custom, extensionData, viewType, options, {}, supportsMultipleEditorsPerResource); + public $registerCustomEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, supportsMultipleEditorsPerDocument: boolean): void { + this.registerEditorProvider(ModelType.Custom, extensionData, viewType, options, {}, supportsMultipleEditorsPerDocument); } private registerEditorProvider( @@ -319,14 +319,14 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma viewType: string, options: modes.IWebviewPanelOptions, capabilities: extHostProtocol.CustomTextEditorCapabilities, - supportsMultipleEditorsPerResource: boolean, + supportsMultipleEditorsPerDocument: boolean, ): DisposableStore { if (this._editorProviders.has(viewType)) { throw new Error(`Provider for ${viewType} already registered`); } this._customEditorService.registerCustomEditorCapabilities(viewType, { - supportsMultipleEditorsPerResource + supportsMultipleEditorsPerDocument }); const extension = reviveWebviewExtension(extensionData); @@ -610,8 +610,9 @@ namespace HotExitState { class MainThreadCustomEditorModel extends Disposable implements ICustomEditorModel, IWorkingCopy { - private _hotExitState: HotExitState.State = HotExitState.Allowed; private readonly _fromBackup: boolean = false; + private _hotExitState: HotExitState.State = HotExitState.Allowed; + private _backupId: string | undefined; private _currentEditIndex: number = -1; private _savePoint: number = -1; @@ -716,6 +717,10 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod return this._viewType; } + public get backupId() { + return this._backupId; + } + public pushEdit(editId: number, label: string | undefined) { if (!this._editable) { throw new Error('Document is not editable'); @@ -902,6 +907,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod if (this._hotExitState === pendingState) { this._hotExitState = HotExitState.Allowed; backupData.meta!.backupId = backupId; + this._backupId = backupId; } } catch (e) { // Make sure state has not changed in the meantime diff --git a/src/vs/workbench/api/browser/media/test.svg b/src/vs/workbench/api/browser/media/test.svg deleted file mode 100644 index 569947a033..0000000000 --- a/src/vs/workbench/api/browser/media/test.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index e1aeb7817f..9b067c5082 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -30,6 +30,7 @@ import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/wor import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { Codicon } from 'vs/base/common/codicons'; export interface IUserFriendlyViewsContainerDescriptor { id: string; @@ -53,7 +54,8 @@ const viewsContainerSchema: IJSONSchema = { description: localize('vscode.extension.contributes.views.containers.icon', "Path to the container icon. Icons are 24x24 centered on a 50x40 block and have a fill color of 'rgb(215, 218, 224)' or '#d7dae0'. It is recommended that icons be in SVG, though any image file type is accepted."), type: 'string' } - } + }, + required: ['id', 'title', 'icon'] }; export const viewsContainersContribution: IJSONSchema = { @@ -255,7 +257,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { private registerTestViewContainer(): void { const title = localize('test', "Test"); - const icon = URI.parse(require.toUrl('./media/test.svg')); + const icon = Codicon.beaker.classNames; this.registerCustomViewContainer(TEST_VIEW_CONTAINER_ID, title, icon, TEST_VIEW_CONTAINER_ORDER, undefined, ViewContainerLocation.Sidebar); } @@ -310,7 +312,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { return order; } - private registerCustomViewContainer(id: string, title: string, icon: URI, order: number, extensionId: ExtensionIdentifier | undefined, location: ViewContainerLocation): ViewContainer { + private registerCustomViewContainer(id: string, title: string, icon: URI | string, order: number, extensionId: ExtensionIdentifier | undefined, location: ViewContainerLocation): ViewContainer { let viewContainer = this.viewContainersRegistry.get(id); if (!viewContainer) { diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index ef5c9e1067..bbac2862db 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -72,6 +72,10 @@ const configurationEntrySchema: IJSONSchema = { deprecationMessage: { type: 'string', description: nls.localize('scope.deprecationMessage', 'If set, the property is marked as deprecated and the given message is shown as an explanation.') + }, + markdownDeprecationMessage: { + type: 'string', + description: nls.localize('scope.markdownDeprecationMessage', 'If set, the property is marked as deprecated and the given message is shown as an explanation in the markdown format.') } } } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 9fdc67a7b5..612bc418d6 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -75,6 +75,7 @@ import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostAp import { ExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication'; import { ExtHostTimeline } from 'vs/workbench/api/common/extHostTimeline'; import { ExtHostNotebookConcatDocument } from 'vs/workbench/api/common/extHostNotebookConcatDocument'; +import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; export interface IExtensionApiFactory { (extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode; @@ -93,6 +94,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const uriTransformer = accessor.get(IURITransformerService); const rpcProtocol = accessor.get(IExtHostRpcService); const extHostStorage = accessor.get(IExtHostStorage); + const extensionStoragePaths = accessor.get(IExtensionStoragePaths); const extHostLogService = accessor.get(ILogService); const extHostTunnelService = accessor.get(IExtHostTunnelService); const extHostApiDeprecation = accessor.get(IExtHostApiDeprecationService); @@ -137,7 +139,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostTheming = rpcProtocol.set(ExtHostContext.ExtHostTheming, new ExtHostTheming(rpcProtocol)); const extHostAuthentication = rpcProtocol.set(ExtHostContext.ExtHostAuthentication, new ExtHostAuthentication(rpcProtocol)); const extHostTimeline = rpcProtocol.set(ExtHostContext.ExtHostTimeline, new ExtHostTimeline(rpcProtocol, extHostCommands)); - const extHostWebviews = rpcProtocol.set(ExtHostContext.ExtHostWebviews, new ExtHostWebviews(rpcProtocol, initData.environment, extHostWorkspace, extHostLogService, extHostApiDeprecation, extHostDocuments)); + const extHostWebviews = rpcProtocol.set(ExtHostContext.ExtHostWebviews, new ExtHostWebviews(rpcProtocol, initData.environment, extHostWorkspace, extHostLogService, extHostApiDeprecation, extHostDocuments, extensionStoragePaths)); // Check that no named customers are missing // {{SQL CARBON EDIT}} filter out the services we don't expose @@ -203,6 +205,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I login(providerId: string, scopes: string[]): Thenable { return extHostAuthentication.login(extension, providerId, scopes); }, + logout(providerId: string, sessionId: string): Thenable { + return extHostAuthentication.logout(providerId, sessionId); + }, get onDidChangeSessions(): Event<{ [providerId: string]: vscode.AuthenticationSessionsChangeEvent }> { return extHostAuthentication.onDidChangeSessions; }, @@ -489,10 +494,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension); return extHostTerminalService.onDidWriteTerminalData(listener, thisArg, disposables); }, - getEnvironmentVariableCollection(persistent?: boolean): vscode.EnvironmentVariableCollection { - checkProposedApiEnabled(extension); - return extHostTerminalService.getEnvironmentVariableCollection(extension, persistent); - }, get state() { return extHostWindow.state; }, @@ -589,7 +590,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerCustomEditorProvider: (viewType: string, provider: vscode.CustomTextEditorProvider, options: { webviewOptions?: vscode.WebviewPanelOptions } = {}) => { return extHostWebviews.registerCustomEditorProvider(extension, viewType, provider, options); }, - registerCustomEditorProvider2: (viewType: string, provider: vscode.CustomEditorProvider, options: { webviewOptions?: vscode.WebviewPanelOptions, supportsMultipleEditorsPerResource?: boolean } = {}) => { + registerCustomEditorProvider2: (viewType: string, provider: vscode.CustomReadonlyEditorProvider, options: { webviewOptions?: vscode.WebviewPanelOptions, supportsMultipleEditorsPerDocument?: boolean } = {}) => { checkProposedApiEnabled(extension); return extHostWebviews.registerCustomEditorProvider(extension, viewType, provider, options); }, @@ -855,7 +856,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I extHostLogService.warn('Debug API is disabled in Azure Data Studio'); return undefined!; }, - registerDebugConfigurationProvider(debugType: string, provider: vscode.DebugConfigurationProvider, scope?: vscode.DebugConfigurationProviderScope) { + registerDebugConfigurationProvider(debugType: string, provider: vscode.DebugConfigurationProvider, triggerKind?: vscode.DebugConfigurationProviderTriggerKind) { extHostLogService.warn('Debug API is disabled in Azure Data Studio'); return undefined!; }, @@ -938,6 +939,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension); return extHostNotebook.activeNotebookEditor; }, + onDidChangeNotebookDocument(listener, thisArgs?, disposables?) { + checkProposedApiEnabled(extension); + return extHostNotebook.onDidChangeNotebookDocument(listener, thisArgs, disposables); + }, createConcatTextDocument(notebook, selector) { checkProposedApiEnabled(extension); return new ExtHostNotebookConcatDocument(extHostNotebook, extHostDocuments, notebook, selector); @@ -1065,7 +1070,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I CallHierarchyIncomingCall: extHostTypes.CallHierarchyIncomingCall, CallHierarchyItem: extHostTypes.CallHierarchyItem, DebugConsoleMode: extHostTypes.DebugConsoleMode, - DebugConfigurationProviderScope: extHostTypes.DebugConfigurationProviderScope, + DebugConfigurationProviderTriggerKind: extHostTypes.DebugConfigurationProviderTriggerKind, Decoration: extHostTypes.Decoration, UIKind: UIKind, ColorThemeKind: extHostTypes.ColorThemeKind, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index c6fa74427d..cb34ad7236 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -55,7 +55,7 @@ import { INotebookMimeTypeSelector, IOutput, INotebookDisplayOrder, NotebookCell import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { Dto } from 'vs/base/common/types'; import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; -import { DebugConfigurationProviderScope } from 'vs/workbench/api/common/extHostTypes'; +import { DebugConfigurationProviderTriggerKind } from 'vs/workbench/api/common/extHostTypes'; // {{SQL CARBON EDIT}} import { ITreeItem as sqlITreeItem } from 'sql/workbench/common/views'; @@ -139,6 +139,7 @@ export interface MainThreadCommandsShape extends IDisposable { export interface CommentProviderFeatures { reactionGroup?: modes.CommentReaction[]; reactionHandler?: boolean; + options?: modes.CommentOptions; } export type CommentThreadChanges = Partial<{ @@ -616,7 +617,7 @@ export interface MainThreadWebviewsShape extends IDisposable { $unregisterSerializer(viewType: string): void; $registerTextEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, capabilities: CustomTextEditorCapabilities): void; - $registerCustomEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, supportsMultipleEditorsPerResource: boolean): void; + $registerCustomEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, supportsMultipleEditorsPerDocument: boolean): void; $unregisterEditorProvider(viewType: string): void; $onDidEdit(resource: UriComponents, viewType: string, editId: number, label: string | undefined): void; @@ -850,7 +851,7 @@ export interface MainThreadDebugServiceShape extends IDisposable { $acceptDAMessage(handle: number, message: DebugProtocol.ProtocolMessage): void; $acceptDAError(handle: number, name: string, message: string, stack: string | undefined): void; $acceptDAExit(handle: number, code: number | undefined, signal: string | undefined): void; - $registerDebugConfigurationProvider(type: string, scope: DebugConfigurationProviderScope, hasProvideMethod: boolean, hasResolveMethod: boolean, hasResolve2Method: boolean, hasProvideDaMethod: boolean, handle: number): Promise; + $registerDebugConfigurationProvider(type: string, triggerKind: DebugConfigurationProviderTriggerKind, hasProvideMethod: boolean, hasResolveMethod: boolean, hasResolve2Method: boolean, hasProvideDaMethod: boolean, handle: number): Promise; $registerDebugAdapterDescriptorFactory(type: string, handle: number): Promise; $unregisterDebugConfigurationProvider(handle: number): void; $unregisterDebugAdapterDescriptorFactory(handle: number): void; diff --git a/src/vs/workbench/api/common/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index f589c4b1ba..7dae881f29 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -143,22 +143,22 @@ const newCommands: ApiCommand[] = [ new ApiCommand( 'vscode.executeDefinitionProvider', '_executeDefinitionProvider', 'Execute all definition providers.', [ApiCommandArgument.Uri, ApiCommandArgument.Position], - new ApiCommandResult<(modes.Location | modes.LocationLink)[], (types.Location | vscode.LocationLink)[] | undefined>('A promise that resolves to an array of Location-instances.', mapLocationOrLocationLink) + new ApiCommandResult<(modes.Location | modes.LocationLink)[], (types.Location | vscode.LocationLink)[] | undefined>('A promise that resolves to an array of Location or LocationLink instances.', mapLocationOrLocationLink) ), new ApiCommand( 'vscode.executeTypeDefinitionProvider', '_executeTypeDefinitionProvider', 'Execute all type definition providers.', [ApiCommandArgument.Uri, ApiCommandArgument.Position], - new ApiCommandResult<(modes.Location | modes.LocationLink)[], (types.Location | vscode.LocationLink)[] | undefined>('A promise that resolves to an array of Location-instances.', mapLocationOrLocationLink) + new ApiCommandResult<(modes.Location | modes.LocationLink)[], (types.Location | vscode.LocationLink)[] | undefined>('A promise that resolves to an array of Location or LocationLink instances.', mapLocationOrLocationLink) ), new ApiCommand( 'vscode.executeDeclarationProvider', '_executeDeclarationProvider', 'Execute all declaration providers.', [ApiCommandArgument.Uri, ApiCommandArgument.Position], - new ApiCommandResult<(modes.Location | modes.LocationLink)[], (types.Location | vscode.LocationLink)[] | undefined>('A promise that resolves to an array of Location-instances.', mapLocationOrLocationLink) + new ApiCommandResult<(modes.Location | modes.LocationLink)[], (types.Location | vscode.LocationLink)[] | undefined>('A promise that resolves to an array of Location or LocationLink instances.', mapLocationOrLocationLink) ), new ApiCommand( 'vscode.executeImplementationProvider', '_executeImplementationProvider', 'Execute all implementation providers.', [ApiCommandArgument.Uri, ApiCommandArgument.Position], - new ApiCommandResult<(modes.Location | modes.LocationLink)[], (types.Location | vscode.LocationLink)[] | undefined>('A promise that resolves to an array of Location-instances.', mapLocationOrLocationLink) + new ApiCommandResult<(modes.Location | modes.LocationLink)[], (types.Location | vscode.LocationLink)[] | undefined>('A promise that resolves to an array of Location or LocationLink instances.', mapLocationOrLocationLink) ), new ApiCommand( 'vscode.executeReferenceProvider', '_executeReferenceProvider', 'Execute all reference providers.', diff --git a/src/vs/workbench/api/common/extHostAuthentication.ts b/src/vs/workbench/api/common/extHostAuthentication.ts index 1b95d7e5ca..e9b61940ed 100644 --- a/src/vs/workbench/api/common/extHostAuthentication.ts +++ b/src/vs/workbench/api/common/extHostAuthentication.ts @@ -102,6 +102,15 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { }; } + async logout(providerId: string, sessionId: string): Promise { + const provider = this._authenticationProviders.get(providerId); + if (!provider) { + throw new Error(`No authentication provider with id '${providerId}' is currently registered.`); + } + + return provider.logout(sessionId); + } + registerAuthenticationProvider(provider: vscode.AuthenticationProvider): vscode.Disposable { if (this._authenticationProviders.get(provider.id)) { throw new Error(`An authentication provider with id '${provider.id}' is already registered.`); diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index 47a4cca2ae..e6e99b1cf9 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -451,6 +451,18 @@ class ExtHostCommentController implements vscode.CommentController { this._proxy.$updateCommentControllerFeatures(this.handle, { reactionHandler: !!handler }); } + private _options: modes.CommentOptions | undefined; + + get options() { + return this._options; + } + + set options(options: modes.CommentOptions | undefined) { + this._options = options; + + this._proxy.$updateCommentControllerFeatures(this.handle, { options: this._options }); + } + constructor( private _extension: IExtensionDescription, private _handle: number, diff --git a/src/vs/workbench/api/common/extHostDebugService.ts b/src/vs/workbench/api/common/extHostDebugService.ts index c5677f5aef..b66e01f6f8 100644 --- a/src/vs/workbench/api/common/extHostDebugService.ts +++ b/src/vs/workbench/api/common/extHostDebugService.ts @@ -51,7 +51,7 @@ export interface IExtHostDebugService extends ExtHostDebugServiceShape { addBreakpoints(breakpoints0: vscode.Breakpoint[]): Promise; removeBreakpoints(breakpoints0: vscode.Breakpoint[]): Promise; startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration, options: vscode.DebugSessionOptions): Promise; - registerDebugConfigurationProvider(type: string, provider: vscode.DebugConfigurationProvider, scope: vscode.DebugConfigurationProviderScope): vscode.Disposable; + registerDebugConfigurationProvider(type: string, provider: vscode.DebugConfigurationProvider, trigger: vscode.DebugConfigurationProviderTriggerKind): vscode.Disposable; registerDebugAdapterDescriptorFactory(extension: IExtensionDescription, type: string, factory: vscode.DebugAdapterDescriptorFactory): vscode.Disposable; registerDebugAdapterTrackerFactory(type: string, factory: vscode.DebugAdapterTrackerFactory): vscode.Disposable; asDebugSourceUri(source: vscode.DebugProtocolSource, session?: vscode.DebugSession): vscode.Uri; @@ -299,7 +299,7 @@ export class ExtHostDebugServiceBase implements IExtHostDebugService, ExtHostDeb }); } - public registerDebugConfigurationProvider(type: string, provider: vscode.DebugConfigurationProvider, scope: vscode.DebugConfigurationProviderScope): vscode.Disposable { + public registerDebugConfigurationProvider(type: string, provider: vscode.DebugConfigurationProvider, trigger: vscode.DebugConfigurationProviderTriggerKind): vscode.Disposable { if (!provider) { return new Disposable(() => { }); @@ -312,7 +312,7 @@ export class ExtHostDebugServiceBase implements IExtHostDebugService, ExtHostDeb const handle = this._configProviderHandleCounter++; this._configProviders.push({ type, handle, provider }); - this._debugServiceProxy.$registerDebugConfigurationProvider(type, scope, + this._debugServiceProxy.$registerDebugConfigurationProvider(type, trigger, !!provider.provideDebugConfigurations, !!provider.resolveDebugConfiguration, !!provider.resolveDebugConfigurationWithSubstitutedVariables, diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index d35fff0152..055fefef06 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -16,7 +16,7 @@ import { ExtHostConfiguration, IExtHostConfiguration } from 'vs/workbench/api/co import { ActivatedExtension, EmptyExtension, ExtensionActivationReason, ExtensionActivationTimes, ExtensionActivationTimesBuilder, ExtensionsActivator, IExtensionAPI, IExtensionModule, HostExtension, ExtensionActivationTimesFragment } from 'vs/workbench/api/common/extHostExtensionActivator'; import { ExtHostStorage, IExtHostStorage } from 'vs/workbench/api/common/extHostStorage'; import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; -import { ExtensionActivationError } from 'vs/workbench/services/extensions/common/extensions'; +import { ExtensionActivationError, checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import * as errors from 'vs/base/common/errors'; @@ -33,6 +33,7 @@ import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePa import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService'; +import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; interface ITestRunner { /** Old test runner API, as exported from `vscode/lib/testrunner` */ @@ -78,6 +79,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio protected readonly _extHostConfiguration: ExtHostConfiguration; protected readonly _logService: ILogService; protected readonly _extHostTunnelService: IExtHostTunnelService; + protected readonly _extHostTerminalService: IExtHostTerminalService; protected readonly _mainThreadWorkspaceProxy: MainThreadWorkspaceShape; protected readonly _mainThreadTelemetryProxy: MainThreadTelemetryShape; @@ -107,7 +109,8 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio @ILogService logService: ILogService, @IExtHostInitDataService initData: IExtHostInitDataService, @IExtensionStoragePaths storagePath: IExtensionStoragePaths, - @IExtHostTunnelService extHostTunnelService: IExtHostTunnelService + @IExtHostTunnelService extHostTunnelService: IExtHostTunnelService, + @IExtHostTerminalService extHostTerminalService: IExtHostTerminalService ) { this._hostUtils = hostUtils; this._extHostContext = extHostContext; @@ -117,6 +120,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio this._extHostConfiguration = extHostConfiguration; this._logService = logService; this._extHostTunnelService = extHostTunnelService; + this._extHostTerminalService = extHostTerminalService; this._disposables = new DisposableStore(); this._mainThreadWorkspaceProxy = this._extHostContext.getProxy(MainContext.MainThreadWorkspace); @@ -371,7 +375,11 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio get storagePath() { return that._storagePath.workspaceValue(extensionDescription); }, get globalStoragePath() { return that._storagePath.globalValue(extensionDescription); }, asAbsolutePath(relativePath: string) { return path.join(extensionDescription.extensionLocation.fsPath, relativePath); }, - get logPath() { return path.join(that._initData.logsLocation.fsPath, extensionDescription.identifier.value); } + get logPath() { return path.join(that._initData.logsLocation.fsPath, extensionDescription.identifier.value); }, + get environmentVariableCollection() { + checkProposedApiEnabled(extensionDescription); + return that._extHostTerminalService.getEnvironmentVariableCollection(extensionDescription); + } }); }); } @@ -449,10 +457,11 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio const fileNames: string[] = []; const globPatterns: string[] = []; + const localWithRemote = !this._initData.remote.isRemote && !!this._initData.remote.authority; for (const activationEvent of activationEvents) { if (/^workspaceContains:/.test(activationEvent)) { const fileNameOrGlob = activationEvent.substr('workspaceContains:'.length); - if (fileNameOrGlob.indexOf('*') >= 0 || fileNameOrGlob.indexOf('?') >= 0) { + if (fileNameOrGlob.indexOf('*') >= 0 || fileNameOrGlob.indexOf('?') >= 0 || localWithRemote) { globPatterns.push(fileNameOrGlob); } else { fileNames.push(fileNameOrGlob); diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index fce5a28608..ceaa33761b 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -13,7 +13,7 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions' import { CellKind, CellOutputKind, ExtHostNotebookShape, IMainContext, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice, MainThreadDocumentsShape, INotebookEditorPropertiesChangeData } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; -import { CellEditType, CellUri, diff, ICellEditOperation, ICellInsertEdit, IErrorOutput, INotebookDisplayOrder, INotebookEditData, IOrderedMimeType, IStreamOutput, ITransformedDisplayOutputDto, mimeTypeSupportedByCore, NotebookCellsChangedEvent, NotebookCellsSplice2, sortMimeTypes, ICellDeleteEdit, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, CellUri, diff, ICellEditOperation, ICellInsertEdit, IErrorOutput, INotebookDisplayOrder, INotebookEditData, IOrderedMimeType, IStreamOutput, ITransformedDisplayOutputDto, mimeTypeSupportedByCore, NotebookCellsChangedEvent, NotebookCellsSplice2, sortMimeTypes, ICellDeleteEdit, notebookDocumentMetadataDefaults, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Disposable as VSCodeDisposable } from './extHostTypes'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData'; @@ -247,7 +247,18 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo get isDirty() { return false; } accpetModelChanged(event: NotebookCellsChangedEvent) { - this.$spliceNotebookCells(event.changes); + if (event.kind === NotebookCellsChangeType.ModelChange) { + this.$spliceNotebookCells(event.changes); + } else if (event.kind === NotebookCellsChangeType.Move) { + this.$moveCell(event.index, event.newIdx); + } else if (event.kind === NotebookCellsChangeType.CellClearOutput) { + this.$clearCellOutputs(event.index); + } else if (event.kind === NotebookCellsChangeType.CellsClearOutput) { + this.$clearAllCellOutputs(); + } else if (event.kind === NotebookCellsChangeType.ChangeLanguage) { + this.$changeCellLanguage(event.index, event.language); + } + this._versionId = event.versionId; } @@ -289,6 +300,25 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo }); } + private $moveCell(index: number, newIdx: number) { + const cells = this.cells.splice(index, 1); + this.cells.splice(newIdx, 0, ...cells); + } + + private $clearCellOutputs(index: number) { + const cell = this.cells[index]; + cell.outputs = []; + } + + private $clearAllCellOutputs() { + this.cells.forEach(cell => cell.outputs = []); + } + + private $changeCellLanguage(index: number, language: string) { + const cell = this.cells[index]; + cell.language = language; + } + eventuallyUpdateCellOutputs(cell: ExtHostCell, diffs: ISplice[]) { let renderers = new Set(); let outputDtos: NotebookCellOutputsSplice[] = diffs.map(diff => { @@ -600,8 +630,8 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN private readonly _editors = new Map }>(); private readonly _notebookOutputRenderers = new Map(); - private readonly _onDidChangeNotebookDocument = new Emitter<{ document: ExtHostNotebookDocument, changes: NotebookCellsSplice2[] }>(); - readonly onDidChangeNotebookDocument: Event<{ document: ExtHostNotebookDocument, changes: NotebookCellsSplice2[] }> = 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; @@ -801,7 +831,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN editor.editor.document.accpetModelChanged(event); this._onDidChangeNotebookDocument.fire({ document: editor.editor.document, - changes: event.changes + changes: [event] }); } diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 17fdf4237f..8a66bec58a 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -644,36 +644,36 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ export class EnvironmentVariableCollection implements vscode.EnvironmentVariableCollection { readonly map: Map = new Map(); + private _persistent: boolean = true; - private _disposed = false; + public get persistent(): boolean { return this._persistent; } + public set persistent(value: boolean) { + this._persistent = value; + this._onDidChangeCollection.fire(); + } protected readonly _onDidChangeCollection: Emitter = new Emitter(); get onDidChangeCollection(): Event { return this._onDidChangeCollection && this._onDidChangeCollection.event; } constructor( - readonly persistent: boolean, serialized?: ISerializableEnvironmentVariableCollection ) { this.map = new Map(serialized); } get size(): number { - this._checkDisposed(); return this.map.size; } replace(variable: string, value: string): void { - this._checkDisposed(); this._setIfDiffers(variable, { value, type: EnvironmentVariableMutatorType.Replace }); } append(variable: string, value: string): void { - this._checkDisposed(); this._setIfDiffers(variable, { value, type: EnvironmentVariableMutatorType.Append }); } prepend(variable: string, value: string): void { - this._checkDisposed(); this._setIfDiffers(variable, { value, type: EnvironmentVariableMutatorType.Prepend }); } @@ -686,40 +686,22 @@ export class EnvironmentVariableCollection implements vscode.EnvironmentVariable } get(variable: string): vscode.EnvironmentVariableMutator | undefined { - this._checkDisposed(); return this.map.get(variable); } forEach(callback: (variable: string, mutator: vscode.EnvironmentVariableMutator, collection: vscode.EnvironmentVariableCollection) => any, thisArg?: any): void { - this._checkDisposed(); this.map.forEach((value, key) => callback.call(thisArg, key, value, this)); } delete(variable: string): void { - this._checkDisposed(); this.map.delete(variable); this._onDidChangeCollection.fire(); } clear(): void { - this._checkDisposed(); this.map.clear(); this._onDidChangeCollection.fire(); } - - dispose(): void { - if (!this._disposed) { - this._disposed = true; - this.map.clear(); - this._onDidChangeCollection.fire(); - } - } - - protected _checkDisposed() { - if (this._disposed) { - throw new Error('EnvironmentVariableCollection has already been disposed'); - } - } } export class WorkerExtHostTerminalService extends BaseExtHostTerminalService { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index df7150fbba..5e27ac4eff 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -2652,18 +2652,13 @@ export enum DebugConsoleMode { MergeWithParent = 1 } -/** - * VS Code can call the `provideDebugConfigurations` method of a `DebugConfigurationProvider` in two situations (aka 'scopes'): - * to provide the initial debug configurations for a newly create launch.json or to provide debug configurations dynamically based on context. - * A scope value is used when registering a `DebugConfigurationProvider` with `debug.registerDebugConfigurationProvider`. - */ -export enum DebugConfigurationProviderScope { +export enum DebugConfigurationProviderTriggerKind { /** - * The 'initial' scope denotes a context where all debug configurations for a newly created launch.json are needed. + * `DebugConfigurationProvider.provideDebugConfigurations` is called to provide the initial debug configurations for a newly created launch.json. */ Initial = 1, /** - * The 'dynamic' scope denotes a context where all debug configurations for the current context are needed. + * `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 } diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index e43a976e1c..401ce6af8c 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -5,7 +5,10 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; +import { hash } from 'vs/base/common/hash'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; +import { joinPath } from 'vs/base/common/resources'; import { URI, UriComponents } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import * as modes from 'vs/editor/common/modes'; @@ -13,6 +16,7 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions' import { ILogService } from 'vs/platform/log/common/log'; import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; +import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; @@ -265,15 +269,17 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa class CustomDocumentStoreEntry { + private _backupCounter = 1; + constructor( public readonly document: vscode.CustomDocument, + private readonly _storagePath: string, ) { } private readonly _edits = new Cache('custom documents'); private _backup?: vscode.CustomDocumentBackup; - addEdit(item: vscode.CustomDocumentEditEvent): number { return this._edits.add([item]); } @@ -298,13 +304,17 @@ class CustomDocumentStoreEntry { } } + getNewBackupUri(): URI { + return joinPath(URI.file(this._storagePath), hashPath(this.document.uri) + (this._backupCounter++)); + } + updateBackup(backup: vscode.CustomDocumentBackup): void { - this._backup?.dispose(); + this._backup?.delete(); this._backup = backup; } disposeBackup(): void { - this._backup?.dispose(); + this._backup?.delete(); this._backup = undefined; } @@ -324,12 +334,12 @@ class CustomDocumentStore { return this._documents.get(this.key(viewType, resource)); } - public add(viewType: string, document: vscode.CustomDocument): CustomDocumentStoreEntry { + public add(viewType: string, document: vscode.CustomDocument, storagePath: string): CustomDocumentStoreEntry { const key = this.key(viewType, document.uri); if (this._documents.has(key)) { throw new Error(`Document already exists for viewType:${viewType} resource:${document.uri}`); } - const entry = new CustomDocumentStoreEntry(document); + const entry = new CustomDocumentStoreEntry(document, storagePath); this._documents.set(key, entry); return entry; } @@ -357,7 +367,7 @@ type ProviderEntry = { } | { readonly extension: IExtensionDescription; readonly type: WebviewEditorType.Custom; - readonly provider: vscode.CustomEditorProvider; + readonly provider: vscode.CustomReadonlyEditorProvider; }; class EditorProviderStore { @@ -367,7 +377,7 @@ class EditorProviderStore { return this.add(WebviewEditorType.Text, viewType, extension, provider); } - public addCustomProvider(viewType: string, extension: IExtensionDescription, provider: vscode.CustomEditorProvider): vscode.Disposable { + public addCustomProvider(viewType: string, extension: IExtensionDescription, provider: vscode.CustomReadonlyEditorProvider): vscode.Disposable { return this.add(WebviewEditorType.Custom, viewType, extension, provider); } @@ -375,7 +385,7 @@ class EditorProviderStore { return this._providers.get(viewType); } - private add(type: WebviewEditorType, viewType: string, extension: IExtensionDescription, provider: vscode.CustomTextEditorProvider | vscode.CustomEditorProvider): vscode.Disposable { + private add(type: WebviewEditorType, viewType: string, extension: IExtensionDescription, provider: vscode.CustomTextEditorProvider | vscode.CustomReadonlyEditorProvider): vscode.Disposable { if (this._providers.has(viewType)) { throw new Error(`Provider for viewType:${viewType} already registered`); } @@ -409,6 +419,7 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape { private readonly _logService: ILogService, private readonly _deprecationService: IExtHostApiDeprecationService, private readonly _extHostDocuments: ExtHostDocuments, + private readonly _extensionStoragePaths?: IExtensionStoragePaths, ) { this._proxy = mainContext.getProxy(extHostProtocol.MainContext.MainThreadWebviews); } @@ -456,8 +467,8 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape { public registerCustomEditorProvider( extension: IExtensionDescription, viewType: string, - provider: vscode.CustomEditorProvider | vscode.CustomTextEditorProvider, - options: { webviewOptions?: vscode.WebviewPanelOptions, supportsMultipleEditorsPerResource?: boolean }, + provider: vscode.CustomReadonlyEditorProvider | vscode.CustomTextEditorProvider, + options: { webviewOptions?: vscode.WebviewPanelOptions, supportsMultipleEditorsPerDocument?: boolean }, ): vscode.Disposable { const disposables = new DisposableStore(); if ('resolveCustomTextEditor' in provider) { @@ -467,7 +478,20 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape { }); } else { disposables.add(this._editorProviders.addCustomProvider(viewType, extension, provider)); - this._proxy.$registerCustomEditorProvider(toExtensionData(extension), viewType, options.webviewOptions || {}, !!options.supportsMultipleEditorsPerResource); + + if (this.supportEditing(provider)) { + disposables.add(provider.onDidChangeCustomDocument(e => { + const entry = this.getCustomDocumentEntry(viewType, e.document.uri); + if (isEditEvent(e)) { + const editId = entry.addEdit(e); + this._proxy.$onDidEdit(e.document.uri, viewType, editId, e.label); + } else { + this._proxy.$onContentChange((e).document.uri, viewType); // {{SQL CARBON EDIT}} strict-null-checks + } + })); + } + + this._proxy.$registerCustomEditorProvider(toExtensionData(extension), viewType, options.webviewOptions || {}, !!options.supportsMultipleEditorsPerDocument); } return extHostTypes.Disposable.from( @@ -567,20 +591,11 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape { const revivedResource = URI.revive(resource); const document = await entry.provider.openCustomDocument(revivedResource, { backupId }, cancellation); - const documentEntry = this._documents.add(viewType, document); - if (this.isEditable(document)) { - document.onDidChange(e => { - if (isEditEvent(e)) { - const editId = documentEntry.addEdit(e); - this._proxy.$onDidEdit(document.uri, viewType, editId, e.label); - } else { - this._proxy.$onContentChange(document.uri, viewType); - } - }); - } + const storageRoot = this._extensionStoragePaths?.workspaceValue(entry.extension) ?? this._extensionStoragePaths?.globalValue(entry.extension); + this._documents.add(viewType, document, storageRoot!); - return { editable: this.isEditable(document) }; + return { editable: this.supportEditing(entry.provider) }; } async $disposeCustomDocument(resource: UriComponents, viewType: string): Promise { @@ -674,29 +689,33 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape { async $revert(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise { const entry = this.getCustomDocumentEntry(viewType, resourceComponents); - const document = this.getEditableCustomDocument(viewType, resourceComponents); - await document.revert(cancellation); + const provider = this.getCustomEditorProvider(viewType); + await provider.revertCustomDocument(entry.document, cancellation); entry.disposeBackup(); } async $onSave(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise { const entry = this.getCustomDocumentEntry(viewType, resourceComponents); - const document = this.getEditableCustomDocument(viewType, resourceComponents); - await document.save(cancellation); + const provider = this.getCustomEditorProvider(viewType); + await provider.saveCustomDocument(entry.document, cancellation); entry.disposeBackup(); } async $onSaveAs(resourceComponents: UriComponents, viewType: string, targetResource: UriComponents, cancellation: CancellationToken): Promise { - const document = this.getEditableCustomDocument(viewType, resourceComponents); - return document.saveAs(URI.revive(targetResource), cancellation); + const entry = this.getCustomDocumentEntry(viewType, resourceComponents); + const provider = this.getCustomEditorProvider(viewType); + return provider.saveCustomDocumentAs(entry.document, URI.revive(targetResource), cancellation); } async $backup(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise { const entry = this.getCustomDocumentEntry(viewType, resourceComponents); - const document = this.getEditableCustomDocument(viewType, resourceComponents); - const backup = await document.backup(cancellation); + const provider = this.getCustomEditorProvider(viewType); + + const backup = await provider.backupCustomDocument(entry.document, { + destination: entry.getNewBackupUri(), + }, cancellation); entry.updateBackup(backup); - return backup.backupId; + return backup.id; } private getWebviewPanel(handle: extHostProtocol.WebviewPanelHandle): ExtHostWebviewEditor | undefined { @@ -711,16 +730,19 @@ export class ExtHostWebviews implements extHostProtocol.ExtHostWebviewsShape { return entry; } - private isEditable(document: vscode.CustomDocument): document is vscode.EditableCustomDocument { - return !!(document as vscode.EditableCustomDocument).onDidChange; - } - - private getEditableCustomDocument(viewType: string, resource: UriComponents): vscode.EditableCustomDocument { - const { document } = this.getCustomDocumentEntry(viewType, resource); - if (!this.isEditable(document)) { + private getCustomEditorProvider(viewType: string): vscode.CustomEditorProvider { + const entry = this._editorProviders.get(viewType); + const provider = entry?.provider; + if (!provider || !this.supportEditing(provider)) { throw new Error('Custom document is not editable'); } - return document; + return provider; + } + + private supportEditing( + provider: vscode.CustomTextEditorProvider | vscode.CustomEditorProvider | vscode.CustomReadonlyEditorProvider + ): provider is vscode.CustomEditorProvider { + return !!(provider as vscode.CustomEditorProvider).onDidChangeCustomDocument; } } @@ -753,3 +775,8 @@ function isEditEvent(e: vscode.CustomDocumentContentChangeEvent | vscode.CustomD return typeof (e as vscode.CustomDocumentEditEvent).undo === 'function' && typeof (e as vscode.CustomDocumentEditEvent).redo === 'function'; } + +function hashPath(resource: URI): string { + const str = resource.scheme === Schemas.file || resource.scheme === Schemas.untitled ? resource.fsPath : resource.toString(); + return hash(str) + ''; +} diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index 48d8d6a606..bcdf6ffcfa 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -97,6 +97,7 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase { cwd: args.cwd, name: args.title || nls.localize('debug.terminal.title', "debuggee"), }; + // @ts-ignore delete args.cwd; this._integratedTerminalInstance = this._terminalService.createTerminalFromOptions(options); } diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index 27cbc65c82..bc7c77ed2f 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -23,7 +23,6 @@ import { getMainProcessParentEnv } from 'vs/workbench/contrib/terminal/node/term import { BaseExtHostTerminalService, ExtHostTerminal, EnvironmentVariableCollection } from 'vs/workbench/api/common/extHostTerminalService'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { dispose } from 'vs/base/common/lifecycle'; import { serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared'; import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; import { MergedEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableCollection'; @@ -227,22 +226,12 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { this._isWorkspaceShellAllowed = isAllowed; } - public getEnvironmentVariableCollection(extension: IExtensionDescription, persistent: boolean = false): vscode.EnvironmentVariableCollection { - let collection: EnvironmentVariableCollection | undefined; - if (persistent) { - // If persistent is specified, return the current collection if it exists - collection = this._environmentVariableCollections.get(extension.identifier.value); - - // If persistence changed then create a new collection - if (collection && !collection.persistent) { - collection = undefined; - } - } + public getEnvironmentVariableCollection(extension: IExtensionDescription): vscode.EnvironmentVariableCollection { + let collection = this._environmentVariableCollections.get(extension.identifier.value); if (!collection) { - // If not persistent, clear out the current collection and create a new one - dispose(this._environmentVariableCollections.get(extension.identifier.value)); - collection = new EnvironmentVariableCollection(persistent); + // TODO: Disable dispose + collection = new EnvironmentVariableCollection(); this._setEnvironmentVariableCollection(extension.identifier.value, collection); } @@ -257,7 +246,7 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { public $initEnvironmentVariableCollections(collections: [string, ISerializableEnvironmentVariableCollection][]): void { collections.forEach(entry => { const extensionIdentifier = entry[0]; - const collection = new EnvironmentVariableCollection(true, entry[1]); + const collection = new EnvironmentVariableCollection(entry[1]); this._setEnvironmentVariableCollection(extensionIdentifier, collection); }); } diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 0e59b27275..10d178652f 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -498,6 +498,12 @@ export class ResetViewLocationsAction extends Action { this.viewDescriptorService.moveViewsToContainer([viewDescriptor], defaultContainer); } }); + + const defaultContainerLocation = this.viewDescriptorService.getDefaultViewContainerLocation(viewContainer); + const currentContainerLocation = this.viewDescriptorService.getViewContainerLocation(viewContainer); + if (defaultContainerLocation !== null && currentContainerLocation !== defaultContainerLocation) { + this.viewDescriptorService.moveViewContainerToLocation(viewContainer, defaultContainerLocation); + } }); } } diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index a04043fc44..6ff9829ea0 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -7,7 +7,7 @@ import { Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IContextKeyService, IContextKey, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext } from 'vs/platform/contextkey/common/contextkeys'; -import { ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, TEXT_DIFF_EDITOR_ID, SplitEditorsVertically, InEditorZenModeContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorIsReadonlyContext, EditorAreaVisibleContext, DirtyWorkingCopiesContext, ActiveEditorAvailableEditorsContext } from 'vs/workbench/common/editor'; +import { ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, TEXT_DIFF_EDITOR_ID, SplitEditorsVertically, InEditorZenModeContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorIsReadonlyContext, EditorAreaVisibleContext, DirtyWorkingCopiesContext, ActiveEditorAvailableEditorIdsContext } from 'vs/workbench/common/editor'; import { trackFocus, addDisposableListener, EventType } from 'vs/base/browser/dom'; import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -41,7 +41,7 @@ export class WorkbenchContextKeysHandler extends Disposable { private activeEditorContext: IContextKey; private activeEditorIsReadonly: IContextKey; - private activeEditorAvailableEditors: IContextKey; + private activeEditorAvailableEditorIds: IContextKey; private activeEditorGroupEmpty: IContextKey; private activeEditorGroupIndex: IContextKey; @@ -92,7 +92,7 @@ export class WorkbenchContextKeysHandler extends Disposable { // Editors this.activeEditorContext = ActiveEditorContext.bindTo(this.contextKeyService); this.activeEditorIsReadonly = ActiveEditorIsReadonlyContext.bindTo(this.contextKeyService); - this.activeEditorAvailableEditors = ActiveEditorAvailableEditorsContext.bindTo(this.contextKeyService); + this.activeEditorAvailableEditorIds = ActiveEditorAvailableEditorIdsContext.bindTo(this.contextKeyService); this.editorsVisibleContext = EditorsVisibleContext.bindTo(this.contextKeyService); this.textCompareEditorVisibleContext = TextCompareEditorVisibleContext.bindTo(this.contextKeyService); this.textCompareEditorActiveContext = TextCompareEditorActiveContext.bindTo(this.contextKeyService); @@ -210,12 +210,12 @@ export class WorkbenchContextKeysHandler extends Disposable { this.activeEditorContext.set(activeEditorPane.getId()); this.activeEditorIsReadonly.set(activeEditorPane.input.isReadonly()); - const editors = this.editorService.getEditorOverrides(activeEditorPane.input, undefined, activeGroup); - this.activeEditorAvailableEditors.set(editors.map(([_, entry]) => entry.id).join(',')); + const editors = activeEditorPane.input.resource ? this.editorService.getEditorOverrides(activeEditorPane.input.resource, undefined, activeGroup) : []; + this.activeEditorAvailableEditorIds.set(editors.map(([_, entry]) => entry.id).join(',')); } else { this.activeEditorContext.reset(); this.activeEditorIsReadonly.reset(); - this.activeEditorAvailableEditors.reset(); + this.activeEditorAvailableEditorIds.reset(); } } diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 33bfa91574..da111c4874 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -23,7 +23,7 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { ITitleService } from 'vs/workbench/services/title/common/titleService'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { LifecyclePhase, StartupKind, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; -import { MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility } from 'vs/platform/windows/common/windows'; +import { MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility, IPath } from 'vs/platform/windows/common/windows'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IEditor } from 'vs/editor/common/editorCommon'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -41,6 +41,8 @@ import { INotificationService, NotificationsFilter } from 'vs/platform/notificat import { IThemeService } from 'vs/platform/theme/common/themeService'; import { WINDOW_ACTIVE_BORDER, WINDOW_INACTIVE_BORDER } from 'vs/workbench/common/theme'; import { LineNumbersType } from 'vs/editor/common/config/editorOptions'; +import { ActivitybarPart } from 'vs/workbench/browser/parts/activitybar/activitybarPart'; +import { URI } from 'vs/base/common/uri'; enum Settings { ACTIVITYBAR_VISIBLE = 'workbench.activityBar.visible', @@ -82,6 +84,21 @@ enum Classes { WINDOW_BORDER = 'border' } +interface PanelActivityState { + id: string; + name?: string; + pinned: boolean; + order: number; + visible: boolean; +} + +interface SideBarActivityState { + id: string; + pinned: boolean; + order: number; + visible: boolean; +} + export abstract class Layout extends Disposable implements IWorkbenchLayoutService { _serviceBrand: undefined; @@ -450,6 +467,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } private initLayoutState(lifecycleService: ILifecycleService, fileService: IFileService): void { + this.applyDefaultLayout(this.environmentService, this.storageService); // Fullscreen this.state.fullscreen = isFullscreen(); @@ -471,7 +489,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Only restore last viewlet if window was reloaded or we are in development mode let viewletToRestore: string; - if (!this.environmentService.isBuilt || lifecycleService.startupKind === StartupKind.ReloadedWindow) { + if (!this.environmentService.isBuilt || lifecycleService.startupKind === StartupKind.ReloadedWindow || isWeb) { viewletToRestore = this.storageService.get(SidebarPart.activeViewletSettingsKey, StorageScope.WORKSPACE, this.viewletService.getDefaultViewletId()); } else { viewletToRestore = this.viewletService.getDefaultViewletId(); @@ -527,18 +545,181 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } + private applyDefaultLayout(environmentService: IWorkbenchEnvironmentService, storageService: IStorageService) { + const defaultLayout = environmentService.options?.defaultLayout; + if (!defaultLayout || !defaultLayout.firstRun) { + return; + } + + const { sidebar } = defaultLayout; + if (sidebar) { + if (sidebar.visible !== undefined) { + if (sidebar.visible) { + storageService.remove(Storage.SIDEBAR_HIDDEN, StorageScope.WORKSPACE); + } else { + storageService.store(Storage.SIDEBAR_HIDDEN, true, StorageScope.WORKSPACE); + } + } + + if (sidebar.containers?.length) { + const sidebarState: SideBarActivityState[] = []; + + let order = -1; + for (const container of sidebar.containers.sort((a, b) => (a.order ?? 1) - (b.order ?? 1))) { + let viewletId; + switch (container.id) { + case 'explorer': + viewletId = 'workbench.view.explorer'; + break; + case 'run': + viewletId = 'workbench.view.debug'; + break; + case 'scm': + viewletId = 'workbench.view.scm'; + break; + case 'search': + viewletId = 'workbench.view.search'; + break; + case 'extensions': + viewletId = 'workbench.view.extensions'; + break; + case 'remote': + viewletId = 'workbench.view.remote'; + break; + default: + viewletId = `workbench.view.extension.${container.id}`; + } + + if (container.active) { + storageService.store(SidebarPart.activeViewletSettingsKey, viewletId, StorageScope.WORKSPACE); + } + + if (container.order !== undefined || (container.active === undefined && (container).visible !== undefined)) { // {{SQL CARBON EDIT}} strict-null-checks + order = container.order ?? (order + 1); + const state: SideBarActivityState = { + id: viewletId, + order: order, + pinned: (container.active || (container).visible) ?? true, // {{SQL CARBON EDIT}} strict-null-checks + visible: (container.active || (container).visible) ?? true // {{SQL CARBON EDIT}} strict-null-checks + }; + + sidebarState.push(state); + } + + if (container.views !== undefined) { + const viewsState: { id: string, isHidden?: boolean, order?: number }[] = []; + const viewsWorkspaceState: { [id: string]: { collapsed: boolean, isHidden?: boolean, size?: number } } = {}; + + for (const view of container.views) { + if (view.order !== undefined || view.visible !== undefined) { + viewsState.push({ + id: view.id, + isHidden: view.visible === undefined ? undefined : !view.visible, + order: view.order === undefined ? undefined : view.order + }); + } + + if (view.collapsed !== undefined) { + viewsWorkspaceState[view.id] = { + collapsed: view.collapsed, + isHidden: view.visible === undefined ? undefined : !view.visible, + }; + } + } + + storageService.store(`${viewletId}.state.hidden`, JSON.stringify(viewsState), StorageScope.GLOBAL); + storageService.store(`${viewletId}.state`, JSON.stringify(viewsWorkspaceState), StorageScope.WORKSPACE); + } + } + + if (sidebarState.length) { + storageService.store(ActivitybarPart.PINNED_VIEWLETS, JSON.stringify(sidebarState), StorageScope.GLOBAL); + } + } + } + + const { panel } = defaultLayout; + if (panel) { + if (panel.visible !== undefined) { + if (panel.visible) { + storageService.store(Storage.PANEL_HIDDEN, false, StorageScope.WORKSPACE); + } else { + storageService.remove(Storage.PANEL_HIDDEN, StorageScope.WORKSPACE); + } + } + + if (panel.containers?.length) { + const panelState: PanelActivityState[] = []; + + let order = -1; + for (const container of panel.containers.sort((a, b) => (a.order ?? 1) - (b.order ?? 1))) { + let name; + let panelId = container.id; + switch (panelId) { + case 'terminal': + name = 'Terminal'; + panelId = 'workbench.panel.terminal'; + break; + case 'debug': + name = 'Debug Console'; + panelId = 'workbench.panel.repl'; + break; + case 'problems': + name = 'Problems'; + panelId = 'workbench.panel.markers'; + break; + case 'output': + name = 'Output'; + panelId = 'workbench.panel.output'; + break; + case 'comments': + name = 'Comments'; + panelId = 'workbench.panel.comments'; + break; + case 'refactor': + name = 'Refactor Preview'; + panelId = 'refactorPreview'; + break; + default: + continue; + } + + if (container.active) { + storageService.store(PanelPart.activePanelSettingsKey, panelId, StorageScope.WORKSPACE); + } + + if (container.order !== undefined || (container.active === undefined && (container).visible !== undefined)) { // {{SQL CARBON EDIT}} strict-null-checks + order = container.order ?? (order + 1); + const state: PanelActivityState = { + id: panelId, + name: name, + order: order, + pinned: (container.active || (container).visible) ?? true, // {{SQL CARBON EDIT}} strict-null-checks + visible: (container.active || (container).visible) ?? true // {{SQL CARBON EDIT}} strict-null-checks + }; + + panelState.push(state); + } + } + + if (panelState.length) { + storageService.store(PanelPart.PINNED_PANELS, JSON.stringify(panelState), StorageScope.GLOBAL); + } + } + } + } + private resolveEditorsToOpen(fileService: IFileService): Promise | IResourceEditorInputType[] { - const configuration = this.environmentService.configuration; - const hasInitialFilesToOpen = this.hasInitialFilesToOpen(); + const initialFilesToOpen = this.getInitialFilesToOpen(); // Only restore editors if we are not instructed to open files initially - this.state.editor.restoreEditors = !hasInitialFilesToOpen; + this.state.editor.restoreEditors = initialFilesToOpen === undefined; // Files to open, diff or create - if (hasInitialFilesToOpen) { + if (initialFilesToOpen !== undefined) { // Files to diff is exclusive - return pathsToEditors(configuration.filesToDiff, fileService).then(filesToDiff => { + return pathsToEditors(initialFilesToOpen.filesToDiff, fileService).then(filesToDiff => { if (filesToDiff?.length === 2) { return [{ leftResource: filesToDiff[0].resource, @@ -549,7 +730,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } // Otherwise: Open/Create files - return pathsToEditors(configuration.filesToOpenOrCreate, fileService); + return pathsToEditors(initialFilesToOpen.filesToOpenOrCreate, fileService); }); } @@ -571,13 +752,26 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return []; } - private hasInitialFilesToOpen(): boolean { - const configuration = this.environmentService.configuration; + private getInitialFilesToOpen(): { filesToOpenOrCreate?: IPath[], filesToDiff?: IPath[] } | undefined { + const defaultLayout = this.environmentService.options?.defaultLayout; + if (defaultLayout?.firstRun && defaultLayout?.editors?.length) { + // + return { + filesToOpenOrCreate: defaultLayout.editors + .sort((a, b) => (a.active ? -1 : 1) - (b.active ? -1 : 1)) + .map(f => ({ fileUri: URI.file(f.path).with({ scheme: f.scheme }), inactive: !f.active })) + }; + } - return !!( - (configuration.filesToOpenOrCreate && configuration.filesToOpenOrCreate.length > 0) || - (configuration.filesToDiff && configuration.filesToDiff.length > 0) - ); + const configuration = this.environmentService.configuration; + if (configuration.filesToOpenOrCreate || configuration.filesToDiff) { + return { + filesToOpenOrCreate: configuration.filesToOpenOrCreate, + filesToDiff: configuration.filesToDiff + }; + } + + return undefined; } private updatePanelPosition() { diff --git a/src/vs/workbench/browser/panecomposite.ts b/src/vs/workbench/browser/panecomposite.ts index 1e34e861a3..0203f9e113 100644 --- a/src/vs/workbench/browser/panecomposite.ts +++ b/src/vs/workbench/browser/panecomposite.ts @@ -59,8 +59,8 @@ export class PaneComposite extends Composite implements IPaneComposite { return this.viewPaneContainer.getOptimalWidth(); } - openView(id: string, focus?: boolean): IView { - return this.viewPaneContainer.openView(id, focus); + openView(id: string, focus?: boolean): T | undefined { + return this.viewPaneContainer.openView(id, focus) as T; } getViewPaneContainer(): ViewPaneContainer { diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index b5c9ea761c..6c7f2a8f40 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -73,7 +73,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { _serviceBrand: undefined; private static readonly ACTION_HEIGHT = 48; - private static readonly PINNED_VIEWLETS = 'workbench.activity.pinnedViewlets2'; + static readonly PINNED_VIEWLETS = 'workbench.activity.pinnedViewlets2'; private static readonly PLACEHOLDER_VIEWLETS = 'workbench.activity.placeholderViewlets'; //#region IView @@ -87,6 +87,9 @@ export class ActivitybarPart extends Part implements IActivityBarService { private content: HTMLElement | undefined; + private homeBar: ActionBar | undefined; + private homeBarContainer: HTMLElement | undefined; + private menuBar: CustomMenubarControl | undefined; private menuBarContainer: HTMLElement | undefined; @@ -155,7 +158,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { (id: string, focus?: boolean) => this.viewletService.openViewlet(id, focus), (from: string, to: string, before?: Before2D) => this.compositeBar.move(from, to, before?.verticallyBefore) ), - compositeSize: 50, + compositeSize: 52, colors: (theme: IColorTheme) => this.getActivitybarItemColors(theme), overflowActionSize: ActivitybarPart.ACTION_HEIGHT })); @@ -353,20 +356,20 @@ export class ActivitybarPart extends Part implements IActivityBarService { } private createHomeBar(command: string, title: string, icon: Codicon): void { - const homeBarContainer = document.createElement('div'); - homeBarContainer.setAttribute('aria-label', nls.localize('homeIndicator', "Home")); - homeBarContainer.setAttribute('role', 'toolbar'); - addClass(homeBarContainer, 'home-bar'); + this.homeBarContainer = document.createElement('div'); + this.homeBarContainer.setAttribute('aria-label', nls.localize('homeIndicator', "Home")); + this.homeBarContainer.setAttribute('role', 'toolbar'); + addClass(this.homeBarContainer, 'home-bar'); - const homeActionBar = this._register(new ActionBar(homeBarContainer, { + this.homeBar = this._register(new ActionBar(this.homeBarContainer, { orientation: ActionsOrientation.VERTICAL, animated: false })); - homeActionBar.push(this._register(this.instantiationService.createInstance(HomeAction, command, title, icon)), { icon: true, label: false }); + this.homeBar.push(this._register(this.instantiationService.createInstance(HomeAction, command, title, icon)), { icon: true, label: false }); const content = assertIsDefined(this.content); - content.prepend(homeBarContainer); + content.prepend(this.homeBarContainer); } updateStyles(): void { @@ -582,12 +585,15 @@ export class ActivitybarPart extends Part implements IActivityBarService { // Layout composite bar let availableHeight = contentAreaSize.height; - if (this.globalActivityActionBar) { - availableHeight -= (this.globalActivityActionBar.viewItems.length * ActivitybarPart.ACTION_HEIGHT); // adjust height for global actions showing + if (this.homeBarContainer) { + availableHeight -= this.homeBarContainer.clientHeight; } if (this.menuBarContainer) { availableHeight -= this.menuBarContainer.clientHeight; } + if (this.globalActivityActionBar) { + availableHeight -= (this.globalActivityActionBar.viewItems.length * ActivitybarPart.ACTION_HEIGHT); // adjust height for global actions showing + } this.compositeBar.layout(new Dimension(width, availableHeight)); } @@ -640,10 +646,11 @@ export class ActivitybarPart extends Part implements IActivityBarService { for (const { when } of viewContainerModel.allViewDescriptors) { views.push({ when: when ? when.serialize() : undefined }); } + const cacheIcon = URI.isUri(viewContainerModel.icon) ? viewContainerModel.icon.scheme === Schemas.file : true; state.push({ id: compositeItem.id, name: viewContainerModel.title, - icon: URI.isUri(viewContainerModel.icon) && viewContainerModel.icon.scheme === Schemas.file ? viewContainerModel.icon : viewContainerModel.icon, + icon: cacheIcon ? viewContainerModel.icon : undefined, views, pinned: compositeItem.pinned, order: compositeItem.order, diff --git a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css index 0783037a87..40466f76c8 100644 --- a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css +++ b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css @@ -5,6 +5,7 @@ .monaco-workbench .part.activitybar { width: 48px; + height: 100%; } .monaco-workbench .activitybar > .content { @@ -17,7 +18,7 @@ /* * Fix menu jumping and background inheritance in Safari */ -.monaco-workbench .activitybar > .content { +.monaco-workbench.safari .activitybar > .content { position: absolute; background-color: inherit; } diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index 6a93a46801..8564acbb80 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -59,19 +59,10 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { return; } - this.viewDescriptorService.moveViewToLocation(viewsToMove[0], this.targetContainerLocation); - const newContainer = this.viewDescriptorService.getViewContainerById(viewsToMove[0].id)!; - this.moveComposite(newContainer.id, targetCompositeId, before); + this.viewDescriptorService.moveViewContainerToLocation(currentContainer, this.targetContainerLocation); + this.moveComposite(currentContainer.id, targetCompositeId, before); - if (viewsToMove.length > 1) { - this.viewDescriptorService.moveViewsToContainer(viewsToMove.slice(1), newContainer); - } - - this.openComposite(newContainer.id, true).then(composite => { - if (composite && viewsToMove.length === 1) { - composite.openView(viewsToMove[0].id, true); - } - }); + this.openComposite(currentContainer.id, true); } } @@ -240,6 +231,9 @@ export class CompositeBar extends Widget implements ICompositeBar { onDragLeave: (e: IDraggedCompositeData) => { toggleClass(parent, 'dragged-over', false); }, + onDragEnd: (e: IDraggedCompositeData) => { + toggleClass(parent, 'dragged-over', false); + }, onDrop: (e: IDraggedCompositeData) => { const pinnedItems = this.getPinnedComposites(); this.options.dndHandler.drop(e.dragAndDropData, pinnedItems[pinnedItems.length - 1].id, e.eventData, { horizontallyBefore: false, verticallyBefore: false }); diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index 6a51b2f7be..14a5c6438d 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -17,7 +17,7 @@ import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { DelayedDragHandler } from 'vs/base/browser/dnd'; import { IActivity } from 'vs/workbench/common/activity'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { Emitter } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { CompositeDragAndDropObserver, ICompositeDragAndDrop, Before2D } from 'vs/workbench/browser/dnd'; import { Color } from 'vs/base/common/color'; import { Codicon } from 'vs/base/common/codicons'; @@ -477,32 +477,34 @@ export class CompositeActionViewItem extends ActivityActionViewItem { CompositeActionViewItem.manageExtensionAction = instantiationService.createInstance(ManageExtensionAction); } - this._register(compositeActivityAction.onDidChangeActivity(() => { this.compositeActivity = undefined; this.updateActivity(); }, this)); + this._register(compositeActivityAction.onDidChangeActivity(() => { + this.compositeActivity = undefined; + this.updateActivity(); + }, this)); + this._register(Event.any( + compositeActivityAction.onDidChangeActivity, + Event.filter(keybindingService.onDidUpdateKeybindings, () => this.compositeActivity!.name !== this.getActivtyName()) + )(() => { + if (this.compositeActivity && this.compositeActivity.name !== this.getActivtyName()) { + this.compositeActivity = undefined; + this.updateActivity(); + } + })); } protected get activity(): IActivity { if (!this.compositeActivity) { - let activityName: string; - const keybinding = typeof this.compositeActivityAction.activity.keybindingId === 'string' ? this.getKeybindingLabel(this.compositeActivityAction.activity.keybindingId) : null; - if (keybinding) { - activityName = nls.localize('titleKeybinding', "{0} ({1})", this.compositeActivityAction.activity.name, keybinding); - } else { - activityName = this.compositeActivityAction.activity.name; - } - - this.compositeActivity = { ...this.compositeActivityAction.activity, ... { name: activityName } }; + this.compositeActivity = { + ...this.compositeActivityAction.activity, + ... { name: this.getActivtyName() } + }; } - return this.compositeActivity; } - private getKeybindingLabel(id: string): string | null { - const kb = this.keybindingService.lookupKeybinding(id); - if (kb) { - return kb.getLabel(); - } - - return null; + private getActivtyName(): string { + const keybinding = this.compositeActivityAction.activity.keybindingId ? this.keybindingService.lookupKeybinding(this.compositeActivityAction.activity.keybindingId) : null; + return keybinding ? nls.localize('titleKeybinding', "{0} ({1})", this.compositeActivityAction.activity.name, keybinding.getLabel()) : this.compositeActivityAction.activity.name; } render(container: HTMLElement): void { diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts index 69c6d5775d..43f658c0a6 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts @@ -24,7 +24,6 @@ import { IWorkspace, IWorkspaceContextService, IWorkspaceFolder } from 'vs/platf import { ResourceLabels, IResourceLabel, DEFAULT_LABELS_CONTAINER } from 'vs/workbench/browser/labels'; import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs'; import { BreadcrumbElement, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel'; - import { IAsyncDataSource, ITreeRenderer, ITreeNode, ITreeFilter, TreeVisibility, ITreeSorter } from 'vs/base/browser/ui/tree/tree'; import { OutlineVirtualDelegate, OutlineGroupRenderer, OutlineElementRenderer, OutlineItemComparator, OutlineIdentityProvider, OutlineNavigationLabelProvider, OutlineDataSource, OutlineSortOrder, OutlineFilter, OutlineAccessibilityProvider } from 'vs/editor/contrib/documentSymbols/outlineTree'; import { IIdentityProvider, IListVirtualDelegate, IKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/list/list'; diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index 0288744411..8bd6e52faa 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -52,9 +52,7 @@ export function getEditorPartOptions(config: IWorkbenchEditorConfiguration): IEd return options; } - if (typeof config.workbench.iconTheme === 'string') { - options.iconTheme = config.workbench.iconTheme; - } + options.iconTheme = config.workbench.iconTheme; if (config.workbench.editor) { Object.assign(options, config.workbench.editor); diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 02dbdd1144..eae878cfcc 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -21,6 +21,7 @@ import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/wo import { ItemActivation, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { AllEditorsByMostRecentlyUsedQuickAccess, ActiveGroupEditorsByMostRecentlyUsedQuickAccess, AllEditorsByAppearanceQuickAccess } from 'vs/workbench/browser/parts/editor/editorQuickAccess'; import { Codicon } from 'vs/base/common/codicons'; +import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; export class ExecuteCommandAction extends Action { @@ -522,7 +523,8 @@ export abstract class BaseCloseAllAction extends Action { private workingCopyService: IWorkingCopyService, private fileDialogService: IFileDialogService, protected editorGroupService: IEditorGroupsService, - private editorService: IEditorService + private editorService: IEditorService, + private filesConfigurationService: IFilesConfigurationService ) { super(id, label, clazz); } @@ -562,31 +564,51 @@ export abstract class BaseCloseAllAction extends Action { })); const dirtyEditorsToConfirm = new Set(); + const dirtyEditorsToAutoSave = new Set(); for (const editor of this.editorService.editors) { if (!editor.isDirty() || editor.isSaving()) { continue; // only interested in dirty editors (unless in the process of saving) } - let name: string; - if (editor instanceof SideBySideEditorInput) { - name = editor.master.getName(); // prefer shorter names by using master's name in this case - } else { - name = editor.getName(); + // Auto-save on focus change: assume to Save unless the editor is untitled + // because bringing up a dialog would save in this case anyway. + if (this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.ON_FOCUS_CHANGE && !editor.isUntitled()) { + dirtyEditorsToAutoSave.add(editor); } - dirtyEditorsToConfirm.add(name); + // No auto-save on focus change: ask user + else { + let name: string; + if (editor instanceof SideBySideEditorInput) { + name = editor.master.getName(); // prefer shorter names by using master's name in this case + } else { + name = editor.getName(); + } + + dirtyEditorsToConfirm.add(name); + } } - const confirm = await this.fileDialogService.showSaveConfirm(Array.from(dirtyEditorsToConfirm.values())); - if (confirm === ConfirmResult.CANCEL) { + let confirmation: ConfirmResult; + let saveReason = SaveReason.EXPLICIT; + if (dirtyEditorsToConfirm.size > 0) { + confirmation = await this.fileDialogService.showSaveConfirm(Array.from(dirtyEditorsToConfirm.values())); + } else if (dirtyEditorsToAutoSave.size > 0) { + confirmation = ConfirmResult.SAVE; + saveReason = SaveReason.FOCUS_CHANGE; + } else { + confirmation = ConfirmResult.DONT_SAVE; + } + + if (confirmation === ConfirmResult.CANCEL) { return; } - if (confirm === ConfirmResult.DONT_SAVE) { + if (confirmation === ConfirmResult.DONT_SAVE) { await this.editorService.revertAll({ soft: true, includeUntitled: true }); } else { - await this.editorService.saveAll({ reason: SaveReason.EXPLICIT, includeUntitled: true }); + await this.editorService.saveAll({ reason: saveReason, includeUntitled: true }); } if (!this.workingCopyService.hasDirty) { @@ -608,9 +630,10 @@ export class CloseAllEditorsAction extends BaseCloseAllAction { @IWorkingCopyService workingCopyService: IWorkingCopyService, @IFileDialogService fileDialogService: IFileDialogService, @IEditorGroupsService editorGroupService: IEditorGroupsService, - @IEditorService editorService: IEditorService + @IEditorService editorService: IEditorService, + @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService ) { - super(id, label, Codicon.closeAll.classNames, workingCopyService, fileDialogService, editorGroupService, editorService); + super(id, label, Codicon.closeAll.classNames, workingCopyService, fileDialogService, editorGroupService, editorService, filesConfigurationService); } protected async doCloseAll(): Promise { @@ -629,9 +652,10 @@ export class CloseAllEditorGroupsAction extends BaseCloseAllAction { @IWorkingCopyService workingCopyService: IWorkingCopyService, @IFileDialogService fileDialogService: IFileDialogService, @IEditorGroupsService editorGroupService: IEditorGroupsService, - @IEditorService editorService: IEditorService + @IEditorService editorService: IEditorService, + @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService ) { - super(id, label, undefined, workingCopyService, fileDialogService, editorGroupService, editorService); + super(id, label, undefined, workingCopyService, fileDialogService, editorGroupService, editorService, filesConfigurationService); } protected async doCloseAll(): Promise { diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index fe22299a1c..200346d0cb 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -52,6 +52,7 @@ import { EditorActivation, EditorOpenContext } from 'vs/platform/editor/common/e import { IDialogService, IFileDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; import { ILogService } from 'vs/platform/log/common/log'; import { Codicon } from 'vs/base/common/codicons'; +import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; export class EditorGroupView extends Themable implements IEditorGroupView { @@ -134,7 +135,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { @IContextMenuService private readonly contextMenuService: IContextMenuService, @IFileDialogService private readonly fileDialogService: IFileDialogService, @ILogService private readonly logService: ILogService, - @IEditorService private readonly editorService: EditorServiceImpl + @IEditorService private readonly editorService: EditorServiceImpl, + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService ) { super(themeService); @@ -1306,30 +1308,43 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return false; // editor is still editable somewhere else } - // Switch to editor that we want to handle and confirm to save/revert - await this.openEditor(editor); - - let name: string; - if (editor instanceof SideBySideEditorInput) { - name = editor.master.getName(); // prefer shorter names by using master's name in this case - } else { - name = editor.getName(); + // Auto-save on focus change: assume to Save unless the editor is untitled + // because bringing up a dialog would save in this case anyway. + let confirmation: ConfirmResult; + let saveReason = SaveReason.EXPLICIT; + if (this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.ON_FOCUS_CHANGE && !editor.isUntitled()) { + confirmation = ConfirmResult.SAVE; + saveReason = SaveReason.FOCUS_CHANGE; } - const res = await this.fileDialogService.showSaveConfirm([name]); + // No auto-save on focus change: ask user + else { + + // Switch to editor that we want to handle and confirm to save/revert + await this.openEditor(editor); + + let name: string; + if (editor instanceof SideBySideEditorInput) { + name = editor.master.getName(); // prefer shorter names by using master's name in this case + } else { + name = editor.getName(); + } + + confirmation = await this.fileDialogService.showSaveConfirm([name]); + } // It could be that the editor saved meanwhile or is saving, so we check // again to see if anything needs to happen before closing for good. // This can happen for example if autoSave: onFocusChange is configured // so that the save happens when the dialog opens. if (!editor.isDirty() || editor.isSaving()) { - return res === ConfirmResult.CANCEL ? true : false; + return confirmation === ConfirmResult.CANCEL ? true : false; } // Otherwise, handle accordingly - switch (res) { + switch (confirmation) { case ConfirmResult.SAVE: - await editor.save(this.id, { reason: SaveReason.EXPLICIT }); + await editor.save(this.id, { reason: saveReason }); return editor.isDirty(); // veto if still dirty case ConfirmResult.DONT_SAVE: diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 69f784245b..05460bd9d0 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -444,7 +444,6 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { this.screenRedearModeElement.value = this.statusbarService.addEntry({ text, ariaLabel: text, - tooltip: nls.localize('screenReaderDetectedExtra', "If you are not using a Screen Reader, please change the setting `editor.accessibilitySupport` to \"off\"."), command: 'showEditorScreenReaderNotification', backgroundColor: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_BACKGROUND), color: themeColorFromId(STATUS_BAR_PROMINENT_ITEM_FOREGROUND) diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 801b3a70fe..331494c005 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -334,23 +334,18 @@ export class TabsTitleControl extends TitleControl { } // Shift-key enables or disables this behaviour depending on the setting - if (this.accessor.partOptions.scrollToSwitchTabs === 'off') { - if (!e.shiftKey) { - return; // 'off': only enable this when Shift-key is pressed - } - } else { + if (this.accessor.partOptions.scrollToSwitchTabs === true) { if (e.shiftKey) { return; // 'on': only enable this when Shift-key is not pressed } + } else { + if (!e.shiftKey) { + return; // 'off': only enable this when Shift-key is pressed + } } // Figure out scrolling direction - let scrollingUp = e.deltaX < 0 || e.deltaY < 0; - if (this.accessor.partOptions.scrollToSwitchTabs === 'reverse') { - scrollingUp = !scrollingUp; - } - - const nextEditor = this.group.getEditorByIndex(this.group.getIndexOfEditor(activeEditor) + (scrollingUp ? -1 : 1)); + const nextEditor = this.group.getEditorByIndex(this.group.getIndexOfEditor(activeEditor) + (e.deltaX < 0 || e.deltaY < 0 /* scrolling up */ ? -1 : 1)); if (!nextEditor) { return; } diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 8089a78d2c..e4bc0a25ff 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -54,7 +54,7 @@ export class PanelPart extends CompositePart implements IPanelService { static readonly activePanelSettingsKey = 'workbench.panelpart.activepanelid'; - private static readonly PINNED_PANELS = 'workbench.panel.pinnedPanels'; + static readonly PINNED_PANELS = 'workbench.panel.pinnedPanels'; private static readonly MIN_COMPOSITE_BAR_WIDTH = 50; _serviceBrand: undefined; @@ -222,14 +222,27 @@ export class PanelPart extends CompositePart implements IPanelService { } } - private onDidDeregisterPanel(panelId: string): void { + private async onDidDeregisterPanel(panelId: string): Promise { const disposable = this.panelDisposables.get(panelId); if (disposable) { disposable.dispose(); } - this.panelDisposables.delete(panelId); - this.hideComposite(panelId); + + const activeContainers = this.viewDescriptorService.getViewContainersByLocation(ViewContainerLocation.Panel) + .filter(container => this.viewDescriptorService.getViewContainerModel(container).activeViewDescriptors.length > 0); + + if (activeContainers.length) { + if (this.getActivePanel()?.getId() === panelId) { + const defaultPanelId = this.panelRegistry.getDefaultPanelId(); + const containerToOpen = activeContainers.filter(c => c.id === defaultPanelId)[0] || activeContainers[0]; + await this.openPanel(containerToOpen.id); + } + } else { + this.layoutService.setPanelHidden(true); + } + + this.removeComposite(panelId); } private updateActivity(viewContainer: ViewContainer, viewContainerModel: IViewContainerModel): void { @@ -287,6 +300,7 @@ export class PanelPart extends CompositePart implements IPanelService { this.compositeBar.onDidChange(() => this.saveCachedPanels(), this, disposables); this.storageService.onDidChangeStorage(e => this.onDidStorageChange(e), this, disposables); })); + } private onDidRegisterExtensions(): void { @@ -537,6 +551,7 @@ export class PanelPart extends CompositePart implements IPanelService { protected removeComposite(compositeId: string): boolean { if (super.removeComposite(compositeId)) { + this.compositeBar.removeComposite(compositeId); const compositeActions = this.compositeActions.get(compositeId); if (compositeActions) { compositeActions.activityAction.dispose(); diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index b73d1c9e63..8af36d16b5 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -34,6 +34,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { LayoutPriority } from 'vs/base/browser/ui/grid/grid'; import { assertIsDefined } from 'vs/base/common/types'; import { CompositeDragAndDropObserver } from 'vs/workbench/browser/dnd'; +import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; export class SidebarPart extends CompositePart implements IViewletService { @@ -93,6 +94,7 @@ export class SidebarPart extends CompositePart implements IViewletServi @IKeybindingService keybindingService: IKeybindingService, @IInstantiationService instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IExtensionService private readonly extensionService: IExtensionService ) { @@ -134,9 +136,18 @@ export class SidebarPart extends CompositePart implements IViewletServi // Viewlet deregister this._register(this.registry.onDidDeregister(async (viewletDescriptor: ViewletDescriptor) => { - const activeViewlet = this.getActiveViewlet(); - if (!activeViewlet || activeViewlet.getId() === viewletDescriptor.id) { - await this.openViewlet(this.getDefaultViewletId()); + + const activeContainers = this.viewDescriptorService.getViewContainersByLocation(ViewContainerLocation.Sidebar) + .filter(container => this.viewDescriptorService.getViewContainerModel(container).activeViewDescriptors.length > 0); + + if (activeContainers.length) { + if (this.getActiveComposite()?.getId() === viewletDescriptor.id) { + const defaultViewletId = this.getDefaultViewletId(); + const containerToOpen = activeContainers.filter(c => c.id === defaultViewletId)[0] || activeContainers[0]; + await this.openViewlet(containerToOpen.id); + } + } else { + this.layoutService.setSideBarHidden(true); } this.removeComposite(viewletDescriptor.id); @@ -165,12 +176,7 @@ export class SidebarPart extends CompositePart implements IViewletServi const draggedItemProvider = (): { type: 'view' | 'composite', id: string } => { const activeViewlet = this.getActiveViewlet()!; - const visibleViews = activeViewlet.getViewPaneContainer().views.filter(v => v.isVisible()); - if (visibleViews.length === 1) { - return { type: 'view', id: visibleViews[0].id }; - } else { - return { type: 'composite', id: activeViewlet.getId() }; - } + return { type: 'composite', id: activeViewlet.getId() }; }; this._register(CompositeDragAndDropObserver.INSTANCE.registerDraggable(this.titleLabelElement!, draggedItemProvider, {})); diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 52a0b39399..09fd6ec854 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -695,6 +695,8 @@ class StatusbarEntryItem extends Disposable { } if (!this.entry || entry.ariaLabel !== this.entry.ariaLabel) { + // Set the aria label on both elements so screen readers would read the correct thing without duplication #96210 + this.container.setAttribute('aria-label', entry.ariaLabel); this.labelContainer.setAttribute('aria-label', entry.ariaLabel); } @@ -703,7 +705,7 @@ class StatusbarEntryItem extends Disposable { if (entry.tooltip) { this.container.title = entry.tooltip; } else { - delete this.container.title; + this.container.title = ''; } } diff --git a/src/vs/workbench/browser/parts/views/media/views.css b/src/vs/workbench/browser/parts/views/media/views.css index 179dad4923..c66a764803 100644 --- a/src/vs/workbench/browser/parts/views/media/views.css +++ b/src/vs/workbench/browser/parts/views/media/views.css @@ -66,7 +66,6 @@ max-width: 260px; margin-left: auto; margin-right: auto; - display: block; } .monaco-workbench .pane > .pane-body .welcome-view-content { diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 1aeb6e48ce..fc3c835c4e 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -458,7 +458,7 @@ export abstract class ViewPane extends Pane implements IView { if (linkedText.nodes.length === 1 && typeof linkedText.nodes[0] !== 'string') { const node = linkedText.nodes[0]; - const button = new Button(this.viewWelcomeContainer, { title: node.title }); + const button = new Button(this.viewWelcomeContainer, { title: node.title, supportCodicons: true }); button.label = node.label; button.onDidClick(_ => { this.telemetryService.publicLog2<{ viewId: string, uri: string }, WelcomeActionClassification>('views.welcomeAction', { viewId: this.id, uri: node.href }); @@ -878,8 +878,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { })); this._register(this.onDidSashChange(() => this.saveViewSizes())); - this.viewContainerModel.onDidAddVisibleViewDescriptors(added => this.onDidAddViewDescriptors(added)); - this.viewContainerModel.onDidRemoveVisibleViewDescriptors(removed => this.onDidRemoveViewDescriptors(removed)); + this._register(this.viewContainerModel.onDidAddVisibleViewDescriptors(added => this.onDidAddViewDescriptors(added))); + this._register(this.viewContainerModel.onDidRemoveVisibleViewDescriptors(removed => this.onDidRemoveViewDescriptors(removed))); const addedViews: IAddedViewDescriptorRef[] = this.viewContainerModel.visibleViewDescriptors.map((viewDescriptor, index) => { const size = this.viewContainerModel.getSize(viewDescriptor.id); const collapsed = this.viewContainerModel.isCollapsed(viewDescriptor.id); @@ -1140,15 +1140,17 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { }); } - openView(id: string, focus?: boolean): IView { + openView(id: string, focus?: boolean): IView | undefined { let view = this.getView(id); if (!view) { this.toggleViewVisibility(id); } - view = this.getView(id)!; - view.setExpanded(true); - if (focus) { - view.focus(); + view = this.getView(id); + if (view) { + view.setExpanded(true); + if (focus) { + view.focus(); + } } return view; } @@ -1199,17 +1201,19 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { panesToRemove.push(this.panes[index]); } this.removePanes(panesToRemove); - dispose(panesToRemove); } protected toggleViewVisibility(viewId: string): void { - const visible = !this.viewContainerModel.isVisible(viewId); - type ViewsToggleVisibilityClassification = { - viewId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; - visible: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; - }; - this.telemetryService.publicLog2<{ viewId: String, visible: boolean }, ViewsToggleVisibilityClassification>('views.toggleVisibility', { viewId, visible }); - this.viewContainerModel.setVisible(viewId, visible); + // Check if view is active + if (this.viewContainerModel.activeViewDescriptors.some(viewDescriptor => viewDescriptor.id === viewId)) { + const visible = !this.viewContainerModel.isVisible(viewId); + type ViewsToggleVisibilityClassification = { + viewId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + visible: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + }; + this.telemetryService.publicLog2<{ viewId: String, visible: boolean }, ViewsToggleVisibilityClassification>('views.toggleVisibility', { viewId, visible }); + this.viewContainerModel.setVisible(viewId, visible); + } } private addPane(pane: ViewPane, size: number, index = this.paneItems.length - 1): void { @@ -1234,8 +1238,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { leftBorder: PANEL_BORDER, dropBackground: SIDE_BAR_DRAG_AND_DROP_BACKGROUND }, pane); - const disposable = combinedDisposable(onDidFocus, onDidChangeTitleArea, paneStyler, onDidChange, onDidChangeVisibility); - const paneItem: IViewPaneItem = { pane: pane, disposable }; + const disposable = combinedDisposable(pane, onDidFocus, onDidChangeTitleArea, paneStyler, onDidChange, onDidChangeVisibility); + const paneItem: IViewPaneItem = { pane, disposable }; this.paneItems.splice(index, 0, paneItem); assertIsDefined(this.paneview).addPane(pane, size, index); diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index ff36442d51..b6932239a7 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -39,13 +39,13 @@ export class ViewsService extends Disposable implements IViewsService { 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 viewPaneContainers: Map; constructor( @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, @@ -59,7 +59,7 @@ export class ViewsService extends Disposable implements IViewsService { this.viewContainersRegistry = Registry.as(ViewExtensions.ViewContainersRegistry); this.viewDisposable = new Map(); this.visibleViewContextKeys = new Map>(); - this.viewPaneContainers = new Map(); + this.viewPaneContainers = new Map(); this._register(toDisposable(() => { this.viewDisposable.forEach(disposable => disposable.dispose()); @@ -67,17 +67,27 @@ export class ViewsService extends Disposable implements IViewsService { })); this.viewDescriptorService.getViewContainers().forEach(viewContainer => this.onDidRegisterViewContainer(viewContainer, this.viewDescriptorService.getViewContainerLocation(viewContainer)!)); - this._register(this.viewContainersRegistry.onDidRegister(({ viewContainer, viewContainerLocation }) => this.onDidRegisterViewContainer(viewContainer, viewContainerLocation))); - - this._register(this.viewContainersRegistry.onDidDeregister(e => this.viewPaneContainers.delete(e.viewContainer.id))); + 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._register(this.viewDescriptorService.onDidChangeContainerLocation(({ viewContainer, from, to }) => this.onDidChangeContainerLocation(viewContainer, from, to))); } private registerViewPaneContainer(viewPaneContainer: ViewPaneContainer): void { - this._register(viewPaneContainer.onDidAddViews(views => this.onViewsAdded(views))); - this._register(viewPaneContainer.onDidChangeViewVisibility(view => this.onViewsVisibilityChanged(view, view.isBodyVisible()))); - this._register(viewPaneContainer.onDidRemoveViews(views => this.onViewsRemoved(views))); + const disposable = new DisposableStore(); + disposable.add(viewPaneContainer); + disposable.add(viewPaneContainer.onDidAddViews(views => this.onViewsAdded(views))); + disposable.add(viewPaneContainer.onDidChangeViewVisibility(view => this.onViewsVisibilityChanged(view, view.isBodyVisible()))); + disposable.add(viewPaneContainer.onDidRemoveViews(views => this.onViewsRemoved(views))); - this.viewPaneContainers.set(viewPaneContainer.getId(), viewPaneContainer); + this.viewPaneContainers.set(viewPaneContainer.getId(), { viewPaneContainer, disposable }); + } + + private deregisterViewPaneContainer(id: string): void { + const viewPaneContainerItem = this.viewPaneContainers.get(id); + if (viewPaneContainerItem) { + viewPaneContainerItem.disposable.dispose(); + this.viewPaneContainers.delete(id); + } } private onViewsAdded(added: IView[]): void { @@ -107,6 +117,11 @@ export class ViewsService extends Disposable implements IViewsService { return contextKey; } + private onDidChangeContainerLocation(viewContainer: ViewContainer, from: ViewContainerLocation, to: ViewContainerLocation): void { + this.deregisterViewletOrPanel(viewContainer, from); + this.registerViewletOrPanel(viewContainer, to); + } + private onDidRegisterViewContainer(viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation): void { this.registerViewletOrPanel(viewContainer, viewContainerLocation); const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); @@ -134,6 +149,7 @@ export class ViewsService extends Disposable implements IViewsService { category: composite ? composite.name : localize('view category', "View"), menu: [{ id: MenuId.CommandPalette, + when: viewDescriptor.when, }], keybinding: { when: ContextKeyExpr.has(`${viewDescriptor.id}.active`), @@ -228,16 +244,22 @@ export class ViewsService extends Disposable implements IViewsService { async openView(id: string, focus: boolean): Promise { const viewContainer = this.viewDescriptorService.getViewContainerByViewId(id); - if (viewContainer) { - const location = this.viewDescriptorService.getViewContainerLocation(viewContainer); - const compositeDescriptor = this.getComposite(viewContainer.id, location!); - if (compositeDescriptor) { - const paneComposite = await this.openComposite(compositeDescriptor.id, location!) as IPaneComposite | undefined; - if (paneComposite && paneComposite.openView) { - return paneComposite.openView(id, focus) as T; - } else if (focus) { - paneComposite?.focus(); - } + if (!viewContainer) { + return null; + } + + if (!this.viewDescriptorService.getViewContainerModel(viewContainer).activeViewDescriptors.some(viewDescriptor => viewDescriptor.id === id)) { + return null; + } + + const location = this.viewDescriptorService.getViewContainerLocation(viewContainer); + const compositeDescriptor = this.getComposite(viewContainer.id, location!); + if (compositeDescriptor) { + const paneComposite = await this.openComposite(compositeDescriptor.id, location!) as IPaneComposite | undefined; + if (paneComposite && paneComposite.openView) { + return paneComposite.openView(id, focus) || null; + } else if (focus) { + paneComposite?.focus(); } } @@ -290,7 +312,7 @@ export class ViewsService extends Disposable implements IViewsService { return undefined; } - const view = this.viewPaneContainers.get(viewContainer.id)?.getView(id); + const view = this.viewPaneContainers.get(viewContainer.id)?.viewPaneContainer?.getView(id); return view?.getProgressIndicator(); } @@ -307,6 +329,19 @@ export class ViewsService extends Disposable implements IViewsService { } } + private deregisterViewletOrPanel(viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation): void { + switch (viewContainerLocation) { + case ViewContainerLocation.Panel: + this.deregisterPanel(viewContainer); + break; + case ViewContainerLocation.Sidebar: + if (viewContainer.ctorDescriptor) { + this.deregisterViewlet(viewContainer); + } + break; + } + } + private registerPanel(viewContainer: ViewContainer): void { const that = this; class PaneContainerPanel extends PaneCompositePanel { @@ -329,12 +364,17 @@ export class ViewsService extends Disposable implements IViewsService { PaneContainerPanel, viewContainer.id, viewContainer.name, - isString(viewContainer.icon) ? viewContainer.icon : undefined, + undefined, viewContainer.order, viewContainer.focusCommand?.id, )); } + private deregisterPanel(viewContainer: ViewContainer): void { + this.deregisterViewPaneContainer(viewContainer.id); + Registry.as(PanelExtensions.Panels).deregisterPanel(viewContainer.id); + } + private registerViewlet(viewContainer: ViewContainer): void { const that = this; class PaneContainerViewlet extends Viewlet { @@ -364,6 +404,11 @@ export class ViewsService extends Disposable implements IViewsService { viewContainer.icon instanceof URI ? viewContainer.icon : undefined )); } + + private deregisterViewlet(viewContainer: ViewContainer): void { + this.deregisterViewPaneContainer(viewContainer.id); + Registry.as(ViewletExtensions.Viewlets).deregisterViewlet(viewContainer.id); + } } export function createFileIconThemableTreeContainerScope(container: HTMLElement, themeService: IThemeService): IDisposable { diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 4b37d5bfb3..c3d6571371 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -38,7 +38,7 @@ import { FileUserDataProvider } from 'vs/workbench/services/userData/common/file import { BACKUPS } from 'vs/platform/environment/common/environment'; import { joinPath } from 'vs/base/common/resources'; import { BrowserStorageService } from 'vs/platform/storage/browser/storageService'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService } from 'vs/platform/storage/common/storage'; import { getThemeTypeSelector, DARK, HIGH_CONTRAST, LIGHT } from 'vs/platform/theme/common/themeService'; import { registerWindowDriver } from 'vs/platform/driver/browser/driver'; import { BufferLogService } from 'vs/platform/log/common/bufferLog'; @@ -52,23 +52,6 @@ import { coalesce } from 'vs/base/common/arrays'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; import { WebResourceIdentityService, IResourceIdentityService } from 'vs/platform/resource/common/resourceIdentityService'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { firstSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry'; - -interface PanelActivityState { - id: string; - name?: string; - pinned: boolean; - order: number; - visible: boolean; -} - -interface SideBarActivityState { - id: string; - pinned: boolean; - order: number; - visible: boolean; -} - class BrowserMain extends Disposable { @@ -82,6 +65,11 @@ class BrowserMain extends Disposable { async open(): Promise { const services = await this.initServices(); + // const defaultLayout = this.configuration?.defaultLayout; + // if (defaultLayout) { + // defaultLayout.firstRun = services.storageService.get(firstSessionDateStorageKey, StorageScope.GLOBAL) === undefined; + // } + await domContentLoaded(); mark('willStartWorkbench'); @@ -98,8 +86,6 @@ class BrowserMain extends Disposable { // Listeners this.registerListeners(workbench, services.storageService); - this.applyDefaultLayout(services.storageService); - // Driver if (this.configuration.driver) { (async () => this._register(await registerWindowDriver()))(); @@ -120,176 +106,6 @@ class BrowserMain extends Disposable { }); } - private applyDefaultLayout(storageService: BrowserStorageService) { - const { defaultLayout } = this.configuration; - if (!defaultLayout) { - return; - } - - const firstRun = storageService.get(firstSessionDateStorageKey, StorageScope.GLOBAL); - if (firstRun !== undefined) { - return; - } - - const { sidebar } = defaultLayout; - if (sidebar) { - if (sidebar.visible !== undefined) { - if (sidebar.visible) { - storageService.remove('workbench.sidebar.hidden', StorageScope.WORKSPACE); - } else { - storageService.store('workbench.sidebar.hidden', true, StorageScope.WORKSPACE); - } - } - - if (sidebar.containers !== undefined) { - const sidebarState: SideBarActivityState[] = []; - - let order = -1; - for (const container of sidebar.containers.sort((a, b) => (a.order ?? 1) - (b.order ?? 1))) { - let viewletId; - switch (container.id) { - case 'explorer': - viewletId = 'workbench.view.explorer'; - break; - case 'run': - viewletId = 'workbench.view.debug'; - break; - case 'scm': - viewletId = 'workbench.view.scm'; - break; - case 'search': - viewletId = 'workbench.view.search'; - break; - case 'extensions': - viewletId = 'workbench.view.extensions'; - break; - case 'remote': - viewletId = 'workbench.view.remote'; - break; - default: - viewletId = `workbench.view.extension.${container.id}`; - } - - order = container.order ?? (order + 1); - const state: SideBarActivityState = { - id: viewletId, - order: order, - pinned: true, - visible: true - }; - - if (container.active) { - storageService.store('workbench.sidebar.activeviewletid', viewletId, StorageScope.WORKSPACE); - } else { - if ((container).visible !== undefined) { // {{SQL CARBON EDIT}} strict-null-checks - state.pinned = (container).visible; // {{SQL CARBON EDIT}} strict-null-checks - state.visible = (container).visible; // {{SQL CARBON EDIT}} strict-null-checks - } - } - - sidebarState.push(state); - - if (container.views !== undefined) { - const viewsState: { id: string, isHidden?: boolean, order?: number }[] = []; - const viewsWorkspaceState: { [id: string]: { collapsed: boolean, isHidden?: boolean, size?: number } } = {}; - - for (const view of container.views) { - if (view.order !== undefined || view.visible !== undefined) { - viewsState.push({ - id: view.id, - isHidden: view.visible === undefined ? undefined : !view.visible, - order: view.order === undefined ? undefined : view.order - }); - } - - if (view.collapsed !== undefined) { - viewsWorkspaceState[view.id] = { - collapsed: view.collapsed, - isHidden: view.visible === undefined ? undefined : !view.visible, - }; - } - } - - storageService.store(`${viewletId}.state.hidden`, JSON.stringify(viewsState), StorageScope.GLOBAL); - storageService.store(`${viewletId}.state`, JSON.stringify(viewsWorkspaceState), StorageScope.WORKSPACE); - } - } - - storageService.store('workbench.activity.pinnedViewlets2', JSON.stringify(sidebarState), StorageScope.GLOBAL); - } - } - - const { panel } = defaultLayout; - if (panel) { - if (panel.visible !== undefined) { - if (panel.visible) { - storageService.store('workbench.panel.hidden', false, StorageScope.WORKSPACE); - } else { - storageService.remove('workbench.panel.hidden', StorageScope.WORKSPACE); - } - } - - if (panel.containers !== undefined) { - const panelState: PanelActivityState[] = []; - - let order = -1; - for (const container of panel.containers.sort((a, b) => (a.order ?? 1) - (b.order ?? 1))) { - let name; - let panelId = container.id; - switch (panelId) { - case 'terminal': - name = 'Terminal'; - panelId = 'workbench.panel.terminal'; - break; - case 'debug': - name = 'Debug Console'; - panelId = 'workbench.panel.repl'; - break; - case 'problems': - name = 'Problems'; - panelId = 'workbench.panel.markers'; - break; - case 'output': - name = 'Output'; - panelId = 'workbench.panel.output'; - break; - case 'comments': - name = 'Comments'; - panelId = 'workbench.panel.comments'; - break; - case 'refactor': - name = 'Refactor Preview'; - panelId = 'refactorPreview'; - default: - continue; - } - - order = container.order ?? (order + 1); - const state: PanelActivityState = { - id: panelId, - name: name, - order: order, - pinned: true, - visible: true - }; - - if (container.active) { - storageService.store('workbench.panelpart.activepanelid', panelId, StorageScope.WORKSPACE); - } else { - if ((container).visible !== undefined) { // {{SQL CARBON EDIT}} strict-null-checks - state.pinned = (container).visible; // {{SQL CARBON EDIT}} strict-null-checks - state.visible = (container).visible; // {{SQL CARBON EDIT}} strict-null-checks - } - } - - panelState.push(state); - } - - storageService.store('workbench.panel.pinnedPanels', JSON.stringify(panelState), StorageScope.GLOBAL); - } - } - } - private registerListeners(workbench: Workbench, storageService: BrowserStorageService): void { // Layout diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 50f2d98f01..b080587d16 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -33,15 +33,9 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio 'default': true }, 'workbench.editor.scrollToSwitchTabs': { - 'type': 'string', - 'enum': ['off', 'natural', 'reverse'], - 'enumDescriptions': [ - nls.localize('workbench.editor.scrollToSwitchTabs.off', "Tabs will reveal when scrolling with the mouse but not open. You can press and hold the Shift-key to switch tabs while scrolling."), - nls.localize('workbench.editor.scrollToSwitchTabs.natural', "Tabs will open when scrolling with the mouse in natural scrolling direction (scroll up to switch to the tab on the left and down for the tab on the right). You can press and hold the Shift-key to disable this behaviour for that duration."), - nls.localize('workbench.editor.scrollToSwitchTabs.reverse', "Tabs will open when scrolling with the mouse in reverse scrolling direction (scroll down to switch to the tab on the left and up for the tab on the right). You can press and hold the Shift-key to disable this behaviour for that duration."), - ], - 'default': 'off', - 'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'scrollToSwitchTabs' }, "Controls wether scrolling over tabs will open them or not. By default tabs will only reveal upon scrolling, but not open. You can press and hold the Shift-key while scrolling to change this behaviour for that duration.") + 'type': 'boolean', + 'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'scrollToSwitchTabs' }, "Controls wether scrolling over tabs will open them or not. By default tabs will only reveal upon scrolling, but not open. You can press and hold the Shift-key while scrolling to change this behaviour for that duration."), + 'default': false }, 'workbench.editor.highlightModifiedTabs': { 'type': 'boolean', diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 72aaa0bccf..c2bf2e5cf3 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -32,7 +32,7 @@ import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/ export const DirtyWorkingCopiesContext = new RawContextKey('dirtyWorkingCopies', false); export const ActiveEditorContext = new RawContextKey('activeEditor', null); export const ActiveEditorIsReadonlyContext = new RawContextKey('activeEditorIsReadonly', false); -export const ActiveEditorAvailableEditorsContext = new RawContextKey('availableEditors', ''); +export const ActiveEditorAvailableEditorIdsContext = new RawContextKey('activeEditorAvailableEditorIds', ''); export const EditorsVisibleContext = new RawContextKey('editorIsOpen', false); export const EditorPinnedContext = new RawContextKey('editorPinned', false); export const EditorGroupActiveEditorDirtyContext = new RawContextKey('groupActiveEditorDirty', false); @@ -1304,7 +1304,7 @@ export interface IWorkbenchEditorConfiguration { interface IEditorPartConfiguration { showTabs?: boolean; - scrollToSwitchTabs?: 'off' | 'natural' | 'reverse'; + scrollToSwitchTabs?: boolean; highlightModifiedTabs?: boolean; tabCloseButton?: 'left' | 'right' | 'off'; tabSizing?: 'fit' | 'shrink'; diff --git a/src/vs/workbench/common/editor/editorGroup.ts b/src/vs/workbench/common/editor/editorGroup.ts index 6d18b1f2f5..f2c8cecc5f 100644 --- a/src/vs/workbench/common/editor/editorGroup.ts +++ b/src/vs/workbench/common/editor/editorGroup.ts @@ -384,7 +384,7 @@ export class EditorGroup extends Disposable { moveEditor(candidate: EditorInput, toIndex: number): EditorInput | undefined { const index = this.indexOf(candidate); - if (index < 0) { + if (index < 0 || toIndex === index) { return undefined; // {{SQL CARBON EDIT}} strict-null-check } @@ -482,6 +482,10 @@ export class EditorGroup extends Disposable { isPinned(editor: EditorInput): boolean; isPinned(index: number): boolean; isPinned(arg1: EditorInput | number): boolean { + if (!this.preview) { + return true; // no preview editor + } + let editor: EditorInput; let index: number; if (typeof arg1 === 'number') { @@ -496,10 +500,6 @@ export class EditorGroup extends Disposable { return false; // editor not found } - if (!this.preview) { - return true; // no preview editor - } - return !this.matches(this.preview, editor); } diff --git a/src/vs/workbench/common/panecomposite.ts b/src/vs/workbench/common/panecomposite.ts index d59ffbb94e..12be25d8a5 100644 --- a/src/vs/workbench/common/panecomposite.ts +++ b/src/vs/workbench/common/panecomposite.ts @@ -7,7 +7,7 @@ import { IView, IViewPaneContainer } from 'vs/workbench/common/views'; import { IComposite } from 'vs/workbench/common/composite'; export interface IPaneComposite extends IComposite { - openView(id: string, focus?: boolean): IView; + openView(id: string, focus?: boolean): T | undefined; getViewPaneContainer(): IViewPaneContainer; saveState(): void; } diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 28a3820e49..e58d18e24e 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -8,7 +8,6 @@ import { UriComponents, URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { RawContextKey, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { localize } from 'vs/nls'; -import { IViewlet } from 'vs/workbench/common/viewlet'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; @@ -443,12 +442,6 @@ export interface IView { getProgressIndicator(): IProgressIndicator | undefined; } -export interface IViewsViewlet extends IViewlet { - - openView(id: string, focus?: boolean): IView; - -} - export const IViewsService = createDecorator('viewsService'); export interface IViewsService { @@ -482,6 +475,7 @@ export interface IViewDescriptorService { 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; @@ -499,6 +493,8 @@ export interface IViewDescriptorService { getViewContainerLocation(viewContainer: ViewContainer): ViewContainerLocation | null; + getDefaultViewContainerLocation(viewContainer: ViewContainer): ViewContainerLocation | null; + getDefaultContainerById(id: string): ViewContainer | null; getViewLocationById(id: string): ViewContainerLocation | null; @@ -631,7 +627,7 @@ export interface IEditableData { validationMessage: (value: string) => { content: string, severity: Severity } | null; placeholder?: string | null; startingValue?: string | null; - onFinish: (value: string, success: boolean) => void; + onFinish: (value: string, success: boolean) => Promise; } export interface IViewPaneContainer { diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.css b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.css index 418fe454b1..b8213e8ca8 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.css +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.css @@ -32,6 +32,7 @@ .tiw-metadata-value { font-family: var(--monaco-monospace-font); text-align: right; + word-break: break-word; } .tiw-metadata-key { vertical-align: top; diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts index 044f03d8f9..149671fb40 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts @@ -27,7 +27,7 @@ import { ITextMateService, IGrammar, IToken, StackElement } from 'vs/workbench/s import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { ColorThemeData, TokenStyleDefinitions, TokenStyleDefinition, TextMateThemingRuleDefinitions } from 'vs/workbench/services/themes/common/colorThemeData'; -import { TokenStylingRule, TokenStyleData, TokenStyle } from 'vs/platform/theme/common/tokenClassificationRegistry'; +import { SemanticTokenRule, TokenStyleData, TokenStyle } from 'vs/platform/theme/common/tokenClassificationRegistry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export interface IEditorSemanticHighlightingOptions { @@ -251,6 +251,7 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { } let text = this._compute(grammar, semanticTokens, position); this._domNode.innerHTML = text; + this._domNode.style.maxWidth = `${Math.max(this._editor.getLayoutInfo().width * 0.66, 500)}px`; this._editor.layoutContentWidget(this); }, (err) => { this._notificationService.warn(err); @@ -552,10 +553,11 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { theme.resolveScopes(definition, scopesDefinition); const matchingRule = scopesDefinition[property]; if (matchingRule && scopesDefinition.scope) { - return `${escape(scopesDefinition.scope.join(' '))}
${matchingRule.scope}\n${JSON.stringify(matchingRule.settings, null, '\t')}`; + const strScopes = Array.isArray(matchingRule.scope) ? matchingRule.scope.join(', ') : String(matchingRule.scope); + return `${escape(scopesDefinition.scope.join(' '))}
${strScopes}\n${JSON.stringify(matchingRule.settings, null, '\t')}`; } return ''; - } else if (TokenStylingRule.is(definition)) { + } else if (SemanticTokenRule.is(definition)) { const scope = theme.getTokenStylingRuleScope(definition); if (scope === 'setting') { return `User settings: ${definition.selector.id} - ${this._renderStyleProperty(definition.style, property)}`; diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts index 08288097f1..b9d449596e 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { IKeyMods, IQuickPickSeparator, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IKeyMods, IQuickPickSeparator, IQuickInputService, IQuickPick } from 'vs/platform/quickinput/common/quickInput'; import { IEditor } from 'vs/editor/common/editorCommon'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IRange } from 'vs/editor/common/core/range'; @@ -12,16 +12,19 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IQuickAccessRegistry, Extensions as QuickaccessExtensions } from 'vs/platform/quickinput/common/quickAccess'; import { AbstractGotoSymbolQuickAccessProvider, IGotoSymbolQuickPickItem } from 'vs/editor/contrib/quickAccess/gotoSymbolQuickAccess'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; +import { IWorkbenchEditorConfiguration, IEditorPane } from 'vs/workbench/common/editor'; import { ITextModel } from 'vs/editor/common/model'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { timeout } from 'vs/base/common/async'; -import { CancellationToken } from 'vs/base/common/cancellation'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Action } from 'vs/base/common/actions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { prepareQuery } from 'vs/base/common/fuzzyScorer'; +import { SymbolKind } from 'vs/editor/common/modes'; +import { fuzzyScore, createMatches } from 'vs/base/common/filters'; +import { onUnexpectedError } from 'vs/base/common/errors'; export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccessProvider { @@ -36,6 +39,8 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess }); } + //#region DocumentSymbols (text editor required) + private get configuration() { const editorConfig = this.configurationService.getValue().workbench.editor; @@ -66,6 +71,7 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess } } + //#endregion //#region public methods to use this picker from other pickers @@ -98,6 +104,81 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess } //#endregion + + protected provideWithoutTextEditor(picker: IQuickPick): IDisposable { + const pane = this.editorService.activeEditorPane; + if (!pane || !TableOfContentsProviderRegistry.has(pane.getId())) { + // + return super.provideWithoutTextEditor(picker); + } + + const provider = TableOfContentsProviderRegistry.get(pane.getId())!; + const cts = new CancellationTokenSource(); + + const disposables = new DisposableStore(); + disposables.add(toDisposable(() => cts.dispose(true))); + + picker.busy = true; + + provider.provideTableOfContents(pane, cts.token).then(entries => { + + picker.busy = false; + + if (cts.token.isCancellationRequested || !entries || entries.length === 0) { + return; + } + + const items: IGotoSymbolQuickPickItem[] = entries.map((entry, idx) => { + return { + kind: SymbolKind.File, + index: idx, + score: 0, + label: entry.label, + detail: entry.detail, + description: entry.description, + }; + }); + + disposables.add(picker.onDidAccept(() => { + picker.hide(); + const [entry] = picker.selectedItems; + entries[entry.index]?.reveal(); + })); + + const updatePickerItems = () => { + const filteredItems = items.filter(item => { + if (picker.value === '@') { + // default, no filtering, scoring... + item.score = 0; + item.highlights = undefined; + return true; + } + const score = fuzzyScore(picker.value, picker.value.toLowerCase(), 1 /*@-character*/, item.label, item.label.toLowerCase(), 0, true); + if (!score) { + return false; + } + item.score = score[1]; + item.highlights = { label: createMatches(score) }; + return true; + }); + if (filteredItems.length === 0) { + const label = localize('empty', 'No matching entries'); + picker.items = [{ label, index: -1, kind: SymbolKind.String }]; + picker.ariaLabel = label; + } else { + picker.items = filteredItems; + } + }; + updatePickerItems(); + disposables.add(picker.onDidChangeValue(updatePickerItems)); + + }).catch(err => { + onUnexpectedError(err); + picker.hide(); + }); + + return disposables; + } } Registry.as(QuickaccessExtensions.Quickaccess).registerQuickAccessProvider({ @@ -132,3 +213,43 @@ export class GotoSymbolAction extends Action { Registry.as(ActionExtensions.WorkbenchActions).registerWorkbenchAction(SyncActionDescriptor.from(GotoSymbolAction, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_O }), 'Go to Symbol in Editor...'); + + +//#region toc definition and logic + +export interface ITableOfContentsEntry { + label: string; + detail?: string; + description?: string; + reveal(): any; +} + +export interface ITableOfContentsProvider { + provideTableOfContents(editor: T, token: CancellationToken): Promise; +} + +class ProviderRegistry { + + private readonly _provider = new Map(); + + register(type: string, provider: ITableOfContentsProvider): IDisposable { + this._provider.set(type, provider); + return toDisposable(() => { + if (this._provider.get(type) === provider) { + this._provider.delete(type); + } + }); + } + + get(type: string): ITableOfContentsProvider | undefined { + return this._provider.get(type); + } + + has(type: string): boolean { + return this._provider.has(type); + } +} + +export const TableOfContentsProviderRegistry = new ProviderRegistry(); + +//#endregion diff --git a/src/vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution.ts b/src/vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution.ts index 0e26f72cc1..3afbb4cc49 100644 --- a/src/vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution.ts +++ b/src/vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution.ts @@ -6,3 +6,4 @@ import './inputClipboardActions'; import './sleepResumeRepaintMinimap'; import './selectionClipboard'; +import './startDebugTextMate'; diff --git a/src/vs/workbench/contrib/codeEditor/electron-browser/startDebugTextMate.ts b/src/vs/workbench/contrib/codeEditor/electron-browser/startDebugTextMate.ts new file mode 100644 index 0000000000..1110be61f5 --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/electron-browser/startDebugTextMate.ts @@ -0,0 +1,107 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as os from 'os'; +import * as path from 'path'; + +import * as nls from 'vs/nls'; +import { Range } from 'vs/editor/common/core/range'; +import { Action } from 'vs/base/common/actions'; +import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; +import { ITextMateService } from 'vs/workbench/services/textMate/common/textMateService'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { URI } from 'vs/base/common/uri'; +import { createRotatingLogger } from 'vs/platform/log/node/spdlogService'; +import { generateUuid } from 'vs/base/common/uuid'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { ITextModel } from 'vs/editor/common/model'; +import { Constants } from 'vs/base/common/uint'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; + +class StartDebugTextMate extends Action { + + private static resource = URI.parse(`inmemory:///tm-log.txt`); + + public static readonly ID = 'editor.action.startDebugTextMate'; + public static readonly LABEL = nls.localize('startDebugTextMate', "Start Text Mate Syntax Grammar Logging"); + + constructor( + id: string, + label: string, + @ITextMateService private readonly _textMateService: ITextMateService, + @IModelService private readonly _modelService: IModelService, + @IEditorService private readonly _editorService: IEditorService, + @ICodeEditorService private readonly _codeEditorService: ICodeEditorService, + @IHostService private readonly _hostService: IHostService + ) { + super(id, label); + } + + private _getOrCreateModel(): ITextModel { + const model = this._modelService.getModel(StartDebugTextMate.resource); + if (model) { + return model; + } + return this._modelService.createModel('', null, StartDebugTextMate.resource); + } + + private _append(model: ITextModel, str: string) { + const lineCount = model.getLineCount(); + model.applyEdits([{ + range: new Range(lineCount, Constants.MAX_SAFE_SMALL_INTEGER, lineCount, Constants.MAX_SAFE_SMALL_INTEGER), + text: str + }]); + } + + public async run(): Promise { + const pathInTemp = path.join(os.tmpdir(), `vcode-tm-log-${generateUuid()}.txt`); + const logger = createRotatingLogger(`tm-log`, pathInTemp, 1024 * 1024 * 30, 1); + const model = this._getOrCreateModel(); + const append = (str: string) => { + this._append(model, str + '\n'); + scrollEditor(); + logger.info(str); + logger.flush(); + }; + await this._hostService.openWindow([{ fileUri: URI.file(pathInTemp) }], { forceNewWindow: true }); + const textEditorPane = await this._editorService.openEditor({ + resource: model.uri + }); + if (!textEditorPane) { + return; + } + const scrollEditor = () => { + const editors = this._codeEditorService.listCodeEditors(); + for (const editor of editors) { + if (editor.hasModel()) { + if (editor.getModel().uri.toString() === StartDebugTextMate.resource.toString()) { + editor.revealLine(editor.getModel().getLineCount()); + } + } + } + }; + + append(`// Open the file you want to test to the side and watch here`); + append(`// Output mirrored at ${pathInTemp}`); + + this._textMateService.startDebugMode( + (str) => { + this._append(model, str + '\n'); + scrollEditor(); + logger.info(str); + logger.flush(); + }, + () => { + + } + ); + } +} + +const registry = Registry.as(ActionExtensions.WorkbenchActions); +registry.registerWorkbenchAction(SyncActionDescriptor.from(StartDebugTextMate), 'Start Text Mate Syntax Grammar Logging', nls.localize('developer', "Developer")); diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index ee669144a5..1a7b42ef52 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -339,6 +339,11 @@ export class CommentNode extends Disposable { this._commentEditor.layout({ width: container.clientWidth - 14, height: 90 }); this._commentEditor.focus(); + dom.scheduleAtNextAnimationFrame(() => { + this._commentEditor!.layout({ width: container.clientWidth - 14, height: 90 }); + this._commentEditor!.focus(); + }); + const lastLine = this._commentEditorModel.getLineCount(); const lastColumn = this._commentEditorModel.getLineContent(lastLine).length + 1; this._commentEditor.setSelection(new Selection(lastLine, lastColumn, lastLine, lastColumn)); diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index 71ccbcfa43..cae261c41f 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -101,6 +101,8 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget private _commentMenus: CommentMenus; + private _commentOptions: modes.CommentOptions | undefined; + constructor( editor: ICodeEditor, private _owner: string, @@ -133,6 +135,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget if (controller) { commentControllerKey.set(controller.contextValue); + this._commentOptions = controller.options; } this._resizeObserver = null; @@ -718,9 +721,9 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget private createReplyButton() { this._reviewThreadReplyButton = dom.append(this._commentForm, dom.$('button.review-thread-reply-button')); - this._reviewThreadReplyButton.title = nls.localize('reply', "Reply..."); + this._reviewThreadReplyButton.title = this._commentOptions?.prompt || nls.localize('reply', "Reply..."); - this._reviewThreadReplyButton.textContent = nls.localize('reply', "Reply..."); + this._reviewThreadReplyButton.textContent = this._commentOptions?.prompt || nls.localize('reply', "Reply..."); // bind click/escape actions for reviewThreadReplyButton and textArea this._disposables.add(dom.addDisposableListener(this._reviewThreadReplyButton, 'click', _ => this.expandReplyArea())); this._disposables.add(dom.addDisposableListener(this._reviewThreadReplyButton, 'focus', _ => this.expandReplyArea())); @@ -768,8 +771,8 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget const placeholder = valueLength > 0 ? '' : hasExistingComments - ? nls.localize('reply', "Reply...") - : nls.localize('newComment', "Type a new comment"); + ? (this._commentOptions?.placeHolder || nls.localize('reply', "Reply...")) + : (this._commentOptions?.placeHolder || nls.localize('newComment', "Type a new comment")); const decorations = [{ range: { startLineNumber: 0, diff --git a/src/vs/workbench/contrib/customEditor/browser/commands.ts b/src/vs/workbench/contrib/customEditor/browser/commands.ts index cc44878552..dd0b53e63e 100644 --- a/src/vs/workbench/contrib/customEditor/browser/commands.ts +++ b/src/vs/workbench/contrib/customEditor/browser/commands.ts @@ -10,8 +10,7 @@ import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkey import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CustomEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; -import { defaultCustomEditor } from 'vs/workbench/contrib/customEditor/common/contributedCustomEditors'; -import { CONTEXT_CUSTOM_EDITORS, CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -67,51 +66,3 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic } }).register(); -(new class ToggleCustomEditorCommand extends Command { - public static readonly ID = 'editor.action.customEditor.toggle'; - - constructor() { - super({ - id: ToggleCustomEditorCommand.ID, - precondition: CONTEXT_CUSTOM_EDITORS, - }); - } - - public runCommand(accessor: ServicesAccessor): void { - const editorService = accessor.get(IEditorService); - const activeEditorPane = editorService.activeEditorPane; - if (!activeEditorPane) { - return; - } - - const activeGroup = activeEditorPane.group; - const activeEditor = activeEditorPane.input; - const targetResource = activeEditor.resource; - - if (!targetResource) { - return; - } - - const customEditorService = accessor.get(ICustomEditorService); - - let toggleView = defaultCustomEditor.id; - if (!(activeEditor instanceof CustomEditorInput)) { - const bestAvailableEditor = customEditorService.getContributedCustomEditors(targetResource).bestAvailableEditor; - if (bestAvailableEditor) { - toggleView = bestAvailableEditor.id; - } else { - return; - } - } - - const newEditorInput = customEditorService.createInput(targetResource, toggleView, activeGroup.id); - - editorService.replaceEditors([{ - editor: activeEditor, - replacement: newEditorInput, - options: { - ignoreOverrides: true, - } - }], activeGroup); - } -}).register(); diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index cb39ecf67d..1c2ba9b4a6 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -29,7 +29,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { private readonly _editorResource: URI; private _defaultDirtyState: boolean | undefined; - public readonly backupId: string | undefined; + private readonly _backupId: string | undefined; get resource() { return this._editorResource; } @@ -54,7 +54,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { super(id, viewType, '', webview, webviewService, webviewWorkbenchService); this._editorResource = resource; this._defaultDirtyState = options.startsDirty; - this.backupId = options.backupId; + this._backupId = options.backupId; } public getTypeId(): string { @@ -213,7 +213,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { this.viewType, this.id, new Lazy(() => undefined!), - { startsDirty: this._defaultDirtyState, backupId: this.backupId }); // this webview is replaced in the transfer call + { startsDirty: this._defaultDirtyState, backupId: this._backupId }); // this webview is replaced in the transfer call this.transfer(newEditor); newEditor.updateGroup(group); return newEditor; @@ -246,4 +246,11 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { this._moveHandler = undefined; return other; } + + get backupId(): string | undefined { + if (this._modelRef) { + return this._modelRef.object.backupId; + } + return this._backupId; + } } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 7a9a8b4e9e..83c714b891 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -26,8 +26,9 @@ import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { CONTEXT_CUSTOM_EDITORS, CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CustomEditorCapabilities, CustomEditorInfo, CustomEditorInfoCollection, CustomEditorPriority, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { CustomEditorModelManager } from 'vs/workbench/contrib/customEditor/common/customEditorModelManager'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; +import { defaultEditorOverrideEntry } from 'vs/workbench/contrib/files/common/openWith'; import { IWebviewService, webviewHasOwnEditFunctionsContext } from 'vs/workbench/contrib/webview/browser/webview'; -import { CustomEditorAssociation, CustomEditorsAssociations, customEditorsAssociationsSettingId } from 'vs/workbench/services/editor/browser/editorAssociationsSetting'; +import { CustomEditorAssociation, CustomEditorsAssociations, customEditorsAssociationsSettingId } from 'vs/workbench/services/editor/common/editorAssociationsSetting'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ICustomEditorInfo, ICustomEditorViewTypesHandler, IEditorService, IOpenEditorOverride, IOpenEditorOverrideEntry } from 'vs/workbench/services/editor/common/editorService'; import { ContributedCustomEditors, defaultCustomEditor } from '../common/contributedCustomEditors'; @@ -170,7 +171,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ // And persist the setting if (pick) { const newAssociation: CustomEditorAssociation = { viewType: pick, filenamePattern: '*' + resourceExt }; - const currentAssociations = [...this.configurationService.getValue(customEditorsAssociationsSettingId)] || []; + const currentAssociations = [...this.configurationService.getValue(customEditorsAssociationsSettingId)]; // First try updating existing association for (let i = 0; i < currentAssociations.length; ++i) { @@ -207,7 +208,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ } const capabilities = this.getCustomEditorCapabilities(viewType) || {}; - if (!capabilities.supportsMultipleEditorsPerResource) { + if (!capabilities.supportsMultipleEditorsPerDocument) { const movedEditor = await this.tryRevealExistingEditorForResourceInGroup(resource, viewType, options, group); if (movedEditor) { return movedEditor; @@ -426,21 +427,24 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo open: (editor, options, group, id) => { return this.onEditorOpening(editor, options, group, id); }, - getEditorOverrides: (editor: IEditorInput, _options: IEditorOptions | undefined, _group: IEditorGroup | undefined): IOpenEditorOverrideEntry[] => { - const resource = editor.resource; - if (!resource) { - return []; - } + getEditorOverrides: (resource: URI, _options: IEditorOptions | undefined, group: IEditorGroup | undefined): IOpenEditorOverrideEntry[] => { + const currentEditor = group?.editors.find(editor => isEqual(editor.resource, resource)); const customEditors = this.customEditorService.getAllCustomEditors(resource); - return customEditors.allEditors.map(entry => { - return { - id: entry.id, - active: editor instanceof CustomEditorInput && editor.viewType === entry.id, - label: entry.displayName, - detail: entry.providerDisplayName, - }; - }); + return [ + { + ...defaultEditorOverrideEntry, + active: currentEditor instanceof FileEditorInput, + }, + ...customEditors.allEditors.map(entry => { + return { + id: entry.id, + active: currentEditor instanceof CustomEditorInput && currentEditor.viewType === entry.id, + label: entry.displayName, + detail: entry.providerDisplayName, + }; + }) + ]; } })); diff --git a/src/vs/workbench/contrib/customEditor/common/customEditor.ts b/src/vs/workbench/contrib/customEditor/common/customEditor.ts index 148b827373..8172d7f871 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditor.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditor.ts @@ -22,7 +22,7 @@ export const CONTEXT_CUSTOM_EDITORS = new RawContextKey('customEditors', export const CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE = new RawContextKey('focusedCustomEditorIsEditable', false); export interface CustomEditorCapabilities { - readonly supportsMultipleEditorsPerResource?: boolean; + readonly supportsMultipleEditorsPerDocument?: boolean; } export interface ICustomEditorService { @@ -56,6 +56,7 @@ export interface ICustomEditorModelManager { export interface ICustomEditorModel extends IDisposable { readonly viewType: string; readonly resource: URI; + readonly backupId: string | undefined; isReadonly(): boolean; diff --git a/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts b/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts index 5ef1db343c..281115b4ca 100644 --- a/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts +++ b/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts @@ -54,6 +54,10 @@ export class CustomTextEditorModel extends Disposable implements ICustomEditorMo return this._model.object.isReadonly(); } + public get backupId() { + return undefined; + } + public isDirty(): boolean { return this.textFileService.isDirty(this.resource); } diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 0670b9676d..479df0b4ad 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -11,7 +11,7 @@ import { IDebugService, State, IStackFrame, IDebugSession, IThread, CONTEXT_CALL import { Thread, StackFrame, ThreadAndSessionIds } from 'vs/workbench/contrib/debug/common/debugModel'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { MenuId, IMenu, IMenuService } from 'vs/platform/actions/common/actions'; +import { MenuId, IMenu, IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { IAction, Action } from 'vs/base/common/actions'; @@ -21,7 +21,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { ILabelService } from 'vs/platform/label/common/label'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; -import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { createAndFillInContextMenuActions, createAndFillInActionBarActions, MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeRenderer, ITreeNode, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; import { TreeResourceNavigator, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; @@ -40,6 +40,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { attachStylerCallback } from 'vs/platform/theme/common/styler'; +import { INotificationService } from 'vs/platform/notification/common/notification'; const $ = dom.$; @@ -62,7 +63,7 @@ export function getContext(element: CallStackItem | null): any { export function getContextForContributedActions(element: CallStackItem | null): string | number { if (element instanceof StackFrame) { if (element.source.inMemory) { - return element.source.raw.path || element.source.reference || ''; + return element.source.raw.path || element.source.reference || element.source.name; } return element.source.uri.toString(); @@ -87,7 +88,7 @@ export class CallStackView extends ViewPane { private callStackItemType: IContextKey; private dataSource!: CallStackDataSource; private tree!: WorkbenchAsyncDataTree; - private contributedContextMenu: IMenu; + private menu: IMenu; private parentSessionToExpand = new Set(); private selectionNeedsUpdate = false; @@ -109,8 +110,8 @@ export class CallStackView extends ViewPane { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.callStackItemType = CONTEXT_CALLSTACK_ITEM_TYPE.bindTo(contextKeyService); - this.contributedContextMenu = menuService.createMenu(MenuId.DebugCallStackContext, contextKeyService); - this._register(this.contributedContextMenu); + this.menu = menuService.createMenu(MenuId.DebugCallStackContext, contextKeyService); + this._register(this.menu); // Create scheduler to prevent unnecessary flashing of tree when reacting to changes this.onCallStackChangeScheduler = new RunOnceScheduler(() => { @@ -171,8 +172,9 @@ export class CallStackView extends ViewPane { const treeContainer = renderViewTree(container); this.dataSource = new CallStackDataSource(this.debugService); + const sessionsRenderer = this.instantiationService.createInstance(SessionsRenderer, this.menu); this.tree = >this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'CallStackView', treeContainer, new CallStackDelegate(), [ - new SessionsRenderer(this.instantiationService, this.debugService), + sessionsRenderer, new ThreadsRenderer(this.instantiationService), this.instantiationService.createInstance(StackFramesRenderer), new ErrorsRenderer(), @@ -378,12 +380,15 @@ export class CallStackView extends ViewPane { this.callStackItemType.reset(); } - const actions: IAction[] = []; - const actionsDisposable = createAndFillInContextMenuActions(this.contributedContextMenu, { arg: getContextForContributedActions(element), shouldForwardArgs: true }, actions, this.contextMenuService); + + const primary: IAction[] = []; + const secondary: IAction[] = []; + const result = { primary, secondary }; + const actionsDisposable = createAndFillInContextMenuActions(this.menu, { arg: getContextForContributedActions(element), shouldForwardArgs: true }, result, this.contextMenuService, g => /^inline/.test(g)); this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, - getActions: () => actions, + getActions: () => result.secondary, getActionsContext: () => getContext(element), onHide: () => dispose(actionsDisposable) }); @@ -406,6 +411,7 @@ interface ISessionTemplateData { stateLabel: HTMLSpanElement; label: HighlightedLabel; actionBar: ActionBar; + elementDisposable: IDisposable[]; } interface IErrorTemplateData { @@ -430,8 +436,12 @@ class SessionsRenderer implements ITreeRenderer { + if (action instanceof MenuItemAction) { + // We need the MenuEntryActionViewItem so the icon would get rendered + return new MenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService); + } - return { session, name, state, stateLabel, label, actionBar }; + return undefined; + } + }); + + return { session, name, state, stateLabel, label, actionBar, elementDisposable: [] }; } renderElement(element: ITreeNode, _: number, data: ISessionTemplateData): void { @@ -456,9 +475,19 @@ class SessionsRenderer implements ITreeRenderer t.stopped).pop(); - data.actionBar.clear(); - const actions = getActions(this.instantiationService, element.element); - data.actionBar.push(actions, { icon: true, label: false }); + const setActionBar = () => { + const actions = getActions(this.instantiationService, element.element); + + const primary: IAction[] = actions; + const secondary: IAction[] = []; + const result = { primary, secondary }; + data.elementDisposable.push(createAndFillInActionBarActions(this.menu, { arg: getContextForContributedActions(session), shouldForwardArgs: true }, result, g => /^inline/.test(g))); + + data.actionBar.clear(); + data.actionBar.push(primary, { icon: true, label: false }); + }; + setActionBar(); + data.elementDisposable.push(this.menu.onDidChange(() => setActionBar())); data.stateLabel.hidden = false; if (thread && thread.stoppedDetails) { @@ -476,6 +505,10 @@ class SessionsRenderer implements ITreeRenderer, _: number, templateData: ISessionTemplateData): void { + dispose(templateData.elementDisposable); + } } class ThreadsRenderer implements ITreeRenderer { diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index eda2f324cb..e181bb3b38 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -91,6 +91,7 @@ const openPanelKb: IKeybindings = { const VIEW_CONTAINER: ViewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ id: DEBUG_PANEL_ID, name: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugPanel' }, 'Debug Console'), + icon: 'codicon-debug-console', ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [DEBUG_PANEL_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), storageId: DEBUG_PANEL_ID, focusCommand: { diff --git a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts index 9b1aa12960..7ddc791d31 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts @@ -12,7 +12,7 @@ import { SelectBox, ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selec import { SelectActionViewItem, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IDebugService, IDebugSession, IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug'; +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'; @@ -36,6 +36,7 @@ export class StartDebugActionViewItem implements IActionViewItem { private options: { label: string, handler?: (() => boolean) }[] = []; private toDispose: IDisposable[]; private selected = 0; + private providers: { label: string, pick: () => Promise<{ launch: ILaunch, config: IConfig } | undefined> }[] = []; constructor( private context: unknown, @@ -127,6 +128,12 @@ export class StartDebugActionViewItem implements IActionViewItem { this.container.style.border = colors.selectBorder ? `1px solid ${colors.selectBorder}` : ''; selectBoxContainer.style.borderLeft = colors.selectBorder ? `1px solid ${colors.selectBorder}` : ''; })); + this.debugService.getConfigurationManager().getDynamicProviders().then(providers => { + this.providers = providers; + if (this.providers.length > 0) { + this.updateOptions(); + } + }); this.updateOptions(); } @@ -155,7 +162,7 @@ export class StartDebugActionViewItem implements IActionViewItem { this.toDispose = dispose(this.toDispose); } - private async updateOptions(): Promise { + private updateOptions(): void { this.selected = 0; this.options = []; const manager = this.debugService.getConfigurationManager(); @@ -191,8 +198,7 @@ export class StartDebugActionViewItem implements IActionViewItem { disabledIdxs.push(this.options.length - 1); } - const providers = await manager.getDynamicProviders(); - providers.forEach(p => { + this.providers.forEach(p => { this.options.push({ label: `${p.label}...`, handler: () => { StartAction.GET_CONFIG_AND_LAUNCH = p.pick; @@ -201,7 +207,7 @@ export class StartDebugActionViewItem implements IActionViewItem { }); }); - if (providers.length > 0) { + if (this.providers.length > 0) { this.options.push({ label: StartDebugActionViewItem.SEPARATOR, handler: undefined }); disabledIdxs.push(this.options.length - 1); } diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index d96199a7ff..b2745d2bfa 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -38,7 +38,7 @@ import { sequence } from 'vs/base/common/async'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { first } from 'vs/base/common/arrays'; import { getVisibleAndSorted } from 'vs/workbench/contrib/debug/common/debugUtils'; -import { DebugConfigurationProviderScope } from 'vs/workbench/api/common/extHostTypes'; +import { DebugConfigurationProviderTriggerKind } from 'vs/workbench/api/common/extHostTypes'; const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); jsonRegistry.registerSchema(launchSchemaId, launchSchema); @@ -195,14 +195,14 @@ export class ConfigurationManager implements IConfigurationManager { } /** - * if scope is not specified,a value of DebugConfigurationProviderScope.Initialization is assumed. + * if scope is not specified,a value of DebugConfigurationProvideTrigger.Initial is assumed. */ - hasDebugConfigurationProvider(debugType: string, scope?: DebugConfigurationProviderScope): boolean { - if (scope === undefined) { - scope = DebugConfigurationProviderScope.Initial; + hasDebugConfigurationProvider(debugType: string, triggerKind?: DebugConfigurationProviderTriggerKind): boolean { + if (triggerKind === undefined) { + 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.scope === scope)); + const providers = this.configProviders.filter(p => p.provideDebugConfigurations && (p.type === debugType) && (p.triggerKind === triggerKind)); return providers.length > 0; } @@ -241,22 +241,28 @@ export class ConfigurationManager implements IConfigurationManager { async provideDebugConfigurations(folderUri: uri | undefined, type: string, token: CancellationToken): Promise { await this.activateDebuggers('onDebugInitialConfigurations'); - const results = await Promise.all(this.configProviders.filter(p => p.type === type && p.scope === DebugConfigurationProviderScope.Initial && p.provideDebugConfigurations).map(p => p.provideDebugConfigurations!(folderUri, token))); + const results = await Promise.all(this.configProviders.filter(p => p.type === type && p.triggerKind === DebugConfigurationProviderTriggerKind.Initial && p.provideDebugConfigurations).map(p => p.provideDebugConfigurations!(folderUri, token))); return results.reduce((first, second) => first.concat(second), []); } async getDynamicProviders(): Promise<{ label: string, pick: () => Promise<{ launch: ILaunch, config: IConfig } | undefined> }[]> { - await this.activateDebuggers('onDebugDynamicConfigurations'); - const dynamicProviders = this.configProviders.filter(p => p.scope === DebugConfigurationProviderScope.Dynamic && p.provideDebugConfigurations); - return dynamicProviders.map(provider => { + 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; + }); + + return debugDynamicExtensions.map(e => { + const type = e.contributes?.debuggers![0].type!; return { - label: this.getDebuggerLabel(provider.type)!, + label: this.getDebuggerLabel(type)!, pick: async () => { + await this.activateDebuggers('onDebugDynamicConfigurations', 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]; this.getLaunches().forEach(launch => { - if (launch.workspace) { + if (launch.workspace && provider) { picks.push(provider.provideDebugConfigurations!(launch.workspace.uri, token.token).then(configurations => configurations.map(config => ({ label: config.name, config, diff --git a/src/vs/workbench/contrib/debug/browser/debugHover.ts b/src/vs/workbench/contrib/debug/browser/debugHover.ts index 2db17e12eb..3f95363259 100644 --- a/src/vs/workbench/contrib/debug/browser/debugHover.ts +++ b/src/vs/workbench/contrib/debug/browser/debugHover.ts @@ -281,16 +281,16 @@ export class DebugHoverWidget implements IContentWidget { } this.valueContainer.hidden = true; - this.complexValueContainer.hidden = false; + await this.tree.setInput(expression); this.complexValueTitle.textContent = expression.value; this.complexValueTitle.title = expression.value; this.layoutTreeAndContainer(); this.editor.layoutContentWidget(this); this.scrollbar.scanDomNode(); - await this.tree.setInput(expression); this.tree.scrollTop = 0; this.tree.scrollLeft = 0; + this.complexValueContainer.hidden = false; if (focus) { this.editor.render(); diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 233ef69397..2b78b5249c 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -170,7 +170,7 @@ export class DebugService implements IDebugService { this.activity.dispose(); } if (numberOfSessions > 0) { - this.activity = this.activityService.showActivity(VIEWLET_ID, new NumberBadge(numberOfSessions, n => n === 1 ? nls.localize('1activeSession', "1 active session") : nls.localize('nActiveSessions', "{0} active sessions", n))); + this.activity = this.activityService.showViewContainerActivity(VIEWLET_ID, { badge: new NumberBadge(numberOfSessions, n => n === 1 ? nls.localize('1activeSession', "1 active session") : nls.localize('nActiveSessions', "{0} active sessions", n)) }); } })); this.toDispose.push(this.model.onDidChangeBreakpoints(() => setBreakpointsExistContext())); diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index 5471c5453f..0f1185ae73 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -271,7 +271,6 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { if (this.$el) { this.$el.remove(); - delete this.$el; } if (this.disposeOnUpdate) { dispose(this.disposeOnUpdate); diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index 81e8cc990a..6509365fd3 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -31,6 +31,7 @@ font-size: 11px; margin-right: 0.3em; height: 20px; + line-height: 20px; flex-shrink: 1; margin-top: 7px; } @@ -40,15 +41,23 @@ } .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; - outline-offset: 2px; + font-size: inherit; + line-height: inherit; + outline-offset: 0px; } .monaco-workbench .monaco-action-bar .start-debug-action-item .configuration.disabled .monaco-select-box { @@ -75,6 +84,11 @@ cursor: initial; } +/* Make icons the same color as the list foreground on focus selection */ +.debug-pane .monaco-list:focus .monaco-list-row.selected.focused .codicon { + color: inherit !important; +} + /* Call stack */ .debug-pane .debug-call-stack-title { @@ -145,7 +159,9 @@ height: 100%; line-height: 22px; margin-right: 8px; - vertical-align: text-top; + background-size: 16px; + background-position: center center; + background-repeat: no-repeat; } .debug-pane .debug-call-stack .thread > .state > .label, diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index 49c9c0c90a..c09054ca8b 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -700,13 +700,13 @@ export class RawDebugSession implements IDisposable { "error" : { "classification": "CallstackOrException", "purpose": "FeatureInsight" } } */ - this.telemetryService.publicLog('debugProtocolErrorResponse', { error: telemetryMessage }); + this.telemetryService.publicLogError('debugProtocolErrorResponse', { error: telemetryMessage }); if (this.customTelemetryService) { /* __GDPR__TODO__ The message is sent in the name of the adapter but the adapter doesn't know about it. However, since adapters are an open-ended set, we can not declared the events statically either. */ - this.customTelemetryService.publicLog('debugProtocolErrorResponse', { error: telemetryMessage }, true); + this.customTelemetryService.publicLogError('debugProtocolErrorResponse', { error: telemetryMessage }); } } diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 392a7d06ef..c1ce98ff28 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -23,7 +23,7 @@ import { TaskIdentifier } from 'vs/workbench/contrib/tasks/common/tasks'; import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { DebugConfigurationProviderScope } from 'vs/workbench/api/common/extHostTypes'; +import { DebugConfigurationProviderTriggerKind } from 'vs/workbench/api/common/extHostTypes'; export const VIEWLET_ID = 'workbench.view.debug'; @@ -603,7 +603,7 @@ export interface IDebuggerContribution extends IPlatformSpecificAdapterContribut export interface IDebugConfigurationProvider { readonly type: string; - readonly scope: DebugConfigurationProviderScope; + readonly triggerKind: DebugConfigurationProviderTriggerKind; resolveDebugConfiguration?(folderUri: uri | undefined, debugConfiguration: IConfig, token: CancellationToken): Promise; resolveDebugConfigurationWithSubstitutedVariables?(folderUri: uri | undefined, debugConfiguration: IConfig, token: CancellationToken): Promise; provideDebugConfigurations?(folderUri: uri | undefined, token: CancellationToken): Promise; diff --git a/src/vs/workbench/contrib/debug/node/debugHelperService.ts b/src/vs/workbench/contrib/debug/node/debugHelperService.ts index d09bcf44df..a624e5eb05 100644 --- a/src/vs/workbench/contrib/debug/node/debugHelperService.ts +++ b/src/vs/workbench/contrib/debug/node/debugHelperService.ts @@ -33,7 +33,7 @@ export class NodeDebugHelperService implements IDebugHelperService { const channel = client.getChannel('telemetryAppender'); const appender = new TelemetryAppenderClient(channel); - return new TelemetryService({ appender }, configurationService); + return new TelemetryService({ appender, sendErrorTelemetry: true }, configurationService); } } diff --git a/src/vs/workbench/contrib/debug/node/terminals.ts b/src/vs/workbench/contrib/debug/node/terminals.ts index 1af4757ab8..41823658d0 100644 --- a/src/vs/workbench/contrib/debug/node/terminals.ts +++ b/src/vs/workbench/contrib/debug/node/terminals.ts @@ -164,8 +164,8 @@ export function prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments case ShellType.bash: quote = (s: string) => { - s = s.replace(/(["';\\])/g, '\\$1'); - return (s.indexOf(' ') >= 0 || s.length === 0) ? `"${s}"` : s; + s = s.replace(/(["'\\])/g, '\\$1'); + return (s.indexOf(' ') >= 0 || s.indexOf(';') >= 0 || s.length === 0) ? `"${s}"` : s; }; const hardQuote = (s: string) => { diff --git a/src/vs/workbench/contrib/experiments/common/experimentService.ts b/src/vs/workbench/contrib/experiments/common/experimentService.ts index 0d60729c1f..7bb9a720e2 100644 --- a/src/vs/workbench/contrib/experiments/common/experimentService.ts +++ b/src/vs/workbench/contrib/experiments/common/experimentService.ts @@ -10,7 +10,7 @@ import { ITelemetryService, lastSessionDateStorageKey } from 'vs/platform/teleme import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { language } from 'vs/base/common/platform'; +import { language, OperatingSystem, OS } from 'vs/base/common/platform'; import { Disposable } from 'vs/base/common/lifecycle'; import { match } from 'vs/base/common/glob'; import { IRequestService, asJson } from 'vs/platform/request/common/request'; @@ -93,7 +93,7 @@ interface IExperimentStorageState { * be incremented when adding a condition, otherwise experiments might activate * on older versions of VS Code where not intended. */ -export const currentSchemaVersion = 3; +export const currentSchemaVersion = 4; interface IRawExperiment { id: string; @@ -111,6 +111,7 @@ interface IRawExperiment { uniqueDays?: number; minEvents: number; }; + os: OperatingSystem[]; installedExtensions?: { excludes?: string[]; includes?: string[]; @@ -439,6 +440,10 @@ export class ExperimentService extends Disposable implements IExperimentService return Promise.resolve(ExperimentState.Run); } + if (experiment.condition?.os && !experiment.condition.os.includes(OS)) { + return Promise.resolve(ExperimentState.NoRun); + } + if (!this.checkExperimentDependencies(experiment)) { return Promise.resolve(ExperimentState.NoRun); } diff --git a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts index a7af651dec..ba7e374cc2 100644 --- a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts +++ b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts @@ -31,6 +31,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { IWillActivateEvent, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { timeout } from 'vs/base/common/async'; import { TestExtensionService } from 'vs/workbench/test/common/workbenchTestServices'; +import { OS } from 'vs/base/common/platform'; interface ExperimentSettings { enabled?: boolean; @@ -308,6 +309,44 @@ suite('Experiment Service', () => { }); }); + test('Experiment with OS should be enabled on current OS', () => { + experimentData = { + experiments: [ + { + id: 'experiment1', + enabled: true, + condition: { + os: [OS], + } + } + ] + }; + + testObject = instantiationService.createInstance(TestExperimentService); + return testObject.getExperimentById('experiment1').then(result => { + assert.equal(result.state, ExperimentState.Run); + }); + }); + + test('Experiment with OS should be disabled on other OS', () => { + experimentData = { + experiments: [ + { + id: 'experiment1', + enabled: true, + condition: { + os: [OS - 1], + } + } + ] + }; + + testObject = instantiationService.createInstance(TestExperimentService); + return testObject.getExperimentById('experiment1').then(result => { + assert.equal(result.state, ExperimentState.NoRun); + }); + }); + test('Activation event experiment with not enough events should be evaluating', () => { experimentData = { experiments: [ diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 8baf97671a..63a06dcd93 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -215,7 +215,7 @@ export class ExtensionEditor extends BaseEditor { const details = append(header, $('.details')); const title = append(details, $('.title')); - const name = append(title, $('span.name.clickable', { title: localize('name', "Extension name") })); + const name = append(title, $('span.name.clickable', { title: localize('name', "Extension name"), role: 'heading', tabIndex: 0 })); const identifier = append(title, $('span.identifier', { title: localize('extension id', "Extension identifier") })); const preview = append(title, $('span.preview', { title: localize('preview', "Preview") })); @@ -399,8 +399,8 @@ export class ExtensionEditor extends BaseEditor { } this.transientDisposables.add(this.onClick(template.publisher, () => { this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet?.getViewPaneContainer()) - .then((viewlet: IExtensionsViewPaneContainer) => { + .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) + .then(viewlet => { viewlet.search(`publisher:"${extension.publisherDisplayName}"`); }); })); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index a8ee32853a..df6ef5b797 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -643,7 +643,7 @@ export class StatusUpdater extends Disposable implements IWorkbenchContribution const outdated = this.extensionsWorkbenchService.outdated.reduce((r, e) => r + (this.extensionEnablementService.isEnabled(e.local!) ? 1 : 0), 0); if (outdated > 0) { const badge = new NumberBadge(outdated, n => localize('outdatedExtensions', '{0} Outdated Extensions', n)); - this.badgeHandle.value = this.activityService.showActivity(VIEWLET_ID, badge, 'extensions-badge count-badge'); + this.badgeHandle.value = this.activityService.showViewContainerActivity(VIEWLET_ID, { badge, clazz: 'extensions-badge count-badge' }); } } } diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 84a72dde01..b4fd280ddd 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/explorerviewlet'; import { localize } from 'vs/nls'; import * as DOM from 'vs/base/browser/dom'; -import { VIEWLET_ID, ExplorerViewletVisibleContext, IFilesConfiguration, OpenEditorsVisibleContext } from 'vs/workbench/contrib/files/common/files'; +import { VIEWLET_ID, ExplorerViewletVisibleContext, IFilesConfiguration, OpenEditorsVisibleContext, VIEW_ID } from 'vs/workbench/contrib/files/common/files'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { ExplorerView } from 'vs/workbench/contrib/files/browser/views/explorerView'; @@ -136,7 +136,7 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor private createExplorerViewDescriptor(): IViewDescriptor { return { - id: ExplorerView.ID, + id: VIEW_ID, name: localize('folders', "Folders"), containerIcon: Codicon.files.classNames, ctorDescriptor: new SyncDescriptor(ExplorerView), @@ -191,7 +191,7 @@ export class ExplorerViewPaneContainer extends ViewPaneContainer { } protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewPane { - if (viewDescriptor.id === ExplorerView.ID) { + if (viewDescriptor.id === VIEW_ID) { // Create a delegating editor service for the explorer to be able to delay the refresh in the opened // editors view above. This is a workaround for being able to double click on a file to make it pinned // without causing the animation in the opened editors view to kick in and change scroll position. @@ -232,7 +232,7 @@ export class ExplorerViewPaneContainer extends ViewPaneContainer { } public getExplorerView(): ExplorerView { - return this.getView(ExplorerView.ID); + return this.getView(VIEW_ID); } public getOpenEditorsView(): OpenEditorsView { @@ -245,7 +245,7 @@ export class ExplorerViewPaneContainer extends ViewPaneContainer { } focus(): void { - const explorerView = this.getView(ExplorerView.ID); + const explorerView = this.getView(VIEW_ID); if (explorerView?.isExpanded()) { explorerView.focus(); } else { diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index 98ae93cf38..93783558c2 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -5,17 +5,17 @@ import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; -import { ToggleAutoSaveAction, GlobalNewUntitledFileAction, FocusFilesExplorer, GlobalCompareResourcesAction, SaveAllAction, ShowActiveFileInExplorer, CollapseExplorerView, RefreshExplorerView, CompareWithClipboardAction, NEW_FILE_COMMAND_ID, NEW_FILE_LABEL, NEW_FOLDER_COMMAND_ID, NEW_FOLDER_LABEL, TRIGGER_RENAME_LABEL, MOVE_FILE_TO_TRASH_LABEL, COPY_FILE_LABEL, PASTE_FILE_LABEL, FileCopiedContext, renameHandler, moveFileToTrashHandler, copyFileHandler, pasteFileHandler, deleteFileHandler, cutFileHandler, DOWNLOAD_COMMAND_ID, openFilePreserveFocusHandler, DOWNLOAD_LABEL, ShowOpenedFileInNewWindow, ReopenResourcesAction, GlobalNewUntitledPlainFileAction } from 'vs/workbench/contrib/files/browser/fileActions'; +import { ToggleAutoSaveAction, GlobalNewUntitledFileAction, FocusFilesExplorer, GlobalCompareResourcesAction, SaveAllAction, ShowActiveFileInExplorer, CollapseExplorerView, RefreshExplorerView, CompareWithClipboardAction, NEW_FILE_COMMAND_ID, NEW_FILE_LABEL, NEW_FOLDER_COMMAND_ID, NEW_FOLDER_LABEL, TRIGGER_RENAME_LABEL, MOVE_FILE_TO_TRASH_LABEL, COPY_FILE_LABEL, PASTE_FILE_LABEL, FileCopiedContext, renameHandler, moveFileToTrashHandler, copyFileHandler, pasteFileHandler, deleteFileHandler, cutFileHandler, DOWNLOAD_COMMAND_ID, openFilePreserveFocusHandler, DOWNLOAD_LABEL, ShowOpenedFileInNewWindow, ReopenResourcesAction, ToggleEditorTypeCommand as ToggleEditorTypeAction, GlobalNewUntitledPlainFileAction } from 'vs/workbench/contrib/files/browser/fileActions'; import { revertLocalChangesCommand, acceptLocalChangesCommand, CONFLICT_RESOLUTION_CONTEXT } from 'vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler'; import { SyncActionDescriptor, MenuId, MenuRegistry, ILocalizedString } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; -import { openWindowCommand, COPY_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, OPEN_TO_SIDE_COMMAND_ID, REVERT_FILE_COMMAND_ID, SAVE_FILE_COMMAND_ID, SAVE_FILE_LABEL, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID, OpenEditorsGroupContext, COMPARE_WITH_SAVED_COMMAND_ID, COMPARE_RESOURCE_COMMAND_ID, SELECT_FOR_COMPARE_COMMAND_ID, ResourceSelectedForCompareContext, DirtyEditorContext, COMPARE_SELECTED_COMMAND_ID, REMOVE_ROOT_FOLDER_COMMAND_ID, REMOVE_ROOT_FOLDER_LABEL, SAVE_FILES_COMMAND_ID, COPY_RELATIVE_PATH_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_LABEL, newWindowCommand, ReadonlyEditorContext } from 'vs/workbench/contrib/files/browser/fileCommands'; +import { openWindowCommand, COPY_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, OPEN_TO_SIDE_COMMAND_ID, REVERT_FILE_COMMAND_ID, SAVE_FILE_COMMAND_ID, SAVE_FILE_LABEL, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID, OpenEditorsGroupContext, COMPARE_WITH_SAVED_COMMAND_ID, COMPARE_RESOURCE_COMMAND_ID, SELECT_FOR_COMPARE_COMMAND_ID, ResourceSelectedForCompareContext, DirtyEditorContext, COMPARE_SELECTED_COMMAND_ID, REMOVE_ROOT_FOLDER_COMMAND_ID, REMOVE_ROOT_FOLDER_LABEL, SAVE_FILES_COMMAND_ID, COPY_RELATIVE_PATH_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_LABEL, newWindowCommand, ReadonlyEditorContext, OPEN_WITH_EXPLORER_COMMAND_ID } from 'vs/workbench/contrib/files/browser/fileCommands'; import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { isMacintosh } from 'vs/base/common/platform'; -import { FilesExplorerFocusCondition, ExplorerRootContext, ExplorerFolderContext, ExplorerResourceNotReadonlyContext, ExplorerResourceCut, IExplorerService, ExplorerResourceMoveableToTrash, ExplorerViewletVisibleContext } from 'vs/workbench/contrib/files/common/files'; +import { FilesExplorerFocusCondition, ExplorerRootContext, ExplorerFolderContext, ExplorerResourceNotReadonlyContext, ExplorerResourceCut, IExplorerService, ExplorerResourceMoveableToTrash, ExplorerViewletVisibleContext, ExplorerResourceAvailableEditorIdsContext } from 'vs/workbench/contrib/files/common/files'; import { ADD_ROOT_FOLDER_COMMAND_ID, ADD_ROOT_FOLDER_LABEL } from 'vs/workbench/browser/actions/workspaceCommands'; import { CLOSE_SAVED_EDITORS_COMMAND_ID, CLOSE_EDITORS_IN_GROUP_COMMAND_ID, CLOSE_EDITOR_COMMAND_ID, CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { AutoSaveAfterShortDelayContext } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; @@ -26,7 +26,7 @@ import { WorkspaceFolderCountContext } from 'vs/workbench/browser/contextkeys'; import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { OpenFileFolderAction, OpenFileAction, OpenFolderAction, OpenWorkspaceAction } from 'vs/workbench/browser/actions/workspaceActions'; -import { ActiveEditorIsReadonlyContext, DirtyWorkingCopiesContext, ActiveEditorContext, ActiveEditorAvailableEditorsContext } from 'vs/workbench/common/editor'; +import { ActiveEditorIsReadonlyContext, DirtyWorkingCopiesContext, ActiveEditorContext, ActiveEditorAvailableEditorIdsContext } from 'vs/workbench/common/editor'; import { SidebarFocusContext } from 'vs/workbench/common/viewlet'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; @@ -36,7 +36,8 @@ const category = { value: nls.localize('filesCategory', "File"), original: 'File const registry = Registry.as(ActionExtensions.WorkbenchActions); registry.registerWorkbenchAction(SyncActionDescriptor.from(SaveAllAction, { primary: undefined, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_S }, win: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_S) } }), 'File: Save All', category.value); registry.registerWorkbenchAction(SyncActionDescriptor.from(GlobalCompareResourcesAction), 'File: Compare Active File With...', category.value); -registry.registerWorkbenchAction(SyncActionDescriptor.from(ReopenResourcesAction), 'File: Reopen With...', category.value, ActiveEditorAvailableEditorsContext); +registry.registerWorkbenchAction(SyncActionDescriptor.from(ReopenResourcesAction), 'File: Reopen With...', category.value, ActiveEditorAvailableEditorIdsContext); +registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleEditorTypeAction), 'File: Toggle Editor Type', category.value, ActiveEditorAvailableEditorIdsContext); registry.registerWorkbenchAction(SyncActionDescriptor.from(FocusFilesExplorer), 'File: Focus on Files Explorer', category.value); registry.registerWorkbenchAction(SyncActionDescriptor.from(ShowActiveFileInExplorer), 'File: Reveal Active File in Side Bar', category.value); registry.registerWorkbenchAction(SyncActionDescriptor.from(CollapseExplorerView), 'File: Collapse Folders in Explorer', category.value); @@ -193,7 +194,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { }, group: '6_reopen', order: 20, - when: ActiveEditorAvailableEditorsContext, + when: ActiveEditorAvailableEditorIdsContext, }); // Editor Title Menu for Conflict Resolution @@ -436,6 +437,16 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { when: ContextKeyExpr.and(ExplorerFolderContext.toNegated(), ResourceContextKey.HasResource) }); +MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { + group: 'navigation', + order: 20, + command: { + id: OPEN_WITH_EXPLORER_COMMAND_ID, + title: nls.localize('explorerOpenWith', "Open With..."), + }, + when: ContextKeyExpr.and(ExplorerRootContext.toNegated(), ExplorerResourceAvailableEditorIdsContext), +}); + MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { group: '3_compare', order: 20, diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 0c82352b7e..043c286198 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -13,12 +13,12 @@ import { toErrorMessage } from 'vs/base/common/errorMessage'; import * as strings from 'vs/base/common/strings'; import { Action } from 'vs/base/common/actions'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { VIEWLET_ID, IExplorerService, IFilesConfiguration, DEFAULT_EDITOR_ID } from 'vs/workbench/contrib/files/common/files'; +import { VIEWLET_ID, IExplorerService, IFilesConfiguration, VIEW_ID } from 'vs/workbench/contrib/files/common/files'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IFileService } from 'vs/platform/files/common/files'; -import { toResource, SideBySideEditor, IEditorInput } from 'vs/workbench/common/editor'; +import { toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { ExplorerViewPaneContainer } from 'vs/workbench/contrib/files/browser/explorerViewlet'; -import { IQuickInputService, ItemActivation, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickInputService, ItemActivation } from 'vs/platform/quickinput/common/quickInput'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ITextModel } from 'vs/editor/common/model'; @@ -34,7 +34,7 @@ import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { Schemas } from 'vs/base/common/network'; import { IDialogService, IConfirmationResult, getFileNamesMessage, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { IEditorService, IOpenEditorOverrideHandler } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Constants } from 'vs/base/common/uint'; import { CLOSE_EDITORS_AND_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { coalesce } from 'vs/base/common/arrays'; @@ -49,9 +49,9 @@ import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/commo import { once } from 'vs/base/common/functional'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { Codicon } from 'vs/base/common/codicons'; -import { CustomEditorsAssociations, customEditorsAssociationsSettingId, CustomEditorAssociation } from 'vs/workbench/services/editor/browser/editorAssociationsSetting'; +import { IViewsService } from 'vs/workbench/common/views'; +import { openEditorWith, getAllAvailableEditors } from 'vs/workbench/contrib/files/common/openWith'; export const NEW_FILE_COMMAND_ID = 'explorer.newFile'; export const NEW_FILE_LABEL = nls.localize('newFile', "New File"); @@ -81,10 +81,10 @@ function onError(notificationService: INotificationService, error: any): void { notificationService.error(toErrorMessage(error, false)); } -function refreshIfSeparator(value: string, explorerService: IExplorerService): void { +async function refreshIfSeparator(value: string, explorerService: IExplorerService): Promise { if (value && ((value.indexOf('/') >= 0) || (value.indexOf('\\') >= 0))) { // New input contains separator, multiple resources will get created workaround for #68204 - explorerService.refresh(); + await explorerService.refresh(); } } @@ -94,15 +94,10 @@ export class NewFileAction extends Action { static readonly LABEL = nls.localize('createNewFile', "New File"); constructor( - @IExplorerService explorerService: IExplorerService, @ICommandService private commandService: ICommandService ) { super('explorer.newFile', NEW_FILE_LABEL); this.class = 'explorer-action ' + Codicon.newFile.classNames; - this._register(explorerService.onDidChangeEditable(e => { - const elementIsBeingEdited = explorerService.isEditable(e); - this.enabled = !elementIsBeingEdited; - })); } run(): Promise { @@ -116,15 +111,10 @@ export class NewFolderAction extends Action { static readonly LABEL = nls.localize('createNewFolder', "New Folder"); constructor( - @IExplorerService explorerService: IExplorerService, @ICommandService private commandService: ICommandService ) { super('explorer.newFolder', NEW_FOLDER_LABEL); this.class = 'explorer-action ' + Codicon.newFolder.classNames; - this._register(explorerService.onDidChangeEditable(e => { - const elementIsBeingEdited = explorerService.isEditable(e); - this.enabled = !elementIsBeingEdited; - })); } run(): Promise { @@ -507,7 +497,7 @@ export class GlobalCompareResourcesAction extends Action { // Compare with next editor that opens const toDispose = this.editorService.overrideOpenEditor({ - getEditorOverrides: (editor: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup | undefined) => { + getEditorOverrides: (resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined) => { return []; }, open: editor => { @@ -542,7 +532,6 @@ export class GlobalCompareResourcesAction extends Action { } } -const builtinProviderDisplayName = nls.localize('builtinProviderDisplayName', "Built-in"); export class ReopenResourcesAction extends Action { static readonly ID = 'workbench.files.action.reopenWithEditor'; @@ -571,98 +560,44 @@ export class ReopenResourcesAction extends Action { const options = activeEditorPane.options; const group = activeEditorPane.group; - const activeResource = activeInput.resource; - if (!activeResource) { + await openEditorWith(activeInput, undefined, options, group, this.editorService, this.configurationService, this.quickInputService); + } +} + +export class ToggleEditorTypeCommand extends Action { + + static readonly ID = 'workbench.files.action.toggleEditorType'; + static readonly LABEL = nls.localize('workbench.files.action.toggleEditorType', "Toggle Editor Type"); + + constructor( + id: string, + label: string, + @IEditorService private readonly editorService: IEditorService, + ) { + super(id, label); + } + + async run(): Promise { + const activeEditorPane = this.editorService.activeEditorPane; + if (!activeEditorPane) { return; } - const resourceExt = extname(activeResource.path); - - const overrides = this.editorService.getEditorOverrides(activeInput, options, group); - const items: (IQuickPickItem & { handler?: IOpenEditorOverrideHandler })[] = overrides.map((override) => { - return { - handler: override[0], - id: override[1].id, - label: override[1].label, - description: override[1].active ? 'Currently Active' : undefined, - detail: override[1].detail, - buttons: resourceExt ? [{ - iconClass: 'codicon-settings-gear', - tooltip: nls.localize('promptOpenWith.setDefaultTooltip', "Set as default editor for '{0}' files", resourceExt) - }] : undefined - }; - }); - - if (!items.length) { + const input = activeEditorPane.input; + if (!input.resource) { return; } - if (!items.find(item => item.id === DEFAULT_EDITOR_ID)) { - items.unshift({ - id: DEFAULT_EDITOR_ID, - label: nls.localize('promptOpenWith.defaultEditor.displayName', "Text Editor"), - description: activeInput instanceof FileEditorInput ? 'Currently Active' : undefined, - detail: builtinProviderDisplayName, - buttons: resourceExt ? [{ - iconClass: 'codicon-settings-gear', - tooltip: nls.localize('promptOpenWith.setDefaultTooltip', "Set as default editor for '{0}' files", resourceExt) - }] : undefined - }); - } + const options = activeEditorPane.options; + const group = activeEditorPane.group; - const picker = this.quickInputService.createQuickPick<(IQuickPickItem & { handler?: IOpenEditorOverrideHandler })>(); - picker.items = items; - picker.placeholder = nls.localize('promptOpenWith.placeHolder', "Select editor to use for '{0}'...", resources.basename(activeResource)); - - const pickedItem = await new Promise<(IQuickPickItem & { handler?: IOpenEditorOverrideHandler }) | undefined>(resolve => { - picker.onDidAccept(() => { - resolve(picker.selectedItems.length === 1 ? picker.selectedItems[0] : undefined); - picker.dispose(); - }); - - picker.onDidTriggerItemButton(e => { - const pick = e.item; - const id = pick.id; - resolve(pick); // open the view - picker.dispose(); - - // And persist the setting - if (pick && id) { - const newAssociation: CustomEditorAssociation = { viewType: id, filenamePattern: '*' + resourceExt }; - const currentAssociations = [...this.configurationService.getValue(customEditorsAssociationsSettingId)] || []; - - // First try updating existing association - for (let i = 0; i < currentAssociations.length; ++i) { - const existing = currentAssociations[i]; - if (existing.filenamePattern === newAssociation.filenamePattern) { - currentAssociations.splice(i, 1, newAssociation); - this.configurationService.updateValue(customEditorsAssociationsSettingId, currentAssociations); - return; - } - } - - // Otherwise, create a new one - currentAssociations.unshift(newAssociation); - this.configurationService.updateValue(customEditorsAssociationsSettingId, currentAssociations); - } - }); - - picker.show(); - }); - - if (!pickedItem) { + const overrides = getAllAvailableEditors(input.resource, options, group, this.editorService); + const firstNonActiveOverride = overrides.find(([_, entry]) => !entry.active); + if (!firstNonActiveOverride) { return; } - if (pickedItem.id === DEFAULT_EDITOR_ID) { - const fileEditorInput = this.editorService.createEditorInput({ resource: activeResource!, forceFile: true }); - const textOptions = options ? { ...options, ignoreOverrides: true } : { ignoreOverrides: true }; - - await this.editorService.openEditor(fileEditorInput, textOptions, group); - return; - } - - await pickedItem.handler!.open(activeInput!, options, group, pickedItem.id); + await firstNonActiveOverride[0].open(input, options, group, firstNonActiveOverride[1].id)?.override; } } @@ -822,10 +757,6 @@ export class CollapseExplorerView extends Action { @IExplorerService readonly explorerService: IExplorerService ) { super(id, label, 'explorer-action ' + Codicon.collapseAll.classNames); - this._register(explorerService.onDidChangeEditable(e => { - const elementIsBeingEdited = explorerService.isEditable(e); - this.enabled = !elementIsBeingEdited; - })); } async run(): Promise { @@ -833,6 +764,11 @@ 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); + } } } } @@ -849,15 +785,11 @@ export class RefreshExplorerView extends Action { @IExplorerService private readonly explorerService: IExplorerService ) { super(id, label, 'explorer-action ' + Codicon.refresh.classNames); - this._register(explorerService.onDidChangeEditable(e => { - const elementIsBeingEdited = explorerService.isEditable(e); - this.enabled = !elementIsBeingEdited; - })); } async run(): Promise { await this.viewletService.openViewlet(VIEWLET_ID); - this.explorerService.refresh(); + await this.explorerService.refresh(); } } @@ -1045,10 +977,10 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole const fileService = accessor.get(IFileService); const textFileService = accessor.get(ITextFileService); const editorService = accessor.get(IEditorService); - const viewletService = accessor.get(IViewletService); + const viewsService = accessor.get(IViewsService); const notificationService = accessor.get(INotificationService); - await viewletService.openViewlet(VIEWLET_ID, true); + await viewsService.openView(VIEW_ID, true); const stats = explorerService.getContext(false); const stat = stats.length > 0 ? stats[0] : undefined; @@ -1064,15 +996,12 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole } const newStat = new NewExplorerItem(fileService, folder, isFolder); - const sortOrder = explorerService.sortOrder; - await folder.fetchChildren(sortOrder); - folder.addChild(newStat); const onSuccess = async (value: string): Promise => { try { const created = isFolder ? await fileService.createFolder(resources.joinPath(folder.resource, value)) : await textFileService.create(resources.joinPath(folder.resource, value)); - refreshIfSeparator(value, explorerService); + await refreshIfSeparator(value, explorerService); isFolder ? await explorerService.select(created.resource, true) : @@ -1082,11 +1011,11 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole } }; - explorerService.setEditable(newStat, { + await explorerService.setEditable(newStat, { validationMessage: value => validateFileName(newStat, value), - onFinish: (value, success) => { + onFinish: async (value, success) => { folder.removeChild(newStat); - explorerService.setEditable(newStat, null); + await explorerService.setEditable(newStat, null); if (success) { onSuccess(value); } @@ -1108,7 +1037,7 @@ CommandsRegistry.registerCommand({ } }); -export const renameHandler = (accessor: ServicesAccessor) => { +export const renameHandler = async (accessor: ServicesAccessor) => { const explorerService = accessor.get(IExplorerService); const workingCopyFileService = accessor.get(IWorkingCopyFileService); const notificationService = accessor.get(INotificationService); @@ -1119,7 +1048,7 @@ export const renameHandler = (accessor: ServicesAccessor) => { return; } - explorerService.setEditable(stat, { + await explorerService.setEditable(stat, { validationMessage: value => validateFileName(stat, value), onFinish: async (value, success) => { if (success) { @@ -1128,13 +1057,13 @@ export const renameHandler = (accessor: ServicesAccessor) => { if (stat.resource.toString() !== targetResource.toString()) { try { await workingCopyFileService.move(stat.resource, targetResource); - refreshIfSeparator(value, explorerService); + await refreshIfSeparator(value, explorerService); } catch (e) { notificationService.error(e); } } } - explorerService.setEditable(stat, null); + await explorerService.setEditable(stat, null); } }); }; diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index 95b0897d71..b7349b8e39 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -40,12 +40,16 @@ import { coalesce } from 'vs/base/common/arrays'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { openEditorWith } from 'vs/workbench/contrib/files/common/openWith'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; // Commands export const REVEAL_IN_EXPLORER_COMMAND_ID = 'revealInExplorer'; export const REVERT_FILE_COMMAND_ID = 'workbench.action.files.revert'; export const OPEN_TO_SIDE_COMMAND_ID = 'explorer.openToSide'; +export const OPEN_WITH_EXPLORER_COMMAND_ID = 'explorer.openWith'; export const SELECT_FOR_COMPARE_COMMAND_ID = 'selectForCompare'; export const COMPARE_SELECTED_COMMAND_ID = 'compareSelected'; @@ -313,6 +317,22 @@ CommandsRegistry.registerCommand({ } }); +CommandsRegistry.registerCommand({ + id: OPEN_WITH_EXPLORER_COMMAND_ID, + handler: async (accessor, resource: URI | object) => { + const editorService = accessor.get(IEditorService); + const editorGroupsService = accessor.get(IEditorGroupsService); + const configurationService = accessor.get(IConfigurationService); + const quickInputService = accessor.get(IQuickInputService); + + const uri = getResourceForCommand(resource, accessor.get(IListService), accessor.get(IEditorService)); + if (uri) { + const input = editorService.createEditorInput({ resource: uri }); + openEditorWith(input, undefined, undefined, editorGroupsService.activeGroup, editorService, configurationService, quickInputService); + } + } +}); + // Save / Save As / Save All / Revert async function saveSelectedEditors(accessor: ServicesAccessor, options?: ISaveEditorsOptions): Promise { diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 8a424a55b2..4a29beaac2 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -321,6 +321,11 @@ configurationRegistry.registerConfiguration({ 'markdownDescription': nls.localize('maxMemoryForLargeFilesMB', "Controls the memory available to Azure Data Studio after restart when trying to open large files. Same effect as specifying `--max-memory=NEWSIZE` on the command line."), // {{SQL CARBON EDIT}} Change product name to ADS included: platform.isNative }, + 'files.maxMemoryForClosedFilesUndoStackMB': { + 'type': 'number', + 'default': 20, + 'markdownDescription': nls.localize('maxMemoryForClosedFilesUndoStackMB', "Controls the maximum ammount of memory the undo stack should hold for files that have been closed.") + }, 'files.saveConflictResolution': { 'type': 'string', 'enum': [ diff --git a/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts b/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts index 3ac08e8586..4f86004996 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts @@ -56,11 +56,6 @@ export class ExplorerDecorationsProvider implements IDecorationsProvider { this.toDispose.add(contextService.onDidChangeWorkspaceFolders(e => { this._onDidChange.fire(e.changed.concat(e.added).map(wf => wf.uri)); })); - this.toDispose.add(explorerService.onDidChangeItem(change => { - if (change.item) { - this._onDidChange.fire([change.item.resource]); - } - })); this.toDispose.add(explorerRootErrorEmitter.event((resource => { this._onDidChange.fire([resource]); }))); diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 6e3c3c009e..593c6708ee 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import * as perf from 'vs/base/common/performance'; import { IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; import { memoize } from 'vs/base/common/decorators'; -import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, ExplorerRootContext, ExplorerResourceReadonlyContext, IExplorerService, ExplorerResourceCut, ExplorerResourceMoveableToTrash, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext, ExplorerCompressedLastFocusContext } from 'vs/workbench/contrib/files/common/files'; +import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, ExplorerRootContext, ExplorerResourceReadonlyContext, IExplorerService, ExplorerResourceCut, ExplorerResourceMoveableToTrash, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext, ExplorerCompressedLastFocusContext, ExplorerResourceAvailableEditorIdsContext } from 'vs/workbench/contrib/files/common/files'; import { NewFolderAction, NewFileAction, FileCopiedContext, RefreshExplorerView, CollapseExplorerView } from 'vs/workbench/contrib/files/browser/fileActions'; import { toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; @@ -123,7 +123,6 @@ export function getContext(focus: ExplorerItem[], selection: ExplorerItem[], res } export class ExplorerView extends ViewPane { - static readonly ID: string = 'workbench.explorer.fileView'; static readonly TREE_VIEW_STATE_STORAGE_KEY: string = 'workbench.explorer.treeViewState'; private tree!: WorkbenchCompressibleAsyncDataTree; @@ -132,12 +131,15 @@ export class ExplorerView extends ViewPane { private resourceContext: ResourceContextKey; private folderContext: IContextKey; private readonlyContext: IContextKey; + private availableEditorIdsContext: IContextKey; + private rootContext: IContextKey; private resourceMoveableToTrash: IContextKey; private renderer!: FilesRenderer; private styleElement!: HTMLStyleElement; + private treeContainer!: HTMLElement; private compressedFocusContext: IContextKey; private compressedFocusFirstContext: IContextKey; private compressedFocusLastContext: IContextKey; @@ -179,13 +181,14 @@ export class ExplorerView extends ViewPane { this.folderContext = ExplorerFolderContext.bindTo(contextKeyService); this.readonlyContext = ExplorerResourceReadonlyContext.bindTo(contextKeyService); + this.availableEditorIdsContext = ExplorerResourceAvailableEditorIdsContext.bindTo(contextKeyService); this.rootContext = ExplorerRootContext.bindTo(contextKeyService); this.resourceMoveableToTrash = ExplorerResourceMoveableToTrash.bindTo(contextKeyService); this.compressedFocusContext = ExplorerCompressedFocusContext.bindTo(contextKeyService); this.compressedFocusFirstContext = ExplorerCompressedFirstFocusContext.bindTo(contextKeyService); this.compressedFocusLastContext = ExplorerCompressedLastFocusContext.bindTo(contextKeyService); - this.explorerService.registerContextProvider(this); + this.explorerService.registerView(this); } get name(): string { @@ -245,48 +248,17 @@ export class ExplorerView extends ViewPane { renderBody(container: HTMLElement): void { super.renderBody(container); - const treeContainer = DOM.append(container, DOM.$('.explorer-folders-view')); + this.treeContainer = DOM.append(container, DOM.$('.explorer-folders-view')); - this.styleElement = DOM.createStyleSheet(treeContainer); + this.styleElement = DOM.createStyleSheet(this.treeContainer); attachStyler(this.themeService, { listDropBackground }, this.styleListDropBackground.bind(this)); - this.createTree(treeContainer); + this.createTree(this.treeContainer); this._register(this.labelService.onDidChangeFormatters(() => { this._onDidChangeTitleArea.fire(); })); - this._register(this.explorerService.onDidChangeRoots(() => this.setTreeInput())); - this._register(this.explorerService.onDidChangeItem(e => { - if (this.explorerService.isEditable(undefined)) { - this.tree.domFocus(); - } - this.refresh(e.recursive, e.item); - })); - this._register(this.explorerService.onDidChangeEditable(async e => { - const isEditing = !!this.explorerService.getEditableData(e); - - if (isEditing) { - if (e.parent !== this.tree.getInput()) { - await this.tree.expand(e.parent!); - this.tree.reveal(e.parent!); - } - } else { - DOM.removeClass(treeContainer, 'highlight'); - } - - await this.refresh(false, e.parent); - - if (isEditing) { - DOM.addClass(treeContainer, 'highlight'); - this.tree.reveal(e); - } else { - this.tree.domFocus(); - } - })); - this._register(this.explorerService.onDidSelectResource(e => this.onSelectResource(e.resource, e.reveal))); - this._register(this.explorerService.onDidCopyItems(e => this.onCopyItems(e.items, e.cut, e.previouslyCutItems))); - // Update configuration const configuration = this.configurationService.getValue(); this.onConfigurationUpdated(configuration); @@ -338,6 +310,29 @@ export class ExplorerView extends ViewPane { return getContext(this.tree.getFocus(), this.tree.getSelection(), respectMultiSelection, this.renderer); } + 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); + } + } else { + DOM.removeClass(this.treeContainer, 'highlight'); + } + + if (shouldRefresh) { + await this.refresh(false, stat.parent); + } + + if (isEditing) { + DOM.addClass(this.treeContainer, 'highlight'); + this.tree.reveal(stat); + } else { + this.tree.domFocus(); + } + } + private selectActiveFile(deselect?: boolean, reveal = this.autoReveal): void { if (this.autoReveal) { const activeFile = this.getActiveFile(); @@ -443,10 +438,10 @@ export class ExplorerView extends ViewPane { this._register(this.tree.onContextMenu(e => this.onContextMenu(e))); - this._register(this.tree.onDidScroll(e => { + this._register(this.tree.onDidScroll(async e => { let editable = this.explorerService.getEditable(); if (e.scrollTopChanged && editable && this.tree.getRelativeTop(editable.stat) === null) { - editable.data.onFinish('', false); + await editable.data.onFinish('', false); } })); @@ -474,6 +469,13 @@ export class ExplorerView extends ViewPane { this.folderContext.set((isSingleFolder && !stat) || !!stat && stat.isDirectory); this.readonlyContext.set(!!stat && stat.isReadonly); this.rootContext.set(!stat || (stat && stat.isRoot)); + + if (resource) { + const overrides = resource ? this.editorService.getEditorOverrides(resource, undefined, undefined) : []; + this.availableEditorIdsContext.set(overrides.map(([, entry]) => entry.id).join(',')); + } else { + this.availableEditorIdsContext.reset(); + } } private onContextMenu(e: ITreeContextMenuEvent): void { @@ -556,19 +558,18 @@ 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. */ - private refresh(recursive: boolean, item?: ExplorerItem): Promise { - if (!this.tree || !this.isBodyVisible()) { + refresh(recursive: boolean, item?: ExplorerItem): Promise { + if (!this.tree || !this.isBodyVisible() || (item && !this.tree.hasNode(item))) { + // Tree node doesn't exist yet this.shouldRefresh = true; return Promise.resolve(undefined); } - // Tree node doesn't exist yet - if (item && !this.tree.hasNode(item)) { - return Promise.resolve(undefined); + if (this.explorerService.isEditable(undefined)) { + this.tree.domFocus(); } const toRefresh = item || this.tree.getInput(); - return this.tree.updateChildren(toRefresh, recursive); } @@ -579,7 +580,7 @@ export class ExplorerView extends ViewPane { return DOM.getLargestChildWidth(parentNode, childNodes); } - private async setTreeInput(): Promise { + async setTreeInput(): Promise { if (!this.isBodyVisible()) { this.shouldRefresh = true; return Promise.resolve(undefined); @@ -647,7 +648,7 @@ export class ExplorerView extends ViewPane { return withNullAsUndefined(toResource(input, { supportSideBySide: SideBySideEditor.MASTER })); } - private async onSelectResource(resource: URI | undefined, reveal = this.autoReveal, retry = 0): Promise { + public async selectResource(resource: URI | undefined, reveal = this.autoReveal, retry = 0): Promise { // do no retry more than once to prevent inifinite loops in cases of inconsistent model if (retry === 2) { return; @@ -664,10 +665,11 @@ export class ExplorerView extends ViewPane { .sort((first, second) => second.resource.path.length - first.resource.path.length)[0]; while (item && item.resource.toString() !== resource.toString()) { - if (item.isDisposed) { - return this.onSelectResource(resource, reveal, retry + 1); + try { + await this.tree.expand(item); + } catch (e) { + return this.selectResource(resource, reveal, retry + 1); } - await this.tree.expand(item); item = first(values(item.children), i => isEqualOrParent(resource, i.resource, ignoreCase)); } @@ -680,10 +682,6 @@ export class ExplorerView extends ViewPane { try { if (reveal) { - if (item.isDisposed) { - return this.onSelectResource(resource, reveal, retry + 1); - } - // Don't scroll to the item if it's already visible if (this.tree.getRelativeTop(item) === null) { this.tree.reveal(item, 0.5); @@ -693,12 +691,13 @@ export class ExplorerView extends ViewPane { this.tree.setFocus([item]); this.tree.setSelection([item]); } catch (e) { - // Element might not be in the tree, silently fail + // Element might not be in the tree, try again and silently fail + return this.selectResource(resource, reveal, retry + 1); } } } - private onCopyItems(stats: ExplorerItem[], cut: boolean, previousCut: ExplorerItem[] | undefined): void { + itemsCopied(stats: ExplorerItem[], cut: boolean, previousCut: ExplorerItem[] | undefined): void { this.fileCopiedContextKey.set(stats.length > 0); this.resourceCutContextKey.set(cut && stats.length > 0); if (previousCut) { diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 65b6de1289..f4e41e49f4 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -445,12 +445,15 @@ 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); diff --git a/src/vs/workbench/contrib/files/common/dirtyFilesIndicator.ts b/src/vs/workbench/contrib/files/common/dirtyFilesIndicator.ts index 73312ac412..5702c89499 100644 --- a/src/vs/workbench/contrib/files/common/dirtyFilesIndicator.ts +++ b/src/vs/workbench/contrib/files/common/dirtyFilesIndicator.ts @@ -55,10 +55,12 @@ export class DirtyFilesIndicator extends Disposable implements IWorkbenchContrib // Indicate dirty count in badge if any if (dirtyCount > 0) { - this.badgeHandle.value = this.activityService.showActivity( + this.badgeHandle.value = this.activityService.showViewContainerActivity( VIEWLET_ID, - new NumberBadge(dirtyCount, num => num === 1 ? nls.localize('dirtyFile', "1 unsaved file") : nls.localize('dirtyFiles', "{0} unsaved files", dirtyCount)), - 'explorer-viewlet-label' + { + badge: new NumberBadge(dirtyCount, num => num === 1 ? nls.localize('dirtyFile', "1 unsaved file") : nls.localize('dirtyFiles', "{0} unsaved files", dirtyCount)), + clazz: 'explorer-viewlet-label' + } ); } else { this.badgeHandle.clear(); diff --git a/src/vs/workbench/contrib/files/common/explorerModel.ts b/src/vs/workbench/contrib/files/common/explorerModel.ts index 0541a925c6..4da6227058 100644 --- a/src/vs/workbench/contrib/files/common/explorerModel.ts +++ b/src/vs/workbench/contrib/files/common/explorerModel.ts @@ -78,7 +78,6 @@ export class ExplorerModel implements IDisposable { export class ExplorerItem { protected _isDirectoryResolved: boolean; - private _isDisposed: boolean; public isError = false; private _isExcluded = false; @@ -93,7 +92,6 @@ export class ExplorerItem { private _unknown = false ) { this._isDirectoryResolved = false; - this._isDisposed = false; } get isExcluded(): boolean { @@ -111,10 +109,6 @@ export class ExplorerItem { this._isExcluded = value; } - get isDisposed(): boolean { - return this._isDisposed; - } - get isDirectoryResolved(): boolean { return this._isDirectoryResolved; } @@ -258,9 +252,11 @@ export class ExplorerItem { } }); - for (let child of oldLocalChildren.values()) { - child._dispose(); - } + oldLocalChildren.forEach(oldChild => { + if (oldChild instanceof NewExplorerItem) { + local.addChild(oldChild); + } + }); } } @@ -310,20 +306,10 @@ export class ExplorerItem { } forgetChildren(): void { - for (let c of this.children.values()) { - c._dispose(); - } this.children.clear(); this._isDirectoryResolved = false; } - private _dispose() { - this._isDisposed = true; - for (let child of this.children.values()) { - child._dispose(); - } - } - private getPlatformAwareName(name: string): string { return this.fileService.hasCapability(this.resource, FileSystemProviderCapabilities.PathCaseSensitive) ? name : name.toLowerCase(); } diff --git a/src/vs/workbench/contrib/files/common/explorerService.ts b/src/vs/workbench/contrib/files/common/explorerService.ts index c3aa6c0f8b..70bcd3a0de 100644 --- a/src/vs/workbench/contrib/files/common/explorerService.ts +++ b/src/vs/workbench/contrib/files/common/explorerService.ts @@ -3,13 +3,13 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event, Emitter } from 'vs/base/common/event'; +import { Event } from 'vs/base/common/event'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { IExplorerService, IFilesConfiguration, SortOrder, IContextProvider } from 'vs/workbench/contrib/files/common/files'; +import { IExplorerService, IFilesConfiguration, SortOrder, IExplorerView } from 'vs/workbench/contrib/files/common/files'; import { ExplorerItem, ExplorerModel } from 'vs/workbench/contrib/files/common/explorerModel'; import { URI } from 'vs/base/common/uri'; -import { FileOperationEvent, FileOperation, IFileStat, IFileService, FileChangesEvent, FILES_EXCLUDE_CONFIG, FileChangeType, IResolveFileOptions } from 'vs/platform/files/common/files'; +import { FileOperationEvent, FileOperation, IFileService, FileChangesEvent, FILES_EXCLUDE_CONFIG, FileChangeType, IResolveFileOptions } from 'vs/platform/files/common/files'; import { dirname } from 'vs/base/common/resources'; import { memoize } from 'vs/base/common/decorators'; import { ResourceGlobMatcher } from 'vs/workbench/common/resources'; @@ -32,16 +32,11 @@ export class ExplorerService implements IExplorerService { private static readonly EXPLORER_FILE_CHANGES_REACT_DELAY = 500; // delay in ms to react to file changes to give our internal events a chance to react first - private readonly _onDidChangeRoots = new Emitter(); - private readonly _onDidChangeItem = new Emitter<{ item?: ExplorerItem, recursive: boolean }>(); - private readonly _onDidChangeEditable = new Emitter(); - private readonly _onDidSelectResource = new Emitter<{ resource?: URI, reveal?: boolean }>(); - private readonly _onDidCopyItems = new Emitter<{ items: ExplorerItem[], cut: boolean, previouslyCutItems: ExplorerItem[] | undefined }>(); private readonly disposables = new DisposableStore(); private editable: { stat: ExplorerItem, data: IEditableData } | undefined; private _sortOrder: SortOrder; private cutItems: ExplorerItem[] | undefined; - private contextProvider: IContextProvider | undefined; + private view: IExplorerView | undefined; private model: ExplorerModel; constructor( @@ -59,7 +54,7 @@ export class ExplorerService implements IExplorerService { this.disposables.add(this.fileService.onDidRunOperation(e => this.onDidRunOperation(e))); this.disposables.add(this.fileService.onDidFilesChange(e => this.onDidFilesChange(e))); this.disposables.add(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getValue()))); - this.disposables.add(Event.any<{ scheme: string }>(this.fileService.onDidChangeFileSystemProviderRegistrations, this.fileService.onDidChangeFileSystemProviderCapabilities)(e => { + this.disposables.add(Event.any<{ scheme: string }>(this.fileService.onDidChangeFileSystemProviderRegistrations, this.fileService.onDidChangeFileSystemProviderCapabilities)(async e => { let affected = false; this.model.roots.forEach(r => { if (r.resource.scheme === e.scheme) { @@ -68,50 +63,35 @@ export class ExplorerService implements IExplorerService { } }); if (affected) { - this._onDidChangeItem.fire({ recursive: true }); + if (this.view) { + await this.view.refresh(true); + } + } + })); + this.disposables.add(this.model.onDidChangeRoots(() => { + if (this.view) { + this.view.setTreeInput(); } })); - this.disposables.add(this.model.onDidChangeRoots(() => this._onDidChangeRoots.fire())); } get roots(): ExplorerItem[] { return this.model.roots; } - get onDidChangeRoots(): Event { - return this._onDidChangeRoots.event; - } - - get onDidChangeItem(): Event<{ item?: ExplorerItem, recursive: boolean }> { - return this._onDidChangeItem.event; - } - - get onDidChangeEditable(): Event { - return this._onDidChangeEditable.event; - } - - get onDidSelectResource(): Event<{ resource?: URI, reveal?: boolean }> { - return this._onDidSelectResource.event; - } - - get onDidCopyItems(): Event<{ items: ExplorerItem[], cut: boolean, previouslyCutItems: ExplorerItem[] | undefined }> { - return this._onDidCopyItems.event; - } - get sortOrder(): SortOrder { return this._sortOrder; } - registerContextProvider(contextProvider: IContextProvider): void { - this.contextProvider = contextProvider; + registerView(contextProvider: IExplorerView): void { + this.view = contextProvider; } getContext(respectMultiSelection: boolean): ExplorerItem[] { - if (!this.contextProvider) { + if (!this.view) { return []; } - - return this.contextProvider.getContext(respectMultiSelection); + return this.view.getContext(respectMultiSelection); } // Memoized locals @@ -132,13 +112,18 @@ export class ExplorerService implements IExplorerService { return this.model.findClosest(resource); } - setEditable(stat: ExplorerItem, data: IEditableData | null): void { + async setEditable(stat: ExplorerItem, data: IEditableData | null): Promise { + if (!this.view) { + return; + } + if (!data) { this.editable = undefined; } else { this.editable = { stat, data }; } - this._onDidChangeEditable.fire(stat); + const isEditing = this.isEditable(stat); + await this.view.setEditable(stat, isEditing); } setToCopy(items: ExplorerItem[], cut: boolean): void { @@ -146,7 +131,7 @@ export class ExplorerService implements IExplorerService { this.cutItems = cut ? items : undefined; this.clipboardService.writeResources(items.map(s => s.resource)); - this._onDidCopyItems.fire({ items, cut, previouslyCutItems }); + this.view?.itemsCopied(items, cut, previouslyCutItems); } isCut(item: ExplorerItem): boolean { @@ -166,9 +151,12 @@ export class ExplorerService implements IExplorerService { } async select(resource: URI, reveal?: boolean): Promise { + if (!this.view) { + return; + } const fileStat = this.findClosest(resource); if (fileStat) { - this._onDidSelectResource.fire({ resource: fileStat.resource, reveal }); + await this.view.selectResource(fileStat.resource, reveal); return Promise.resolve(undefined); } @@ -190,31 +178,33 @@ export class ExplorerService implements IExplorerService { // Update Input with disk Stat ExplorerItem.mergeLocalWithDisk(modelStat, root); const item = root.find(resource); - this._onDidChangeItem.fire({ item: root, recursive: true }); + await this.view.refresh(true, root); // Select and Reveal - this._onDidSelectResource.fire({ resource: item ? item.resource : undefined, reveal }); + await this.view.selectResource(item ? item.resource : undefined, reveal); } catch (error) { root.isError = true; - this._onDidChangeItem.fire({ item: root, recursive: false }); + await this.view.refresh(false, root); } } - refresh(): void { + async refresh(reveal = true): Promise { this.model.roots.forEach(r => r.forgetChildren()); - this._onDidChangeItem.fire({ recursive: true }); - const resource = this.editorService.activeEditor ? this.editorService.activeEditor.resource : undefined; - const autoReveal = this.configurationService.getValue().explorer.autoReveal; + if (this.view) { + await this.view.refresh(true); + const resource = this.editorService.activeEditor ? this.editorService.activeEditor.resource : undefined; + const autoReveal = this.configurationService.getValue().explorer.autoReveal; - if (resource && autoReveal) { - // We did a top level refresh, reveal the active file #67118 - this.select(resource, true); + if (reveal && resource && autoReveal) { + // We did a top level refresh, reveal the active file #67118 + this.select(resource, true); + } } } // File events - private onDidRunOperation(e: FileOperationEvent): void { + private async onDidRunOperation(e: FileOperationEvent): Promise { // Add if (e.isOperation(FileOperation.CREATE) || e.isOperation(FileOperation.COPY)) { const addedElement = e.target; @@ -224,23 +214,23 @@ export class ExplorerService implements IExplorerService { if (parents.length) { // Add the new file to its parent (Model) - parents.forEach(p => { + parents.forEach(async p => { // We have to check if the parent is resolved #29177 const resolveMetadata = this.sortOrder === `modified`; - const thenable: Promise = p.isDirectoryResolved ? Promise.resolve(undefined) : this.fileService.resolve(p.resource, { resolveMetadata }); - thenable.then(stat => { + if (!p.isDirectoryResolved) { + const stat = await this.fileService.resolve(p.resource, { resolveMetadata }); if (stat) { const modelStat = ExplorerItem.create(this.fileService, stat, p.parent); ExplorerItem.mergeLocalWithDisk(modelStat, p); } + } - const childElement = ExplorerItem.create(this.fileService, addedElement, p.parent); - // Make sure to remove any previous version of the file if any - p.removeChild(childElement); - p.addChild(childElement); - // Refresh the Parent (View) - this._onDidChangeItem.fire({ item: p, recursive: false }); - }); + const childElement = ExplorerItem.create(this.fileService, addedElement, p.parent); + // Make sure to remove any previous version of the file if any + p.removeChild(childElement); + p.addChild(childElement); + // Refresh the Parent (View) + await this.view?.refresh(false, p); }); } } @@ -255,10 +245,10 @@ export class ExplorerService implements IExplorerService { // Handle Rename if (oldParentResource.toString() === newParentResource.toString()) { const modelElements = this.model.findAll(oldResource); - modelElements.forEach(modelElement => { + modelElements.forEach(async modelElement => { // Rename File (Model) modelElement.rename(newElement); - this._onDidChangeItem.fire({ item: modelElement.parent, recursive: false }); + await this.view?.refresh(false, modelElement.parent); }); } @@ -269,11 +259,11 @@ export class ExplorerService implements IExplorerService { if (newParents.length && modelElements.length) { // Move in Model - modelElements.forEach((modelElement, index) => { + modelElements.forEach(async (modelElement, index) => { const oldParent = modelElement.parent; modelElement.move(newParents[index]); - this._onDidChangeItem.fire({ item: oldParent, recursive: false }); - this._onDidChangeItem.fire({ item: newParents[index], recursive: false }); + await this.view?.refresh(false, oldParent); + await this.view?.refresh(false, newParents[index]); }); } } @@ -282,13 +272,13 @@ export class ExplorerService implements IExplorerService { // Delete else if (e.isOperation(FileOperation.DELETE)) { const modelElements = this.model.findAll(e.resource); - modelElements.forEach(element => { + modelElements.forEach(async element => { if (element.parent) { const parent = element.parent; // Remove Element from Parent (Model) parent.removeChild(element); // Refresh Parent (View) - this._onDidChangeItem.fire({ item: parent, recursive: false }); + await this.view?.refresh(false, parent); } }); } @@ -298,7 +288,7 @@ export class ExplorerService implements IExplorerService { // Check if an explorer refresh is necessary (delayed to give internal events a chance to react first) // Note: there is no guarantee when the internal events are fired vs real ones. Code has to deal with the fact that one might // be fired first over the other or not at all. - setTimeout(() => { + setTimeout(async () => { // Filter to the ones we care const shouldRefresh = () => { e = this.filterToViewRelevantEvents(e); @@ -365,8 +355,7 @@ export class ExplorerService implements IExplorerService { }; if (shouldRefresh()) { - this.roots.forEach(r => r.forgetChildren()); - this._onDidChangeItem.fire({ recursive: true }); + await this.refresh(false); } }, ExplorerService.EXPLORER_FILE_CHANGES_REACT_DELAY); } @@ -389,13 +378,13 @@ export class ExplorerService implements IExplorerService { })); } - private onConfigurationUpdated(configuration: IFilesConfiguration, event?: IConfigurationChangeEvent): void { - const configSortOrder = configuration?.explorer?.sortOrder || SortOrder.Default; // {{SQL CARBON EDIT}} strict-null-checks? + private async onConfigurationUpdated(configuration: IFilesConfiguration, event?: IConfigurationChangeEvent): Promise { + const configSortOrder = configuration?.explorer?.sortOrder || 'default'; if (this._sortOrder !== configSortOrder) { const shouldRefresh = this._sortOrder !== undefined; - this._sortOrder = configSortOrder; + this._sortOrder = configSortOrder as SortOrder; // {{SQL CARBON EDIT}} strict-null-checks if (shouldRefresh) { - this.refresh(); + await this.refresh(); } } } diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts index 950687b396..80c52885e2 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -11,7 +11,6 @@ import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/con import { ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { ITextModel } from 'vs/editor/common/model'; -import { Event } from 'vs/base/common/event'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService, ILanguageSelection } from 'vs/editor/common/services/modeService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; @@ -29,6 +28,11 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic */ export const VIEWLET_ID = 'workbench.view.explorer'; +/** + * Explorer file view id. + */ +export const VIEW_ID = 'workbench.explorer.fileView'; + /** * Id of the default editor for open with. */ @@ -38,20 +42,15 @@ export interface IExplorerService { _serviceBrand: undefined; readonly roots: ExplorerItem[]; readonly sortOrder: SortOrder; - readonly onDidChangeRoots: Event; - readonly onDidChangeItem: Event<{ item?: ExplorerItem, recursive: boolean }>; - readonly onDidChangeEditable: Event; - readonly onDidSelectResource: Event<{ resource?: URI, reveal?: boolean }>; - readonly onDidCopyItems: Event<{ items: ExplorerItem[], cut: boolean, previouslyCutItems: ExplorerItem[] | undefined }>; getContext(respectMultiSelection: boolean): ExplorerItem[]; - setEditable(stat: ExplorerItem, data: IEditableData | null): void; + setEditable(stat: ExplorerItem, data: IEditableData | null): Promise; getEditable(): { stat: ExplorerItem, data: IEditableData } | undefined; getEditableData(stat: ExplorerItem): IEditableData | undefined; // If undefined is passed checks if any element is currently being edited. isEditable(stat: ExplorerItem | undefined): boolean; findClosest(resource: URI): ExplorerItem | null; - refresh(): void; + refresh(): Promise; setToCopy(stats: ExplorerItem[], cut: boolean): void; isCut(stat: ExplorerItem): boolean; @@ -61,11 +60,16 @@ export interface IExplorerService { */ select(resource: URI, reveal?: boolean): Promise; - registerContextProvider(contextProvider: IContextProvider): void; + registerView(contextAndRefreshProvider: IExplorerView): void; } -export interface IContextProvider { +export interface IExplorerView { getContext(respectMultiSelection: boolean): ExplorerItem[]; + refresh(recursive: boolean, item?: ExplorerItem): Promise; + selectResource(resource: URI | undefined, reveal?: boolean): Promise; + setTreeInput(): Promise; + itemsCopied(tats: ExplorerItem[], cut: boolean, previousCut: ExplorerItem[] | undefined): void; + setEditable(stat: ExplorerItem, isEditing: boolean): Promise; } export const IExplorerService = createDecorator('explorerService'); @@ -77,6 +81,10 @@ export const ExplorerViewletVisibleContext = new RawContextKey('explore export const ExplorerFolderContext = new RawContextKey('explorerResourceIsFolder', false); export const ExplorerResourceReadonlyContext = new RawContextKey('explorerResourceReadonly', false); export const ExplorerResourceNotReadonlyContext = ExplorerResourceReadonlyContext.toNegated(); +/** + * Comma separated list of editor ids that can be used for the selected explorer resource. + */ +export const ExplorerResourceAvailableEditorIdsContext = new RawContextKey('explorerResourceAvailableEditorIds', ''); export const ExplorerRootContext = new RawContextKey('explorerResourceIsRoot', false); export const ExplorerResourceCut = new RawContextKey('explorerResourceCut', false); export const ExplorerResourceMoveableToTrash = new RawContextKey('explorerResourceMoveableToTrash', false); diff --git a/src/vs/workbench/contrib/files/common/openWith.ts b/src/vs/workbench/contrib/files/common/openWith.ts new file mode 100644 index 0000000000..323e9b178f --- /dev/null +++ b/src/vs/workbench/contrib/files/common/openWith.ts @@ -0,0 +1,150 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { basename, extname, isEqual } from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; +import * as nls from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { IEditorInput, IEditorPane } from 'vs/workbench/common/editor'; +import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; +import { DEFAULT_EDITOR_ID } from 'vs/workbench/contrib/files/common/files'; +import { CustomEditorAssociation, CustomEditorsAssociations, customEditorsAssociationsSettingId } from 'vs/workbench/services/editor/common/editorAssociationsSetting'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorService, IOpenEditorOverrideEntry, IOpenEditorOverrideHandler } from 'vs/workbench/services/editor/common/editorService'; + +const builtinProviderDisplayName = nls.localize('builtinProviderDisplayName', "Built-in"); + +/** + * Try to open an resource with a given editor. + * + * @param input Resource to open. + * @param id Id of the editor to use. If not provided, the user is prompted for which editor to use. + */ +export async function openEditorWith( + input: IEditorInput, + id: string | undefined, + options: IEditorOptions | ITextEditorOptions | undefined, + group: IEditorGroup, + editorService: IEditorService, + configurationService: IConfigurationService, + quickInputService: IQuickInputService, +): Promise { + const resource = input.resource; + if (!resource) { + return undefined; // {{SQL CARBON EDIT}} strict-null-checks + } + + const allEditorOverrides = getAllAvailableEditors(resource, options, group, editorService); + if (!allEditorOverrides.length) { + return undefined; // {{SQL CARBON EDIT}} strict-null-checks + } + + const overrideToUse = typeof id === 'string' && allEditorOverrides.find(([_, entry]) => entry.id === id); + if (overrideToUse) { + return overrideToUse[0].open(input, options, group, id)?.override; + } + + // Prompt + const resourceExt = extname(resource); + + const items: (IQuickPickItem & { handler: IOpenEditorOverrideHandler })[] = allEditorOverrides.map((override) => { + return { + handler: override[0], + id: override[1].id, + label: override[1].label, + description: override[1].active ? nls.localize('promptOpenWith.currentlyActive', 'Currently Active') : undefined, + detail: override[1].detail, + buttons: resourceExt ? [{ + iconClass: 'codicon-settings-gear', + tooltip: nls.localize('promptOpenWith.setDefaultTooltip', "Set as default editor for '{0}' files", resourceExt) + }] : undefined + }; + }); + + const picker = quickInputService.createQuickPick<(IQuickPickItem & { handler: IOpenEditorOverrideHandler })>(); + picker.items = items; + if (items.length) { + picker.selectedItems = [items[0]]; + } + picker.placeholder = nls.localize('promptOpenWith.placeHolder', "Select editor for '{0}'", basename(resource)); + + const pickedItem = await new Promise<(IQuickPickItem & { handler: IOpenEditorOverrideHandler }) | undefined>(resolve => { + picker.onDidAccept(() => { + resolve(picker.selectedItems.length === 1 ? picker.selectedItems[0] : undefined); + picker.dispose(); + }); + + picker.onDidTriggerItemButton(e => { + const pick = e.item; + const id = pick.id; + resolve(pick); // open the view + picker.dispose(); + + // And persist the setting + if (pick && id) { + const newAssociation: CustomEditorAssociation = { viewType: id, filenamePattern: '*' + resourceExt }; + const currentAssociations = [...configurationService.getValue(customEditorsAssociationsSettingId)]; + + // First try updating existing association + for (let i = 0; i < currentAssociations.length; ++i) { + const existing = currentAssociations[i]; + if (existing.filenamePattern === newAssociation.filenamePattern) { + currentAssociations.splice(i, 1, newAssociation); + configurationService.updateValue(customEditorsAssociationsSettingId, currentAssociations); + return; + } + } + + // Otherwise, create a new one + currentAssociations.unshift(newAssociation); + configurationService.updateValue(customEditorsAssociationsSettingId, currentAssociations); + } + }); + + picker.show(); + }); + + return pickedItem?.handler.open(input!, options, group, pickedItem.id)?.override; +} + +export const defaultEditorOverrideEntry = Object.freeze({ + id: DEFAULT_EDITOR_ID, + label: nls.localize('promptOpenWith.defaultEditor.displayName', "Text Editor"), + detail: builtinProviderDisplayName, +}); + +/** + * Get a list of all available editors, including the default text editor. + */ +export function getAllAvailableEditors( + resource: URI, + options: IEditorOptions | ITextEditorOptions | undefined, + group: IEditorGroup, + editorService: IEditorService, +): Array<[IOpenEditorOverrideHandler, IOpenEditorOverrideEntry]> { + const overrides = editorService.getEditorOverrides(resource, options, group); + if (!overrides.some(([_, entry]) => entry.id === DEFAULT_EDITOR_ID)) { + overrides.unshift([ + { + open: (input: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup) => { + if (!input.resource) { + return undefined; // {{SQL CARBON EDIT}} strict-null-checks + } + + const fileEditorInput = editorService.createEditorInput({ resource: input.resource, forceFile: true }); + const textOptions = options ? { ...options, ignoreOverrides: true } : { ignoreOverrides: true }; + return { override: editorService.openEditor(fileEditorInput, textOptions, group) }; + } + }, + { + ...defaultEditorOverrideEntry, + active: editorService.activeEditor instanceof FileEditorInput && isEqual(editorService.activeEditor.resource, resource), + }]); + } + return overrides; +} + diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index 844d0e9bfb..77f321af39 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -113,6 +113,7 @@ class ToggleMarkersPanelAction extends ToggleViewAction { const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: Constants.MARKERS_CONTAINER_ID, name: Messages.MARKERS_PANEL_TITLE_PROBLEMS, + icon: Codicon.warning.classNames, hideIfEmpty: true, order: 0, ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [Constants.MARKERS_CONTAINER_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), diff --git a/src/vs/workbench/contrib/markers/browser/markers.ts b/src/vs/workbench/contrib/markers/browser/markers.ts index ded38247fc..3642a4ab67 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.ts @@ -5,9 +5,9 @@ import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { MarkersModel, compareMarkersByUri } from './markersModel'; -import { Disposable, MutableDisposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { IMarkerService, MarkerSeverity, IMarker } from 'vs/platform/markers/common/markers'; -import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; +import { NumberBadge, ViewContaierActivityByView } from 'vs/workbench/services/activity/common/activity'; import { localize } from 'vs/nls'; import Constants from './constants'; import { URI } from 'vs/base/common/uri'; @@ -55,21 +55,22 @@ export class MarkersWorkbenchService extends Disposable implements IMarkersWorkb export class ActivityUpdater extends Disposable implements IWorkbenchContribution { - private readonly activity = this._register(new MutableDisposable()); + private readonly activity: ViewContaierActivityByView; constructor( - @IActivityService private readonly activityService: IActivityService, + @IInstantiationService instantiationService: IInstantiationService, @IMarkerService private readonly markerService: IMarkerService ) { super(); - this._register(this.markerService.onMarkerChanged(() => this.updateBadge())); - this.updateBadge(); + this.activity = this._register(instantiationService.createInstance(ViewContaierActivityByView, Constants.MARKERS_VIEW_ID)); + this._register(this.markerService.onMarkerChanged(() => this.updateActivity())); + this.updateActivity(); } - private updateBadge(): void { + private updateActivity(): void { const { errors, warnings, infos } = this.markerService.getStatistics(); const total = errors + warnings + infos; const message = localize('totalProblems', 'Total {0} Problems', total); - this.activity.value = this.activityService.showActivity(Constants.MARKERS_CONTAINER_ID, new NumberBadge(total, () => message)); + this.activity.setActivity({ badge: new NumberBadge(total, () => message) }); } } diff --git a/src/vs/workbench/contrib/markers/browser/markersView.ts b/src/vs/workbench/contrib/markers/browser/markersView.ts index d7e91c8810..edb27d8b19 100644 --- a/src/vs/workbench/contrib/markers/browser/markersView.ts +++ b/src/vs/workbench/contrib/markers/browser/markersView.ts @@ -114,8 +114,13 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.smallLayoutContextKey = Constants.MarkersViewSmallLayoutContextKey.bindTo(this.contextKeyService); this.panelState = new Memento(Constants.MARKERS_VIEW_STORAGE_ID, storageService).getMemento(StorageScope.WORKSPACE); + this.markersViewModel = this._register(instantiationService.createInstance(MarkersViewModel, this.panelState['multiline'])); + for (const resourceMarker of this.markersWorkbenchService.markersModel.resourceMarkers) { + resourceMarker.markers.forEach(marker => this.markersViewModel.add(marker)); + } this._register(this.markersViewModel.onDidChange(marker => this.onDidChangeViewState(marker))); + this.setCurrentActiveEditor(); this.filter = new Filter(new FilterOptions()); @@ -860,6 +865,10 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { super.saveState(); } + dispose() { + super.dispose(); + } + } class MarkersTree extends WorkbenchObjectTree { diff --git a/src/vs/workbench/contrib/markers/browser/markersViewActions.ts b/src/vs/workbench/contrib/markers/browser/markersViewActions.ts index c83997c06c..459d333752 100644 --- a/src/vs/workbench/contrib/markers/browser/markersViewActions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersViewActions.ts @@ -335,6 +335,10 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { this._register(DOM.addStandardDisposableListener(this.filterInputBox.inputElement, DOM.EventType.KEY_DOWN, (e: any) => this.onInputKeyDown(e, this.filterInputBox!))); this._register(DOM.addStandardDisposableListener(container, DOM.EventType.KEY_DOWN, this.handleKeyboardEvent)); this._register(DOM.addStandardDisposableListener(container, DOM.EventType.KEY_UP, this.handleKeyboardEvent)); + this._register(DOM.addStandardDisposableListener(this.filterInputBox.inputElement, DOM.EventType.CLICK, (e) => { + e.stopPropagation(); + e.preventDefault(); + })); const focusTracker = this._register(DOM.trackFocus(this.filterInputBox.inputElement)); this._register(focusTracker.onDidFocus(() => this.focusContextKey.set(true))); diff --git a/src/vs/workbench/contrib/notebook/browser/constants.ts b/src/vs/workbench/contrib/notebook/browser/constants.ts index 88d691135b..0e516d4fcd 100644 --- a/src/vs/workbench/contrib/notebook/browser/constants.ts +++ b/src/vs/workbench/contrib/notebook/browser/constants.ts @@ -5,27 +5,15 @@ // Cell sizing related export const CELL_MARGIN = 20; -export const CELL_RUN_GUTTER = 32; // TODO should be dynamic based on execution order width, and runnable enablement +export const CELL_RUN_GUTTER = 32; export const EDITOR_TOOLBAR_HEIGHT = 0; -export const BOTTOM_CELL_TOOLBAR_HEIGHT = 32; +export const BOTTOM_CELL_TOOLBAR_HEIGHT = 36; export const CELL_STATUSBAR_HEIGHT = 22; // Top margin of editor -export const EDITOR_TOP_MARGIN = 16; +export const EDITOR_TOP_MARGIN = 0; // Top and bottom padding inside the monaco editor in a cell, which are included in `cell.editorHeight` export const EDITOR_TOP_PADDING = 12; export const EDITOR_BOTTOM_PADDING = 12; - -// Cell context keys - -export const NOTEBOOK_VIEW_TYPE = 'notebookViewType'; -export const NOTEBOOK_CELL_TYPE_CONTEXT_KEY = 'notebookCellType'; // code, markdown -export const NOTEBOOK_CELL_EDITABLE_CONTEXT_KEY = 'notebookCellEditable'; // bool -export const NOTEBOOK_CELL_MARKDOWN_EDIT_MODE_CONTEXT_KEY = 'notebookCellMarkdownEditMode'; // bool -export const NOTEBOOK_CELL_RUN_STATE_CONTEXT_KEY = 'notebookCellRunState'; // idle, running - -// Notebook context keys -export const NOTEBOOK_EDITABLE_CONTEXT_KEY = 'notebookEditable'; -export const NOTEBOOK_EXECUTING_KEY = 'notebookExecuting'; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts index dc06b5627a..d750681720 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts @@ -12,37 +12,59 @@ import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/commo import { InputFocusedContext, InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { NOTEBOOK_CELL_EDITABLE_CONTEXT_KEY, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE_CONTEXT_KEY, NOTEBOOK_CELL_TYPE_CONTEXT_KEY, NOTEBOOK_EDITABLE_CONTEXT_KEY, NOTEBOOK_EXECUTING_KEY } from 'vs/workbench/contrib/notebook/browser/constants'; -import { BaseCellRenderTemplate, CellEditState, CellRunState, ICellViewModel, INotebookEditor, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { BaseCellRenderTemplate, CellEditState, CellRunState, ICellViewModel, INotebookEditor, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_RUNNABLE, NOTEBOOK_CELL_TYPE, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_EDITABLE } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; 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 { IModeService } from 'vs/editor/common/services/modeService'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; +import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; +import { URI } from 'vs/base/common/uri'; -const INSERT_CODE_CELL_ABOVE_COMMAND_ID = 'workbench.notebook.code.insertCellAbove'; -const INSERT_CODE_CELL_BELOW_COMMAND_ID = 'workbench.notebook.code.insertCellBelow'; -const INSERT_MARKDOWN_CELL_ABOVE_COMMAND_ID = 'workbench.notebook.markdown.insertCellAbove'; -const INSERT_MARKDOWN_CELL_BELOW_COMMAND_ID = 'workbench.notebook.markdown.insertCellBelow'; +// Notebook Commands +const EXECUTE_NOTEBOOK_COMMAND_ID = 'notebook.execute'; +const CANCEL_NOTEBOOK_COMMAND_ID = 'notebook.cancelExecution'; +const NOTEBOOK_FOCUS_TOP = 'notebook.focusTop'; +const NOTEBOOK_FOCUS_BOTTOM = 'notebook.focusBottom'; +const NOTEBOOK_REDO = 'notebook.redo'; +const NOTEBOOK_UNDO = 'notebook.undo'; +const NOTEBOOK_CURSOR_UP = 'notebook.cursorUp'; +const NOTEBOOK_CURSOR_DOWN = 'notebook.cursorDown'; +const CLEAR_ALL_CELLS_OUTPUTS_COMMAND_ID = 'notebook.clearAllCellsOutputs'; -const EDIT_CELL_COMMAND_ID = 'workbench.notebook.cell.edit'; -const SAVE_CELL_COMMAND_ID = 'workbench.notebook.cell.save'; -const DELETE_CELL_COMMAND_ID = 'workbench.notebook.cell.delete'; +// Cell Commands +const INSERT_CODE_CELL_ABOVE_COMMAND_ID = 'notebook.cell.insertCodeCellAbove'; +const INSERT_CODE_CELL_BELOW_COMMAND_ID = 'notebook.cell.insertCodeCellBelow'; +const INSERT_MARKDOWN_CELL_ABOVE_COMMAND_ID = 'notebook.cell.insertMarkdownCellAbove'; +const INSERT_MARKDOWN_CELL_BELOW_COMMAND_ID = 'notebook.cell.insertMarkdownCellBelow'; +const CHANGE_CELL_TO_CODE_COMMAND_ID = 'notebook.cell.changeToCode'; +const CHANGE_CELL_TO_MARKDOWN_COMMAND_ID = 'notebook.cell.changeToMarkdown'; -const MOVE_CELL_UP_COMMAND_ID = 'workbench.notebook.cell.moveUp'; -const MOVE_CELL_DOWN_COMMAND_ID = 'workbench.notebook.cell.moveDown'; -const COPY_CELL_COMMAND_ID = 'workbench.notebook.cell.copy'; -const CUT_CELL_COMMAND_ID = 'workbench.notebook.cell.cut'; -const PASTE_CELL_COMMAND_ID = 'workbench.notebook.cell.paste'; -const PASTE_CELL_ABOVE_COMMAND_ID = 'workbench.notebook.cell.pasteAbove'; -const COPY_CELL_UP_COMMAND_ID = 'workbench.notebook.cell.copyUp'; -const COPY_CELL_DOWN_COMMAND_ID = 'workbench.notebook.cell.copyDown'; +const EDIT_CELL_COMMAND_ID = 'notebook.cell.edit'; +const QUIT_EDIT_CELL_COMMAND_ID = 'notebook.cell.quitEdit'; +const SAVE_CELL_COMMAND_ID = 'notebook.cell.save'; +const DELETE_CELL_COMMAND_ID = 'notebook.cell.delete'; -const EXECUTE_CELL_COMMAND_ID = 'workbench.notebook.cell.execute'; -const CANCEL_CELL_COMMAND_ID = 'workbench.notebook.cell.cancelExecution'; -const EXECUTE_NOTEBOOK_COMMAND_ID = 'workbench.notebook.executeNotebook'; -const CANCEL_NOTEBOOK_COMMAND_ID = 'workbench.notebook.cancelExecution'; +const MOVE_CELL_UP_COMMAND_ID = 'notebook.cell.moveUp'; +const MOVE_CELL_DOWN_COMMAND_ID = 'notebook.cell.moveDown'; +const COPY_CELL_COMMAND_ID = 'notebook.cell.copy'; +const CUT_CELL_COMMAND_ID = 'notebook.cell.cut'; +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 NOTEBOOK_ACTIONS_CATEGORY = localize('notebookActions.category', "Notebook"); +const EXECUTE_CELL_COMMAND_ID = 'notebook.cell.execute'; +const CANCEL_CELL_COMMAND_ID = 'notebook.cell.cancelExecution'; +const EXECUTE_CELL_SELECT_BELOW = 'notebook.cell.executeAndSelectBelow'; +const EXECUTE_CELL_INSERT_BELOW = 'notebook.cell.executeAndInsertBelow'; +const CLEAR_CELL_OUTPUTS_COMMAND_ID = 'notebook.cell.clearOutputs'; +const CHANGE_CELL_LANGUAGE = 'notebook.cell.changeLanguage'; + + +export const NOTEBOOK_ACTIONS_CATEGORY = localize('notebookActions.category', "Notebook"); const EDITOR_WIDGET_ACTION_WEIGHT = KeybindingWeight.EditorContrib; // smaller than Suggest Widget, etc @@ -51,6 +73,7 @@ const enum CellToolbarOrder { MoveCellDown, EditCell, SaveCell, + ClearCellOutput, InsertCell, DeleteCell } @@ -62,7 +85,7 @@ registerAction2(class extends Action2 { category: NOTEBOOK_ACTIONS_CATEGORY, title: localize('notebookActions.execute', "Execute Cell"), keybinding: { - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext), + when: NOTEBOOK_EDITOR_FOCUSED, primary: KeyMod.WinCtrl | KeyCode.Enter, win: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Enter @@ -82,7 +105,7 @@ registerAction2(class extends Action2 { } } - runCell(context); + return runCell(context); } }); @@ -149,10 +172,11 @@ export class CancelCellAction extends MenuItemAction { registerAction2(class extends Action2 { constructor() { super({ - id: 'workbench.action.executeNotebookCellSelectBelow', + id: EXECUTE_CELL_SELECT_BELOW, title: localize('notebookActions.executeAndSelectBelow', "Execute Notebook Cell and Select Below"), + category: NOTEBOOK_ACTIONS_CATEGORY, keybinding: { - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext), + when: NOTEBOOK_EDITOR_FOCUSED, primary: KeyMod.Shift | KeyCode.Enter, weight: EDITOR_WIDGET_ACTION_WEIGHT } @@ -182,7 +206,9 @@ registerAction2(class extends Action2 { editor.focusNotebookCell(nextCell, activeCell.editState === CellEditState.Editing); } else { const newCell = editor.insertNotebookCell(activeCell, CellKind.Code, 'below'); - editor.focusNotebookCell(newCell, true); + if (newCell) { + editor.focusNotebookCell(newCell, true); + } } } }); @@ -190,10 +216,11 @@ registerAction2(class extends Action2 { registerAction2(class extends Action2 { constructor() { super({ - id: 'workbench.action.executeNotebookCellInsertBelow', + id: EXECUTE_CELL_INSERT_BELOW, title: localize('notebookActions.executeAndInsertBelow', "Execute Notebook Cell and Insert Below"), + category: NOTEBOOK_ACTIONS_CATEGORY, keybinding: { - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext), + when: NOTEBOOK_EDITOR_FOCUSED, primary: KeyMod.Alt | KeyCode.Enter, weight: EDITOR_WIDGET_ACTION_WEIGHT } @@ -213,7 +240,9 @@ registerAction2(class extends Action2 { } const newCell = editor.insertNotebookCell(activeCell, CellKind.Code, 'below'); - editor.focusNotebookCell(newCell, true); + if (newCell) { + editor.focusNotebookCell(newCell, true); + } } }); @@ -262,8 +291,9 @@ registerAction2(class extends Action2 { registerAction2(class extends Action2 { constructor() { super({ - id: 'workbench.action.quitNotebookEdit', + id: QUIT_EDIT_CELL_COMMAND_ID, title: localize('notebookActions.quitEditing', "Quit Notebook Cell Editing"), + category: NOTEBOOK_ACTIONS_CATEGORY, keybinding: { when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext), primary: KeyCode.Escape, @@ -295,17 +325,19 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: EXECUTE_NOTEBOOK_COMMAND_ID, title: localize('notebookActions.menu.executeNotebook', "Execute Notebook (Run all cells)"), + category: NOTEBOOK_ACTIONS_CATEGORY, icon: { id: 'codicon/run-all' } }, order: -1, group: 'navigation', - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(NOTEBOOK_EXECUTING_KEY)) + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK.toNegated(), NOTEBOOK_EDITOR_RUNNABLE) }); MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: CANCEL_NOTEBOOK_COMMAND_ID, title: localize('notebookActions.menu.cancelNotebook', "Stop Notebook Execution"), + category: NOTEBOOK_ACTIONS_CATEGORY, icon: { id: 'codicon/primitive-square' } }, order: -1, @@ -318,17 +350,18 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: EXECUTE_CELL_COMMAND_ID, title: localize('notebookActions.menu.execute', "Execute Notebook Cell"), + category: NOTEBOOK_ACTIONS_CATEGORY, icon: { id: 'codicon/run' } }, order: 0, group: 'navigation', - when: NOTEBOOK_EDITOR_FOCUSED + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_CELL_RUNNABLE) }); registerAction2(class extends Action2 { constructor() { super({ - id: 'workbench.action.changeCellToCode', + id: CHANGE_CELL_TO_CODE_COMMAND_ID, title: localize('notebookActions.changeCellToCode', "Change Cell to Code"), keybinding: { when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), @@ -348,7 +381,7 @@ registerAction2(class extends Action2 { registerAction2(class extends Action2 { constructor() { super({ - id: 'workbench.action.changeCellToMarkdown', + id: CHANGE_CELL_TO_MARKDOWN_COMMAND_ID, title: localize('notebookActions.changeCellToMarkdown', "Change Cell to Markdown"), keybinding: { when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), @@ -407,30 +440,46 @@ async function changeActiveCellToKind(kind: CellKind, accessor: ServicesAccessor return; } - if (activeCell.cellKind === kind) { - return; + changeCellToKind(kind, { cell: activeCell, notebookEditor: editor }); +} + +export async function changeCellToKind(kind: CellKind, context: INotebookCellActionContext, language?: string): Promise { + const { cell, notebookEditor } = context; + + if (cell.cellKind === kind) { + return null; } - const text = activeCell.getText(); - editor.insertNotebookCell(activeCell, kind, 'below', text); - const idx = editor.viewModel?.getCellIndex(activeCell); + const text = cell.getText(); + if (!notebookEditor.insertNotebookCell(cell, kind, 'below', text)) { + return null; + } + + const idx = notebookEditor.viewModel?.getCellIndex(cell); if (typeof idx !== 'number') { - return; + return null; } - const newCell = editor.viewModel?.viewCells[idx + 1]; + const newCell = notebookEditor.viewModel?.viewCells[idx + 1]; if (!newCell) { - return; + return null; } - editor.focusNotebookCell(newCell, activeCell.editState === CellEditState.Editing); - editor.deleteNotebookCell(activeCell); + if (language) { + newCell.model.language = language; + } + + notebookEditor.focusNotebookCell(newCell, cell.editState === CellEditState.Editing); + notebookEditor.deleteNotebookCell(cell); + + return newCell; } export interface INotebookCellActionContext { cellTemplate?: BaseCellRenderTemplate; cell: ICellViewModel; notebookEditor: INotebookEditor; + ui?: boolean; } function isCellActionContext(context: any): context is INotebookCellActionContext { @@ -473,8 +522,10 @@ abstract class InsertCellCommand extends Action2 { } } - const newCell = context.notebookEditor.insertNotebookCell(context.cell, this.kind, this.direction); - context.notebookEditor.focusNotebookCell(newCell, true); + const newCell = context.notebookEditor.insertNotebookCell(context.cell, this.kind, this.direction, undefined, context.ui); + if (newCell) { + context.notebookEditor.focusNotebookCell(newCell, true); + } } } @@ -583,9 +634,9 @@ registerAction2(class extends Action2 { menu: { id: MenuId.NotebookCellTitle, when: ContextKeyExpr.and( - ContextKeyExpr.equals(NOTEBOOK_CELL_TYPE_CONTEXT_KEY, 'markdown'), - ContextKeyExpr.equals(NOTEBOOK_CELL_MARKDOWN_EDIT_MODE_CONTEXT_KEY, false), - ContextKeyExpr.equals(NOTEBOOK_CELL_EDITABLE_CONTEXT_KEY, true)), + NOTEBOOK_CELL_TYPE.isEqualTo('markdown'), + NOTEBOOK_CELL_MARKDOWN_EDIT_MODE.toNegated(), + NOTEBOOK_CELL_EDITABLE), order: CellToolbarOrder.EditCell }, icon: { id: 'codicon/pencil' } @@ -613,9 +664,9 @@ registerAction2(class extends Action2 { menu: { id: MenuId.NotebookCellTitle, when: ContextKeyExpr.and( - ContextKeyExpr.equals(NOTEBOOK_CELL_TYPE_CONTEXT_KEY, 'markdown'), - ContextKeyExpr.equals(NOTEBOOK_CELL_MARKDOWN_EDIT_MODE_CONTEXT_KEY, true), - ContextKeyExpr.equals(NOTEBOOK_CELL_EDITABLE_CONTEXT_KEY, true)), + NOTEBOOK_CELL_TYPE.isEqualTo('markdown'), + NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, + NOTEBOOK_CELL_EDITABLE), order: CellToolbarOrder.SaveCell }, icon: { id: 'codicon/check' } @@ -645,7 +696,7 @@ registerAction2(class extends Action2 { menu: { id: MenuId.NotebookCellTitle, order: CellToolbarOrder.DeleteCell, - when: ContextKeyExpr.equals(NOTEBOOK_EDITABLE_CONTEXT_KEY, true) + when: NOTEBOOK_EDITOR_EDITABLE }, keybinding: { primary: KeyCode.Delete, @@ -679,7 +730,9 @@ registerAction2(class extends Action2 { } else { // No cells left, insert a new empty one const newCell = context.notebookEditor.insertNotebookCell(undefined, context.cell.cellKind); - context.notebookEditor.focusNotebookCell(newCell, true); + if (newCell) { + context.notebookEditor.focusNotebookCell(newCell, true); + } } } } @@ -687,8 +740,8 @@ registerAction2(class extends Action2 { async function moveCell(context: INotebookCellActionContext, direction: 'up' | 'down'): Promise { const result = direction === 'up' ? - context.notebookEditor.moveCellUp(context.cell) : - context.notebookEditor.moveCellDown(context.cell); + await context.notebookEditor.moveCellUp(context.cell) : + await context.notebookEditor.moveCellDown(context.cell); if (result) { // move cell command only works when the cell container has focus @@ -700,7 +753,9 @@ async function copyCell(context: INotebookCellActionContext, direction: 'up' | ' const text = context.cell.getText(); const newCellDirection = direction === 'up' ? 'above' : 'below'; const newCell = context.notebookEditor.insertNotebookCell(context.cell, context.cell.cellKind, newCellDirection, text); - context.notebookEditor.focusNotebookCell(newCell, false); + if (newCell) { + context.notebookEditor.focusNotebookCell(newCell, false); + } } registerAction2(class extends Action2 { @@ -952,8 +1007,9 @@ registerAction2(class extends Action2 { registerAction2(class extends Action2 { constructor() { super({ - id: 'workbench.action.notebook.cursorDown', - title: 'Notebook Cursor Move Down', + id: NOTEBOOK_CURSOR_DOWN, + title: localize('cursorMoveDown', 'Cursor Move Down'), + category: NOTEBOOK_ACTIONS_CATEGORY, keybinding: { when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.has(InputFocusedContextKey), EditorContextKeys.editorTextFocus, NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('top'), NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none')), primary: KeyCode.DownArrow, @@ -991,8 +1047,9 @@ registerAction2(class extends Action2 { registerAction2(class extends Action2 { constructor() { super({ - id: 'workbench.action.notebook.cursorUp', - title: 'Notebook Cursor Move Up', + id: NOTEBOOK_CURSOR_UP, + title: localize('cursorMoveUp', 'Cursor Move Up'), + category: NOTEBOOK_ACTIONS_CATEGORY, keybinding: { when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.has(InputFocusedContextKey), EditorContextKeys.editorTextFocus, NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('bottom'), NOTEBOOK_EDITOR_CURSOR_BOUNDARY.notEqualsTo('none')), primary: KeyCode.UpArrow, @@ -1035,8 +1092,9 @@ registerAction2(class extends Action2 { registerAction2(class extends Action2 { constructor() { super({ - id: 'workbench.action.notebook.undo', - title: 'Notebook Undo', + id: NOTEBOOK_UNDO, + title: localize('undo', 'Undo'), + category: NOTEBOOK_ACTIONS_CATEGORY, keybinding: { when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), primary: KeyMod.CtrlCmd | KeyCode.KEY_Z, @@ -1066,8 +1124,9 @@ registerAction2(class extends Action2 { registerAction2(class extends Action2 { constructor() { super({ - id: 'workbench.action.notebook.redo', - title: 'Notebook Redo', + id: NOTEBOOK_REDO, + title: localize('redo', 'Redo'), + category: NOTEBOOK_ACTIONS_CATEGORY, keybinding: { when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z, @@ -1097,8 +1156,9 @@ registerAction2(class extends Action2 { registerAction2(class extends Action2 { constructor() { super({ - id: 'workbench.action.notebook.focusTop', - title: 'Notebook Focus First Cell', + id: NOTEBOOK_FOCUS_TOP, + title: localize('focusFirstCell', 'Focus First Cell'), + category: NOTEBOOK_ACTIONS_CATEGORY, keybinding: { when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), primary: KeyMod.CtrlCmd | KeyCode.Home, @@ -1130,8 +1190,9 @@ registerAction2(class extends Action2 { registerAction2(class extends Action2 { constructor() { super({ - id: 'workbench.action.notebook.focusBottom', - title: 'Notebook Focus Last Cell', + id: NOTEBOOK_FOCUS_BOTTOM, + title: localize('focusLastCell', 'Focus Last Cell'), + category: NOTEBOOK_ACTIONS_CATEGORY, keybinding: { when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), primary: KeyMod.CtrlCmd | KeyCode.End, @@ -1159,3 +1220,179 @@ registerAction2(class extends Action2 { editor.focusNotebookCell(firstCell, false); } }); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: CLEAR_CELL_OUTPUTS_COMMAND_ID, + title: localize('clearActiveCellOutputs', 'Clear Active Cell Outputs'), + category: NOTEBOOK_ACTIONS_CATEGORY, + menu: { + id: MenuId.NotebookCellTitle, + when: ContextKeyExpr.and(NOTEBOOK_CELL_TYPE.isEqualTo('code'), NOTEBOOK_EDITOR_RUNNABLE), + order: CellToolbarOrder.ClearCellOutput + }, + icon: { id: 'codicon/clear-all' }, + f1: true + }); + } + + async run(accessor: ServicesAccessor, context?: INotebookCellActionContext): Promise { + if (!isCellActionContext(context)) { + context = getActiveCellContext(accessor); + if (!context) { + return; + } + } + + const editor = context.notebookEditor; + if (!editor.viewModel || !editor.viewModel.length) { + return; + } + + editor.viewModel.notebookDocument.clearCellOutput(context.cell.handle); + } +}); + +interface ILanguagePickInput extends IQuickPickItem { + languageId: string; + description: string; +} + +export class ChangeCellLanguageAction extends Action2 { + constructor() { + super({ + id: CHANGE_CELL_LANGUAGE, + title: localize('changeLanguage', 'Change Cell Language'), + category: NOTEBOOK_ACTIONS_CATEGORY, + f1: true + }); + } + + async run(accessor: ServicesAccessor, context?: INotebookCellActionContext): Promise { + if (!isCellActionContext(context)) { + context = getActiveCellContext(accessor); + if (!context) { + return; + } + } + + this.showLanguagePicker(accessor, context); + } + + private async showLanguagePicker(accessor: ServicesAccessor, context: INotebookCellActionContext) { + const topItems: ILanguagePickInput[] = []; + const mainItems: ILanguagePickInput[] = []; + + const modeService = accessor.get(IModeService); + const modelService = accessor.get(IModelService); + const quickInputService = accessor.get(IQuickInputService); + + const providerLanguages = [...context.notebookEditor.viewModel!.notebookDocument.languages, 'markdown']; + providerLanguages.forEach(languageId => { + let description: string; + if (languageId === context.cell.language) { + description = localize('languageDescription', "({0}) - Current Language", languageId); + } else { + description = localize('languageDescriptionConfigured', "({0})", languageId); + } + + const languageName = modeService.getLanguageName(languageId); + if (!languageName) { + // Notebook has unrecognized language + return; + } + + const item = { + label: languageName, + iconClasses: getIconClasses(modelService, modeService, this.getFakeResource(languageName, modeService)), + description, + languageId + }; + + if (languageId === 'markdown' || languageId === context.cell.language) { + topItems.push(item); + } else { + mainItems.push(item); + } + }); + + mainItems.sort((a, b) => { + return a.description.localeCompare(b.description); + }); + + const picks: QuickPickInput[] = [ + ...topItems, + { type: 'separator' }, + ...mainItems + ]; + + const selection = await quickInputService.pick(picks, { placeHolder: localize('pickLanguageToConfigure', "Select Language Mode") }) as ILanguagePickInput | undefined; + if (selection && selection.languageId) { + if (selection.languageId === 'markdown' && context.cell?.language !== 'markdown') { + const newCell = await changeCellToKind(CellKind.Markdown, { cell: context.cell, notebookEditor: context.notebookEditor }); + if (newCell) { + context.notebookEditor.focusNotebookCell(newCell, true); + } + } else if (selection.languageId !== 'markdown' && context.cell?.language === 'markdown') { + await changeCellToKind(CellKind.Code, { cell: context.cell, notebookEditor: context.notebookEditor }, selection.languageId); + } else { + context.notebookEditor.viewModel!.notebookDocument.changeCellLanguage(context.cell.handle, selection.languageId); + } + } + } + + /** + * Copied from editorStatus.ts + */ + private getFakeResource(lang: string, modeService: IModeService): URI | undefined { + let fakeResource: URI | undefined; + + const extensions = modeService.getExtensions(lang); + if (extensions?.length) { + fakeResource = URI.file(extensions[0]); + } else { + const filenames = modeService.getFilenames(lang); + if (filenames?.length) { + fakeResource = URI.file(filenames[0]); + } + } + + return fakeResource; + } +} +registerAction2(ChangeCellLanguageAction); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: CLEAR_ALL_CELLS_OUTPUTS_COMMAND_ID, + title: localize('clearAllCellsOutputs', 'Clear All Cells Outputs'), + category: NOTEBOOK_ACTIONS_CATEGORY, + menu: { + id: MenuId.EditorTitle, + when: NOTEBOOK_EDITOR_FOCUSED, + group: 'navigation', + order: 0 + }, + icon: { id: 'codicon/clear-all' }, + f1: true + }); + } + + async run(accessor: ServicesAccessor, context?: INotebookCellActionContext): Promise { + if (!isCellActionContext(context)) { + context = getActiveCellContext(accessor); + if (!context) { + return; + } + } + + const editor = context.notebookEditor; + if (!editor.viewModel || !editor.viewModel.length) { + return; + } + + editor.viewModel.notebookDocument.clearAllCellOutputs(); + } +}); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts index e79a090776..9e57315644 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts @@ -254,7 +254,7 @@ registerNotebookContribution(NotebookFindWidget.id, NotebookFindWidget); registerAction2(class extends Action2 { constructor() { super({ - id: 'workbench.action.notebook.hideFind', + id: 'notebook.hideFind', title: localize('notebookActions.hideFind', "Hide Find in Notebook"), keybinding: { when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED), @@ -281,7 +281,7 @@ registerAction2(class extends Action2 { registerAction2(class extends Action2 { constructor() { super({ - id: 'workbench.action.notebook.find', + id: 'notebook.find', title: localize('notebookActions.findInNotebook', "Find in Notebook"), keybinding: { when: NOTEBOOK_EDITOR_FOCUSED, diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/fold/folding.ts b/src/vs/workbench/contrib/notebook/browser/contrib/fold/folding.ts index fdab87c8a1..75c64346a1 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/fold/folding.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/fold/folding.ts @@ -16,7 +16,8 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { getActiveNotebookEditor } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; +import { getActiveNotebookEditor, NOTEBOOK_ACTIONS_CATEGORY } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; +import { localize } from 'vs/nls'; export class FoldingController extends Disposable implements INotebookEditorContribution { static id: string = 'workbench.notebook.findController'; @@ -132,8 +133,9 @@ registerNotebookContribution(FoldingController.id, FoldingController); registerAction2(class extends Action2 { constructor() { super({ - id: 'workbench.action.notebook.fold', - title: 'Notebook Fold Cell', + id: 'notebook.fold', + title: localize('fold.cell', 'Fold Cell'), + category: NOTEBOOK_ACTIONS_CATEGORY, keybinding: { when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), primary: KeyCode.LeftArrow, @@ -169,8 +171,9 @@ registerAction2(class extends Action2 { registerAction2(class extends Action2 { constructor() { super({ - id: 'workbench.action.notebook.unfold', - title: 'Notebook Unfold Cell', + id: 'notebook.unfold', + title: localize('unfold.cell', 'Unfold Cell'), + category: NOTEBOOK_ACTIONS_CATEGORY, keybinding: { when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), primary: KeyCode.RightArrow, diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel.ts b/src/vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel.ts index aada510002..3abadd895c 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel.ts @@ -44,6 +44,32 @@ export class FoldingModel extends Disposable { this.recompute(); })); + this._viewModelStore.add(this._viewModel.onDidChangeSelection(() => { + const selectionHandles = this._viewModel!.selectionHandles; + const indexes = selectionHandles.map(handle => + this._viewModel!.getCellIndex(this._viewModel!.getCellByHandle(handle)!) + ); + + let changed = false; + + indexes.forEach(index => { + let regionIndex = this.regions.findRange(index + 1); + + while (regionIndex !== -1) { + if (this._regions.isCollapsed(regionIndex) && index > this._regions.getStartLineNumber(regionIndex) - 1) { + this._regions.setCollapsed(regionIndex, false); + changed = true; + } + regionIndex = this._regions.getParentIndex(regionIndex); + } + }); + + if (changed) { + this._onDidFoldingRegionChanges.fire(); + } + + })); + this.recompute(); } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/fold/test/notebookFolding.test.ts b/src/vs/workbench/contrib/notebook/browser/contrib/fold/test/notebookFolding.test.ts index 6cf10d1f76..1545f1d7ad 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/fold/test/notebookFolding.test.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/fold/test/notebookFolding.test.ts @@ -320,4 +320,92 @@ suite('Notebook Folding', () => { } ); }); + + test('View Index', function () { + withTestNotebook( + instantiationService, + blukEditService, + undoRedoService, + [ + [['# header 1'], 'markdown', CellKind.Markdown, [], {}], + [['body'], 'markdown', CellKind.Markdown, [], {}], + [['# header 2.1\n'], 'markdown', CellKind.Markdown, [], {}], + [['body 2'], 'markdown', CellKind.Markdown, [], {}], + [['body 3'], 'markdown', CellKind.Markdown, [], {}], + [['## header 2.2'], 'markdown', CellKind.Markdown, [], {}], + [['var e = 7;'], 'markdown', CellKind.Markdown, [], {}], + [['# header 2.1\n'], 'markdown', CellKind.Markdown, [], {}], + [['body 2'], 'markdown', CellKind.Markdown, [], {}], + [['body 3'], 'markdown', CellKind.Markdown, [], {}], + [['## header 2.2'], 'markdown', CellKind.Markdown, [], {}], + [['var e = 7;'], 'markdown', CellKind.Markdown, [], {}], + ], + (editor, viewModel) => { + const foldingModel = new FoldingModel(); + foldingModel.attachViewModel(viewModel); + foldingModel.applyMemento([{ start: 2, end: 6 }]); + viewModel.updateFoldingRanges(foldingModel.regions); + + // Note that hidden ranges !== folding ranges + assert.deepEqual(viewModel.getHiddenRanges(), [ + { start: 3, end: 6 } + ]); + + assert.equal(viewModel.getNextVisibleCellIndex(1), 2); + assert.equal(viewModel.getNextVisibleCellIndex(2), 7); + assert.equal(viewModel.getNextVisibleCellIndex(3), 7); + assert.equal(viewModel.getNextVisibleCellIndex(4), 7); + assert.equal(viewModel.getNextVisibleCellIndex(5), 7); + assert.equal(viewModel.getNextVisibleCellIndex(6), 7); + assert.equal(viewModel.getNextVisibleCellIndex(7), 8); + } + ); + + withTestNotebook( + instantiationService, + blukEditService, + undoRedoService, + [ + [['# header 1'], 'markdown', CellKind.Markdown, [], {}], + [['body'], 'markdown', CellKind.Markdown, [], {}], + [['# header 2.1\n'], 'markdown', CellKind.Markdown, [], {}], + [['body 2'], 'markdown', CellKind.Markdown, [], {}], + [['body 3'], 'markdown', CellKind.Markdown, [], {}], + [['## header 2.2'], 'markdown', CellKind.Markdown, [], {}], + [['var e = 7;'], 'markdown', CellKind.Markdown, [], {}], + [['# header 2.1\n'], 'markdown', CellKind.Markdown, [], {}], + [['body 2'], 'markdown', CellKind.Markdown, [], {}], + [['body 3'], 'markdown', CellKind.Markdown, [], {}], + [['## header 2.2'], 'markdown', CellKind.Markdown, [], {}], + [['var e = 7;'], 'markdown', CellKind.Markdown, [], {}], + ], + (editor, viewModel) => { + const foldingModel = new FoldingModel(); + foldingModel.attachViewModel(viewModel); + foldingModel.applyMemento([ + { start: 5, end: 6 }, + { start: 10, end: 11 }, + ]); + + viewModel.updateFoldingRanges(foldingModel.regions); + + // Note that hidden ranges !== folding ranges + assert.deepEqual(viewModel.getHiddenRanges(), [ + { start: 6, end: 6 }, + { start: 11, end: 11 } + ]); + + // folding ranges + // [5, 6] + // [10, 11] + assert.equal(viewModel.getNextVisibleCellIndex(4), 5); + assert.equal(viewModel.getNextVisibleCellIndex(5), 7); + assert.equal(viewModel.getNextVisibleCellIndex(6), 7); + + assert.equal(viewModel.getNextVisibleCellIndex(9), 10); + assert.equal(viewModel.getNextVisibleCellIndex(10), 12); + assert.equal(viewModel.getNextVisibleCellIndex(11), 12); + } + ); + }); }); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/format/formatting.ts b/src/vs/workbench/contrib/notebook/browser/contrib/format/formatting.ts new file mode 100644 index 0000000000..e294650322 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/contrib/format/formatting.ts @@ -0,0 +1,89 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * 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 { 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 { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { ServicesAccessor } 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 { 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'; + +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), + 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 + }); + } + + async run(accessor: ServicesAccessor): Promise { + const editorService = accessor.get(IEditorService); + const textModelService = accessor.get(ITextModelService); + const editorWorkerService = accessor.get(IEditorWorkerService); + const bulkEditService = accessor.get(IBulkEditService); + + const editor = getActiveNotebookEditor(editorService); + if (!editor || !editor.viewModel) { + return; + } + + const notebook = editor.viewModel.notebookDocument; + const dispoables = new DisposableStore(); + try { + + const edits: WorkspaceTextEdit[] = []; + + for (let cell of notebook.cells) { + + const ref = await textModelService.createModelReference(cell.uri); + dispoables.add(ref); + + const model = ref.object.textEditorModel; + + const formatEdits = await getDocumentFormattingEditsUntilResult( + editorWorkerService, model, + model.getOptions(), CancellationToken.None + ); + + if (formatEdits) { + formatEdits.forEach(edit => edits.push({ + edit, + resource: model.uri, + modelVersionId: model.getVersionId() + })); + } + } + + await bulkEditService.apply( + { edits }, + { label: localize('label', "Format Notebook") } + ); + + } finally { + dispoables.dispose(); + } + + } +}); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/toc/tocProvider.ts b/src/vs/workbench/contrib/notebook/browser/contrib/toc/tocProvider.ts new file mode 100644 index 0000000000..82f1ee3689 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/contrib/toc/tocProvider.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 { TableOfContentsProviderRegistry, ITableOfContentsProvider, ITableOfContentsEntry } from 'vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess'; +import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEditor'; +import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; + + +TableOfContentsProviderRegistry.register(NotebookEditor.ID, new class implements ITableOfContentsProvider { + async provideTableOfContents(editor: NotebookEditor) { + if (!editor.viewModel) { + return undefined; + } + // return an entry per markdown header + const result: ITableOfContentsEntry[] = []; + for (let cell of editor.viewModel.viewCells) { + if (cell.cellKind === CellKind.Code) { + continue; + } + const content = cell.getText(); + const matches = content.match(/^[ \t]*(\#+)(.+)$/gm); + if (matches && matches.length) { + for (let j = 0; j < matches.length; j++) { + result.push({ + label: matches[j].replace(/^[ \t]*(\#+)/, ''), + reveal: () => editor.revealInCenterIfOutsideViewport(cell) + }); + } + } + } + return result; + } +}); diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebook.css b/src/vs/workbench/contrib/notebook/browser/media/notebook.css index cd9e92a6af..78528318e9 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebook.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebook.css @@ -17,10 +17,14 @@ white-space: initial; } -/* .monaco-workbench .part.editor > .content .notebook-editor .cell-list-container .monaco-scrollable-element { +/* .monaco-workbench .part.editor > .content .notebook-editor .cell-list-container > .monaco-list > .monaco-scrollable-element { overflow: visible !important; } */ +.monaco-workbench .part.editor > .content .notebook-editor .cell-list-container .monaco-editor-hover { + z-index: 600 !important; /* @rebornix: larger than the editor title bar */ +} + .monaco-workbench .part.editor > .content .notebook-editor .cell-list-container .monaco-list-rows { min-height: 100%; overflow: visible !important; @@ -41,31 +45,24 @@ width: 100%; } -.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-drag-handle { - position: absolute; - visibility: hidden; - left: 1px; -} - -.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-drag-handle { - top: 20px; -} - -.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.code-cell-row .cell-drag-handle { - top: 21px; -} - -.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused .cell-drag-handle, -.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover .cell-drag-handle, -.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.cell-output-hover .cell-drag-handle { - visibility: visible; -} - .monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.cell-drag-image { position: absolute; top: -500px; z-index: 1000; - padding-bottom: 16px; + padding-bottom: 8px; + padding-top: 8px; +} + +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.cell-drag-image .notebook-cell-focus-indicator { + top: 8px !important; +} + +.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 .notebook-cell-focus-indicator { + bottom: 8px; +} + +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.cell-drag-image .output { + display: none !important; } .monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.cell-drag-image > .monaco-toolbar { @@ -74,7 +71,6 @@ .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-statusbar-container { display: none; - box-sizing: border-box; } .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 { @@ -112,6 +108,10 @@ box-sizing: border-box; } +.monaco-workbench .part.editor > .content .notebook-editor .cell-drag-image .output .multi-mimetype-output { + display: none; +} + .monaco-workbench .part.editor > .content .notebook-editor .output .multi-mimetype-output { position: absolute; top: 4px; @@ -125,11 +125,15 @@ color: red; } -.monaco-workbench .part.editor > .content .notebook-editor .output pre.traceback { +.monaco-workbench .part.editor > .content .notebook-editor .output .error > div { + white-space: normal; +} + +.monaco-workbench .part.editor > .content .notebook-editor .output .error pre.traceback { margin: 8px 0; } -.monaco-workbench .part.editor > .content .notebook-editor .output .traceback > span { +.monaco-workbench .part.editor > .content .notebook-editor .output .error .traceback > span { display: block; } @@ -137,11 +141,6 @@ max-width: 100%; } - -.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:focus-within { - z-index: 10; -} - .monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .menu { position: absolute; left: 0; @@ -159,6 +158,7 @@ visibility: visible; } +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row, .monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover, .monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.cell-output-hover { outline: none !important; @@ -174,20 +174,18 @@ cursor: pointer; } +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element { + padding-top: 16px; +} + .monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row > .monaco-toolbar { visibility: hidden; - display: inline-block; position: absolute; height: 26px; - right: 32px; - top: 0px; - z-index: 10; - box-shadow: 0 0 8px 4px rgba(0, 0, 0, 0.2); -} - -.vs .monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row > .monaco-toolbar { - box-shadow: 0 0 8px 4px rgba(168, 168, 168, 0.1); + right: 36px; + top: -14px; /* this lines up the bottom toolbar border with the current line when on line 01 */ + z-index: 30; } .monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row > .monaco-toolbar .action-item { @@ -205,12 +203,42 @@ } .monaco-workbench .part.editor > .content .notebook-editor .cell-statusbar-container { - height: 22px; + height: 21px; font-size: 12px; display: flex; position: relative; } +.monaco-workbench .part.editor > .content .notebook-editor .cell-statusbar-container .cell-status-left { + display: flex; + flex-grow: 1; +} + +.monaco-workbench .part.editor > .content .notebook-editor .cell-statusbar-container .cell-status-right { + padding-right: 12px; +} + +.monaco-workbench .part.editor > .content .notebook-editor .cell-statusbar-container .cell-language-picker { + padding: 0px 6px; + cursor: pointer; +} + +.monaco-workbench .part.editor > .content .notebook-editor .cell-statusbar-container .cell-language-picker:hover { + background-color: rgba(255, 255, 255, 0.6); +} + +.monaco-workbench .part.editor > .content .notebook-editor .cell-statusbar-container .cell-language-picker:hover { + background-color: rgba(255, 255, 255, 0.9); +} + +.vs-dark .monaco-workbench .part.editor > .content .notebook-editor .cell-statusbar-container .cell-language-picker:hover { + background-color: rgba(255, 255, 255, 0.15); +} + +.vs-dark .monaco-workbench .part.editor > .content .notebook-editor .cell-statusbar-container .cell-language-picker:active { + background-color: rgba(255, 255, 255, 0.2); +} + .monaco-workbench .part.editor > .content .notebook-editor .cell-statusbar-container .cell-status-message { display: flex; align-items: center; @@ -253,10 +281,12 @@ .monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell .run-button-container { position: relative; + height: 22px; + flex-shrink: 0; } .monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell .run-button-container .monaco-toolbar { - margin-top: 5px; + margin-top: 8px; visibility: hidden; } @@ -274,12 +304,14 @@ position: absolute; top: 2px; font-size: 10px; + font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback"; visibility: visible; white-space: pre; width: 100%; text-align: center; padding-right: 2px; box-sizing: border-box; + opacity: .6; } .monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover .cell .run-button-container .execution-count-label, @@ -296,9 +328,9 @@ top: -5px; } -.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused .monaco-toolbar, -.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.cell-output-hover .monaco-toolbar, -.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover .monaco-toolbar { +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.cell-has-toolbar-actions.focused > .monaco-toolbar, +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.cell-has-toolbar-actions.cell-output-hover > .monaco-toolbar, +.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.cell-has-toolbar-actions:hover > .monaco-toolbar { visibility: visible; } @@ -312,13 +344,14 @@ display: block; content: ' '; position: absolute; - width: 6px; + width: 32px; + box-sizing: border-box; border-left-width: 2px; border-left-style: solid; - left: 20px; top: 22px; - bottom: 22px; + bottom: 36px; visibility: hidden; + cursor: pointer; } .monaco-workbench .part.editor > .content .notebook-editor .monaco-list .monaco-list-row:hover .notebook-cell-focus-indicator, @@ -328,7 +361,7 @@ } .monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row.cell-editor-focus .cell-editor-part:before { - z-index: 5; + z-index: 20; content: ""; right: 0px; left: 0px; @@ -353,7 +386,7 @@ transition: opacity 0.2s ease-in-out; position: absolute; height: 2px; - top: 0px; + top: -15px; } .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 { @@ -362,15 +395,15 @@ opacity: 0; transition: opacity 0.2s ease-in-out; cursor: auto; - padding: 2px 0; + padding: 0; } .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-bottom-toolbar-container { display: none; } -.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:focus-within, -.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:hover { +.monaco-workbench .part.editor > .content .notebook-editor.notebook-editor-editable > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container:focus-within, +.monaco-workbench .part.editor > .content .notebook-editor.notebook-editor-editable > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container:hover { opacity: 1; } @@ -389,10 +422,12 @@ .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 .button { display: flex; margin: 0 8px; + padding: 0 8px; align-self: center; align-items: center; white-space: pre; cursor: pointer; + font-size: 12px; } .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 span.codicon { @@ -460,17 +495,27 @@ width: 100%; } -/* Removes margin of first item in markdown cell */ +/* Adjust margin of first item in markdown cell */ .monaco-workbench .part.editor > .content .notebook-editor .cell.markdown div *:first-child { + margin-top: 4px; +} + +/* h1 tags don't need top margin */ +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown div h1:first-child { margin-top: 0; } -/* Removes margin when only one item exists in markdown cell */ +/* Removes bottom margin when only one item exists in markdown cell */ .monaco-workbench .part.editor > .content .notebook-editor .cell.markdown div *:only-child, .monaco-workbench .part.editor > .content .notebook-editor .cell.markdown div *:last-child { margin-bottom: 0; } +/* makes all markdown cells consistent */ +.monaco-workbench .part.editor > .content .notebook-editor .cell.markdown div { + min-height: 32px; +} + .monaco-workbench .part.editor > .content .notebook-editor .cell.markdown table { border-collapse: collapse; border-spacing: 0; @@ -547,11 +592,15 @@ .monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container .notebook-folding-indicator { position: absolute; - top: 20px; + top: 8px; left: 26px; 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 45546c08bd..efcdddc6b4 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -6,7 +6,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { ResourceMap } from 'vs/base/common/map'; import { parse } from 'vs/base/common/marshalling'; -import { basename } from 'vs/base/common/resources'; +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'; @@ -31,12 +31,18 @@ import { CellKind, CellUri } from 'vs/workbench/contrib/notebook/common/notebook import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { CustomEditorsAssociations, customEditorsAssociationsSettingId } from 'vs/workbench/services/editor/common/editorAssociationsSetting'; +import { coalesce, distinct } from 'vs/base/common/arrays'; +import { CustomEditorInfo } from 'vs/workbench/contrib/customEditor/common/customEditor'; // Editor Contribution import 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; -import 'vs/workbench/contrib/notebook/browser/contrib/fold/folding'; import 'vs/workbench/contrib/notebook/browser/contrib/find/findController'; +import 'vs/workbench/contrib/notebook/browser/contrib/fold/folding'; +import 'vs/workbench/contrib/notebook/browser/contrib/format/formatting'; +import 'vs/workbench/contrib/notebook/browser/contrib/toc/tocProvider'; // Output renderers registration @@ -81,7 +87,7 @@ Registry.as(EditorInputExtensions.EditorInputFactor if (!data || !URI.isUri(resource) || typeof name !== 'string' || typeof viewType !== 'string') { return undefined; } - return instantiationService.createInstance(NotebookEditorInput, resource, name, viewType); + return NotebookEditorInput.getOrCreate(instantiationService, resource, name, viewType); } } ); @@ -96,23 +102,24 @@ export class NotebookContribution implements IWorkbenchContribution { constructor( @IEditorService private readonly editorService: IEditorService, @INotebookService private readonly notebookService: INotebookService, - @IInstantiationService private readonly instantiationService: IInstantiationService + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IConfigurationService private readonly configurationService: IConfigurationService ) { this.editorService.overrideOpenEditor({ - getEditorOverrides: (editor: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup | undefined) => { - let resource = editor.resource; - if (!resource) { - return []; - } + getEditorOverrides: (resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined) => { + const currentEditorForResource = group?.editors.find(editor => isEqual(editor.resource, resource)); - const infos = notebookService.getContributedNotebookProviders(resource); + const associatedEditors = distinct([ + ...this.getUserAssociatedNotebookEditors(resource), + ...this.getContributedEditors(resource) + ], editor => editor.id); - return infos.map(info => { + return associatedEditors.map(info => { return { label: info.displayName, id: info.id, - active: editor instanceof NotebookEditorInput && editor.viewType === info.id, + active: currentEditorForResource instanceof NotebookEditorInput && currentEditorForResource.viewType === info.id, detail: info.providerDisplayName }; }); @@ -128,23 +135,69 @@ export class NotebookContribution implements IWorkbenchContribution { }); } + getUserAssociatedEditors(resource: URI) { + const rawAssociations = this.configurationService.getValue(customEditorsAssociationsSettingId) || []; + + return coalesce(rawAssociations + .filter(association => CustomEditorInfo.selectorMatches(association, resource))); + } + + getUserAssociatedNotebookEditors(resource: URI) { + const rawAssociations = this.configurationService.getValue(customEditorsAssociationsSettingId) || []; + + return coalesce(rawAssociations + .filter(association => CustomEditorInfo.selectorMatches(association, resource)) + .map(association => this.notebookService.getContributedNotebookProvider(association.viewType))); + } + + getContributedEditors(resource: URI) { + return this.notebookService.getContributedNotebookProviders(resource); + } + private onEditorOpening(originalInput: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup, id: string | undefined): IOpenEditorOverride | undefined { let resource = originalInput.resource; if (!resource) { return undefined; } + if (id === undefined) { + const existingEditors = group.editors.filter(editor => editor.resource && isEqual(editor.resource, resource) && !(editor instanceof NotebookEditorInput)); + + if (existingEditors.length) { + return undefined; + } + + const userAssociatedEditors = this.getUserAssociatedEditors(resource); + const notebookEditor = userAssociatedEditors.filter(association => this.notebookService.getContributedNotebookProvider(association.viewType)); + + if (userAssociatedEditors.length && !notebookEditor.length) { + // user pick a non-notebook editor for this resource + return undefined; + } + } + + if (this._resourceMapping.has(resource)) { + const input = this._resourceMapping.get(resource); + + if (!input!.isDisposed()) { + return { override: this.editorService.openEditor(input!, new NotebookEditorOptions(options || {}).with({ ignoreOverrides: true }), group) }; + } + } + let info: NotebookProviderInfo | undefined; const data = CellUri.parse(resource); if (data) { - const infos = this.notebookService.getContributedNotebookProviders(data.notebook); + const infos = this.getContributedEditors(data.notebook); if (infos.length) { const info = id === undefined ? infos[0] : (infos.find(info => info.id === id) || infos[0]); // cell-uri -> open (container) notebook const name = basename(data.notebook); - const input = this.instantiationService.createInstance(NotebookEditorInput, data.notebook, name, info.id); - this._resourceMapping.set(resource, input); + let input = this._resourceMapping.get(data.notebook); + if (!input || input.isDisposed()) { + input = NotebookEditorInput.getOrCreate(this.instantiationService, data.notebook, name, info.id); + this._resourceMapping.set(data.notebook, input); + } return { override: this.editorService.openEditor(input, new NotebookEditorOptions({ ...options, forceReload: true, cellOptions: { resource, options } }), group) }; } } @@ -156,15 +209,7 @@ export class NotebookContribution implements IWorkbenchContribution { return undefined; } - if (this._resourceMapping.has(resource)) { - const input = this._resourceMapping.get(resource); - - if (!input!.isDisposed()) { - return { override: this.editorService.openEditor(input!, new NotebookEditorOptions(options || {}).with({ ignoreOverrides: true }), group) }; - } - } - - const input = this.instantiationService.createInstance(NotebookEditorInput, resource, originalInput.getName(), info.id); + const input = NotebookEditorInput.getOrCreate(this.instantiationService, resource, originalInput.getName(), info.id); this._resourceMapping.set(resource, input); return { override: this.editorService.openEditor(input, options, group) }; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index f3ab1f57b2..7f7f661b23 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -17,19 +17,33 @@ import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; 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 } from 'vs/platform/contextkey/common/contextkey'; -import { NOTEBOOK_EDITABLE_CONTEXT_KEY, NOTEBOOK_EXECUTING_KEY } from 'vs/workbench/contrib/notebook/browser/constants'; +import { RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; 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); +// Is Notebook +export const NOTEBOOK_IS_ACTIVE_EDITOR = ContextKeyExpr.equals('activeEditor', 'workbench.editor.notebook'); + +// Editor keys export const NOTEBOOK_EDITOR_FOCUSED = new RawContextKey('notebookEditorFocused', false); -export const NOTEBOOK_EDITOR_EDITABLE = new RawContextKey(NOTEBOOK_EDITABLE_CONTEXT_KEY, true); -export const NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK = new RawContextKey(NOTEBOOK_EXECUTING_KEY, false); +export const NOTEBOOK_EDITOR_EDITABLE = new RawContextKey('notebookEditable', true); +export const NOTEBOOK_EDITOR_RUNNABLE = new RawContextKey('notebookRunnable', true); +export const NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK = new RawContextKey('notebookExecuting', false); + +// Cell keys +export const NOTEBOOK_VIEW_TYPE = new RawContextKey('notebookViewType', undefined); +export const NOTEBOOK_CELL_TYPE = new RawContextKey('notebookCellType', undefined); // code, markdown +export const NOTEBOOK_CELL_EDITABLE = new RawContextKey('notebookCellEditable', false); // bool +export const NOTEBOOK_CELL_RUNNABLE = new RawContextKey('notebookCellRunnable', false); // bool +export const NOTEBOOK_CELL_MARKDOWN_EDIT_MODE = new RawContextKey('notebookCellMarkdownEditMode', false); // bool +export const NOTEBOOK_CELL_RUN_STATE = new RawContextKey('notebookCellRunState', undefined); // idle, running +export const NOTEBOOK_CELL_HAS_OUTPUTS = new RawContextKey('notebookCellHasOutputs', false); // bool export interface NotebookLayoutInfo { width: number; @@ -81,12 +95,14 @@ export interface ICellViewModel { dragging: boolean; handle: number; uri: URI; + language: string; cellKind: CellKind; editState: CellEditState; readonly runState: CellRunState; currentTokenSource: CancellationTokenSource | undefined; focusMode: CellFocusMode; getText(): string; + save(): void; metadata: NotebookCellMetadata | undefined; getEvaluatedMetadata(documentMetadata: NotebookDocumentMetadata | undefined): NotebookCellMetadata; } @@ -150,7 +166,7 @@ export interface INotebookEditor { /** * Insert a new cell around `cell` */ - insertNotebookCell(cell: ICellViewModel | undefined, type: CellKind, direction?: 'above' | 'below', initialText?: string): CellViewModel; + insertNotebookCell(cell: ICellViewModel | undefined, type: CellKind, direction?: 'above' | 'below', initialText?: string, ui?: boolean): CellViewModel | null; /** * Delete a cell from the notebook @@ -369,7 +385,6 @@ export interface INotebookCellList { export interface BaseCellRenderTemplate { container: HTMLElement; cellContainer: HTMLElement; - dragHandle: HTMLElement; toolbar: ToolBar; focusIndicator: HTMLElement; insertionIndicatorTop: HTMLElement; @@ -377,19 +392,20 @@ export interface BaseCellRenderTemplate { elementDisposables: DisposableStore; bottomCellContainer: HTMLElement; currentRenderedCell?: ICellViewModel; + statusBarContainer: HTMLElement; + languageStatusBarItem: CellLanguageStatusBarItem; toJSON: () => any; } export interface MarkdownCellRenderTemplate extends BaseCellRenderTemplate { - editingContainer: HTMLElement; + editorPart: HTMLElement; + editorContainer: HTMLElement; foldingIndicator: HTMLElement; } export interface CodeCellRenderTemplate extends BaseCellRenderTemplate { - statusBarContainer: HTMLElement; cellRunStatusContainer: HTMLElement; cellStatusMessageContainer: HTMLElement; - cellStatusPlaceholderContainer: HTMLElement; runToolbar: ToolBar; runButtonContainer: HTMLElement; executionOrderLabel: HTMLElement; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index d58aac10b4..c9f418f862 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -10,7 +10,7 @@ import { IMouseWheelEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Color, RGBA } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; -import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, MutableDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; @@ -29,7 +29,7 @@ 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 } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +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 { NotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList'; @@ -92,6 +92,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { static readonly ID: string = 'workbench.editor.notebook'; private _rootElement!: HTMLElement; private body!: HTMLElement; + private titleBar: HTMLElement | null = null; private webview: BackLayerWebView | null = null; private webviewTransparentCover: HTMLElement | null = null; private list: INotebookCellList | undefined; @@ -106,6 +107,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { private dimension: DOM.Dimension | null = null; private editorFocus: IContextKey | null = null; private editorEditable: IContextKey | null = null; + private editorRunnable: IContextKey | null = null; private editorExecutingNotebook: IContextKey | null = null; private outputRenderer: OutputRenderer; protected readonly _contributions: { [key: string]: INotebookEditorContribution; }; @@ -182,6 +184,8 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { this.editorEditable = NOTEBOOK_EDITOR_EDITABLE.bindTo(this.contextKeyService); this.editorEditable.set(true); + this.editorRunnable = NOTEBOOK_EDITOR_RUNNABLE.bindTo(this.contextKeyService); + this.editorRunnable.set(true); this.editorExecutingNotebook = NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK.bindTo(this.contextKeyService); const contributions = NotebookEditorExtensionsRegistry.getEditorContributions(); @@ -196,6 +200,43 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { } } + populateEditorTitlebar() { + for (let element: HTMLElement | null = this._rootElement.parentElement; element; element = element.parentElement) { + if (DOM.hasClass(element, 'editor-group-container')) { + // elemnet is editor group container + for (let i = 0; i < element.childElementCount; i++) { + const child = element.childNodes.item(i) as HTMLElement; + + if (DOM.hasClass(child, 'title')) { + this.titleBar = child; + break; + } + } + break; + } + } + } + + clearEditorTitlebarZindex() { + if (this.titleBar === null) { + this.populateEditorTitlebar(); + } + + if (this.titleBar) { + this.titleBar.style.zIndex = 'auto'; + } + } + + increaseEditorTitlebarZindex() { + if (this.titleBar === null) { + this.populateEditorTitlebar(); + } + + if (this.titleBar) { + this.titleBar.style.zIndex = '500'; + } + } + private generateFontInfo(): void { const editorOptions = this.configurationService.getValue('editor'); this.fontInfo = BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel()); @@ -214,7 +255,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { const dndController = new CellDragAndDropController(this); const renders = [ this.instantiationService.createInstance(CodeCellRenderer, this, this.contextKeyService, this.renderedEditors, dndController), - this.instantiationService.createInstance(MarkdownCellRenderer, this.contextKeyService, this, dndController), + this.instantiationService.createInstance(MarkdownCellRenderer, this.contextKeyService, this, dndController, this.renderedEditors), ]; this.list = this.instantiationService.createInstance( @@ -234,6 +275,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { multipleSelectionSupport: false, enableKeyboardNavigation: true, additionalScrollHeight: 0, + transformOptimization: false, styleController: (_suffix: string) => { return this.list!; }, overrideStyles: { listBackground: editorBackground, @@ -271,6 +313,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { this.list.rowsContainer.appendChild(this.webview.element); this._register(this.list); + this._register(combinedDisposable(...renders)); // transparent cover this.webviewTransparentCover = DOM.append(this.list.rowsContainer, $('.webview-cover')); @@ -315,6 +358,16 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { return this.webview?.webview; } + setVisible(visible: boolean, group?: IEditorGroup): void { + if (visible) { + this.increaseEditorTitlebarZindex(); + } else { + this.clearEditorTitlebarZindex(); + } + + super.setVisible(visible, group); + } + onWillHide() { if (this.input && this.input instanceof NotebookEditorInput && !this.input.isDisposed()) { this.saveEditorViewState(this.input); @@ -344,6 +397,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { } if (editor === this.input) { + this.clearEditorTitlebarZindex(); this.saveEditorViewState(editor); } } @@ -407,6 +461,12 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { this.list?.clear(); } + private updateForMetadata(): void { + this.editorEditable?.set(!!this.viewModel!.metadata?.editable); + this.editorRunnable?.set(!!this.viewModel!.metadata?.runnable); + DOM.toggleClass(this.getDomNode(), 'notebook-editor-editable', !!this.viewModel!.metadata?.editable); + } + private async attachModel(input: NotebookEditorInput, model: NotebookEditorModel) { if (!this.webview) { this.webview = this.instantiationService.createInstance(BackLayerWebView, this); @@ -417,11 +477,11 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { this.eventDispatcher = new NotebookEventDispatcher(); this.viewModel = this.instantiationService.createInstance(NotebookViewModel, input.viewType!, model, this.eventDispatcher, this.getLayoutInfo()); - this.editorEditable?.set(!!this.viewModel.metadata?.editable); this.eventDispatcher.emit([new NotebookLayoutChangedEvent({ width: true, fontInfo: true }, this.getLayoutInfo())]); + this.updateForMetadata(); this.localStore.add(this.eventDispatcher.onDidChangeMetadata((e) => { - this.editorEditable?.set(e.source.editable); + this.updateForMetadata(); })); // restore view states, including contributions @@ -456,9 +516,10 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { const scrollTop = this.list?.scrollTop || 0; const scrollHeight = this.list?.scrollHeight || 0; this.webview!.element.style.height = `${scrollHeight}px`; - let updateItems: { cell: CodeCellViewModel, output: IOutput, cellTop: number }[] = []; if (this.webview?.insetMapping) { + let updateItems: { cell: CodeCellViewModel, output: IOutput, cellTop: number }[] = []; + let removedItems: IOutput[] = []; this.webview?.insetMapping.forEach((value, key) => { const cell = value.cell; const viewIndex = this.list?.getViewIndex(cell); @@ -467,6 +528,11 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { return; } + if (cell.outputs.indexOf(key) < 0) { + // output is already gone + removedItems.push(key); + } + const cellTop = this.list?.getAbsoluteTopOfElement(cell) || 0; if (this.webview!.shouldUpdateInset(cell, key, cellTop)) { updateItems.push({ @@ -477,6 +543,8 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { } }); + removedItems.forEach(output => this.webview?.removeInset(output)); + if (updateItems.length) { this.webview?.updateViewScrollTop(-scrollTop, updateItems); } @@ -495,10 +563,10 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { this.list!.layout(); // restore list state at last, it must be after list layout - this.restoreTextEditorViewState(viewState); + this.restoreListViewState(viewState); } - private restoreTextEditorViewState(viewState: INotebookEditorViewState | undefined): void { + private restoreListViewState(viewState: INotebookEditorViewState | undefined): void { if (viewState?.scrollPosition !== undefined) { this.list!.scrollTop = viewState!.scrollPosition.top; this.list!.scrollLeft = viewState!.scrollPosition.left; @@ -508,8 +576,12 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { } const focusIdx = typeof viewState?.focus === 'number' ? viewState.focus : 0; - this.list!.setFocus([focusIdx]); - this.list!.setSelection([focusIdx]); + if (focusIdx < this.list!.length) { + this.list!.setFocus([focusIdx]); + this.list!.setSelection([focusIdx]); + } else if (this.list!.length > 0) { + this.list!.setFocus([0]); + } if (viewState?.editorFocused) { this.list?.focusView(); @@ -564,6 +636,8 @@ 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()); } } @@ -605,6 +679,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { selectElement(cell: ICellViewModel) { this.list?.selectElement(cell); + // this.viewModel!.selectionHandles = [cell.handle]; } revealInView(cell: ICellViewModel) { @@ -687,12 +762,17 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { return new Promise(resolve => { r = resolve; }); } - insertNotebookCell(cell: ICellViewModel | undefined, type: CellKind, direction: 'above' | 'below' = 'above', initialText: string = ''): CellViewModel { + insertNotebookCell(cell: ICellViewModel | undefined, type: CellKind, direction: 'above' | 'below' = 'above', initialText: string = '', ui: boolean = false): CellViewModel | null { + if (!this.notebookViewModel!.metadata.editable) { + return null; + } + const newLanguages = this.notebookViewModel!.languages; - const language = newLanguages && newLanguages.length ? newLanguages[0] : 'markdown'; + const language = (type === CellKind.Code && newLanguages && newLanguages.length) ? newLanguages[0] : 'markdown'; const index = cell ? this.notebookViewModel!.getCellIndex(cell) : 0; + const nextIndex = ui ? this.notebookViewModel!.getNextVisibleCellIndex(index) : index + 1; const insertIndex = cell ? - (direction === 'above' ? index : index + 1) : + (direction === 'above' ? index : nextIndex) : index; const newCell = this.notebookViewModel!.createCell(insertIndex, initialText.split(/\r?\n/g), language, type, true); @@ -704,6 +784,10 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { } async deleteNotebookCell(cell: ICellViewModel): Promise { + if (!this.notebookViewModel!.metadata.editable) { + return false; + } + (cell as CellViewModel).save(); const index = this.notebookViewModel!.getCellIndex(cell); this.notebookViewModel!.deleteCell(index, true); @@ -711,6 +795,10 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { } async moveCellDown(cell: ICellViewModel): Promise { + if (!this.notebookViewModel!.metadata.editable) { + return false; + } + const index = this.notebookViewModel!.getCellIndex(cell); if (index === this.notebookViewModel!.length - 1) { return false; @@ -721,6 +809,10 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { } async moveCellUp(cell: ICellViewModel): Promise { + if (!this.notebookViewModel!.metadata.editable) { + return false; + } + const index = this.notebookViewModel!.getCellIndex(cell); if (index === 0) { return false; @@ -731,6 +823,10 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { } async moveCell(cell: ICellViewModel, relativeToCell: ICellViewModel, direction: 'above' | 'below'): Promise { + if (!this.notebookViewModel!.metadata.editable) { + return false; + } + if (cell === relativeToCell) { return false; } @@ -738,11 +834,19 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { const originalIdx = this.notebookViewModel!.getCellIndex(cell); const relativeToIndex = this.notebookViewModel!.getCellIndex(relativeToCell); - const newIdx = direction === 'above' ? relativeToIndex : relativeToIndex + 1; + let newIdx = direction === 'above' ? relativeToIndex : relativeToIndex + 1; + if (originalIdx < newIdx) { + newIdx--; + } + return this.moveCellToIndex(originalIdx, newIdx); } private async moveCellToIndex(index: number, newIdx: number): Promise { + if (index === newIdx) { + return false; + } + if (!this.notebookViewModel!.moveCellToIdx(index, newIdx, true)) { throw new Error('Notebook Editor move cell, index out of range'); } @@ -757,6 +861,10 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { } editNotebookCell(cell: CellViewModel): void { + if (!cell.getEvaluatedMetadata(this.notebookViewModel!.metadata).editable) { + return; + } + cell.editState = CellEditState.Editing; this.renderedEditors.get(cell)?.focus(); @@ -787,6 +895,10 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { } async executeNotebook(): Promise { + if (!this.notebookViewModel!.metadata.runnable) { + return; + } + // return this.progressService.showWhile(this._executeNotebook()); return this._executeNotebook(); } @@ -823,6 +935,10 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { } async executeNotebookCell(cell: ICellViewModel): Promise { + if (!cell.getEvaluatedMetadata(this.notebookViewModel!.metadata).runnable) { + return; + } + const tokenSource = new CancellationTokenSource(); try { this._executeNotebookCell(cell, tokenSource); @@ -968,7 +1084,7 @@ export const focusedCellIndicator = registerColor('notebook.focusedCellIndicator export const notebookOutputContainerColor = registerColor('notebook.outputContainerBackgroundColor', { dark: new Color(new RGBA(255, 255, 255, 0.06)), - light: new Color(new RGBA(228, 230, 241)), + light: new Color(new RGBA(237, 239, 249)), hc: null } , nls.localize('notebook.outputContainerBackgroundColor', "The Color of the notebook output container background.")); @@ -991,11 +1107,12 @@ registerThemingParticipant((theme, collector) => { } const link = theme.getColor(textLinkForeground); if (link) { - collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell .output a { color: ${link}; }`); + collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .output a, + .monaco-workbench .part.editor > .content .notebook-editor .cell.markdown a { color: ${link};} `); } const activeLink = theme.getColor(textLinkActiveForeground); if (activeLink) { - collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell .output a:hover, + collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .output a:hover, .monaco-workbench .part.editor > .content .notebook-editor .cell .output a:active { color: ${activeLink}; }`); } const shortcut = theme.getColor(textPreformatForeground); @@ -1045,6 +1162,13 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row.cell-editor-focus .cell-editor-part:before { outline: solid 1px ${focusedCellIndicatorColor}; }`); } + // const widgetShadowColor = theme.getColor(widgetShadow); + // if (widgetShadowColor) { + // collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row > .monaco-toolbar { + // box-shadow: 0 0 8px 4px ${widgetShadowColor} + // }`) + // } + // Cell Margin 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 { margin: 0px ${CELL_MARGIN}px 0px ${CELL_MARGIN}px; }`); collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row { padding-top: ${EDITOR_TOP_MARGIN}px; }`); @@ -1056,4 +1180,5 @@ registerThemingParticipant((theme, collector) => { 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 .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 776b936e5f..1a0f9a4173 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts @@ -11,6 +11,9 @@ 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; @@ -21,6 +24,9 @@ export class NotebookEditorModel extends EditorModel { 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; @@ -35,6 +41,7 @@ export class NotebookEditorModel extends EditorModel { this._register(_notebook.onDidChangeContent(() => { this._dirty = true; this._onDidChangeDirty.fire(); + this._onDidChangeContent.fire(); })); this._register(_notebook.onDidChangeCells((e) => { this._onDidChangeCells.fire(e); @@ -69,6 +76,10 @@ export class NotebookEditorModel extends EditorModel { } } + moveCellToIdx(index: number, newIdx: number) { + this.notebook.moveCellToIdx(index, newIdx); + } + async save(): Promise { if (this._notebook) { this._dirty = false; @@ -82,17 +93,56 @@ export class NotebookEditorModel extends EditorModel { } export class NotebookEditorInput extends EditorInput { + + private static readonly _instances = new Map(); + + static getOrCreate(instantiationService: IInstantiationService, resource: URI, name: string, viewType: string | undefined) { + const key = resource.toString() + viewType; + let input = NotebookEditorInput._instances.get(key); + if (!input) { + input = instantiationService.createInstance(class extends NotebookEditorInput { + dispose() { + NotebookEditorInput._instances.delete(key); + super.dispose(); + } + }, resource, name, viewType); + + NotebookEditorInput._instances.set(key, input); + } + return input; + } + 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 + @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 { @@ -107,6 +157,18 @@ export class NotebookEditorInput extends EditorInput { return this.textModel?.isDirty() || false; } + public isSaving(): boolean { + if (!this.isDirty()) { + return false; // the editor needs to be dirty for being saved + } + + if (this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) { + return true; // a short auto save is configured, treat this as being saved + } + + return false; + } + async save(group: GroupIdentifier, options?: ISaveOptions): Promise { if (this.textModel) { await this.notebookService.save(this.textModel.notebook.viewType, this.textModel.notebook.uri); @@ -125,6 +187,10 @@ export class NotebookEditorInput extends EditorInput { } async resolve(): Promise { + if (this.textModel) { + return this.textModel; + } + if (!this.promise) { if (!await this.notebookService.canResolve(this.viewType!)) { throw new Error(`Cannot open notebook of type '${this.viewType}'`); @@ -133,6 +199,9 @@ export class NotebookEditorInput extends EditorInput { 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; }); } @@ -140,6 +209,10 @@ export class NotebookEditorInput extends EditorInput { return this.promise; } + async backup(): Promise { + return {}; + } + matches(otherInput: unknown): boolean { if (this === otherInput) { return true; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookService.ts b/src/vs/workbench/contrib/notebook/browser/notebookService.ts index 1020ac367e..66f9c82bca 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookService.ts @@ -49,6 +49,7 @@ export interface INotebookService { 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; @@ -273,6 +274,10 @@ export class NotebookService extends Disposable implements INotebookService, ICu return this.notebookProviderInfoStore.getContributedNotebook(resource); } + getContributedNotebookProvider(viewType: string): NotebookProviderInfo | undefined { + return this.notebookProviderInfoStore.get(viewType); + } + getContributedNotebookOutputRenderers(mimeType: string): readonly NotebookOutputRendererInfo[] { return this.notebookRenderersInfoStore.getContributedRenderer(mimeType); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 299d59b75e..3e0b0f634d 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -193,6 +193,14 @@ export class NotebookCellList extends WorkbenchList implements ID } })); + this._viewModelStore.add(model.onDidChangeSelection(() => { + // convert model selections to view selections + const viewSelections = model.selectionHandles.map(handle => { + return model.getCellByHandle(handle); + }).filter(cell => !!cell).map(cell => this._getViewIndexUpperBound(cell!)); + this.setFocus(viewSelections); + })); + const hiddenRanges = model.getHiddenRanges(); this.setHiddenAreas(hiddenRanges, false); const newRanges = reduceCellRanges(hiddenRanges); @@ -346,8 +354,11 @@ export class NotebookCellList extends WorkbenchList implements ID } selectElement(cell: ICellViewModel) { - const index = this._getViewIndexUpperBound(cell); + if (this._viewModel) { + this._viewModel.selectionHandles = [cell.handle]; + } + const index = this._getViewIndexUpperBound(cell); if (index !== undefined) { this.setSelection([index]); this.setFocus([index]); @@ -356,7 +367,7 @@ export class NotebookCellList extends WorkbenchList implements ID setFocus(indexes: number[], browserEvent?: UIEvent): void { if (this._viewModel) { - this._viewModel.selections = indexes.map(index => this.element(index)).map(cell => cell.handle); + this._viewModel.selectionHandles = indexes.map(index => this.element(index)).map(cell => cell.handle); } super.setFocus(indexes, browserEvent); diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/errorTransform.ts b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/errorTransform.ts index 5a5fa98ff6..fa60ada8f3 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/errorTransform.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/errorTransform.ts @@ -35,6 +35,7 @@ class ErrorTransform implements IOutputTransformContribution { } } container.appendChild(traceback); + DOM.addClasses(container, 'error'); return { hasDynamicHeight: false }; 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 3451b91616..8d67cda6bc 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -19,6 +19,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { isWeb } from 'vs/base/common/platform'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; export interface IDimensionMessage { __vscode_notebook_message: boolean; @@ -114,14 +115,15 @@ export class BackLayerWebView extends Disposable { @IWebviewService readonly webviewService: IWebviewService, @IOpenerService readonly openerService: IOpenerService, @INotebookService private readonly notebookService: INotebookService, + @IEnvironmentService private readonly environmentService: IEnvironmentService ) { super(); this.element = document.createElement('div'); - this.element.style.width = `calc(100% - ${CELL_MARGIN * 2}px)`; + this.element.style.width = `calc(100% - ${CELL_MARGIN * 2 + CELL_RUN_GUTTER}px)`; this.element.style.height = '1400px'; this.element.style.position = 'absolute'; - this.element.style.margin = `0px 0 0px ${CELL_MARGIN}px`; + this.element.style.margin = `0px 0 0px ${CELL_MARGIN + CELL_RUN_GUTTER}px`; const pathsPath = getPathFromAmdModule(require, 'vs/loader.js'); const loader = URI.file(pathsPath).with({ scheme: WebviewResourceScheme }); @@ -168,7 +170,7 @@ ${loaderJs} width: 100%; padding: ${outputNodePadding}px; box-sizing: border-box; - background-color: var(--vscode-list-inactiveSelectionBackground); + background-color: var(--vscode-notebook-outputContainerBackgroundColor); } body { padding: 0px; @@ -498,7 +500,7 @@ ${loaderJs} return { id: id, top: outputOffset, - left: CELL_RUN_GUTTER + left: 0 }; }); @@ -538,7 +540,7 @@ ${loaderJs} id: cell.id, outputId: outputId, top: initialTop, - left: CELL_RUN_GUTTER + left: 0 }; this.webview.sendMessage(message); @@ -594,7 +596,12 @@ ${loaderJs} let rendererInfo = this.notebookService.getRendererInfo(preload); if (rendererInfo) { - let preloadResources = rendererInfo.preloads.map(preloadResource => preloadResource.with({ scheme: WebviewResourceScheme })); + let preloadResources = rendererInfo.preloads.map(preloadResource => { + if (this.environmentService.isExtensionDevelopment && (preloadResource.scheme === 'http' || preloadResource.scheme === 'https')) { + return preloadResource; + } + return preloadResource.with({ scheme: WebviewResourceScheme }); + }); extensionLocations.push(rendererInfo.extensionLocation); preloadResources.forEach(e => { if (!this.preloadsCache.has(e.toString())) { 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 27842b44c4..922df3cbf0 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -7,47 +7,49 @@ import 'vs/css!vs/workbench/contrib/notebook/browser/media/notebook'; import { getZoomLevel } from 'vs/base/browser/browser'; import * as DOM from 'vs/base/browser/dom'; +import { domEvent } from 'vs/base/browser/event'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; -import { IAction, ActionRunner } from 'vs/base/common/actions'; -import { Range } from 'vs/editor/common/core/range'; -import { escape } from 'vs/base/common/strings'; -import { DisposableStore } from 'vs/base/common/lifecycle'; -import * as modes from 'vs/editor/common/modes'; -import * as platform from 'vs/base/common/platform'; +import { ActionRunner, IAction } from 'vs/base/common/actions'; +import { renderCodicons } from 'vs/base/common/codicons'; import { Color } from 'vs/base/common/color'; +import { Emitter, Event } from 'vs/base/common/event'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { deepClone } from 'vs/base/common/objects'; +import * as platform from 'vs/base/common/platform'; +import { escape } from 'vs/base/common/strings'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import * as nls from 'vs/nls'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; -import { IEditorOptions, EditorOption, EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions'; +import { EditorOption, EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; +import { Range } from 'vs/editor/common/core/range'; +import { ITextModel } from 'vs/editor/common/model'; +import * as modes from 'vs/editor/common/modes'; +import { tokenizeLineToHTML } from 'vs/editor/common/modes/textToHtmlTokenizer'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import * as nls from 'vs/nls'; import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenu, MenuItemAction } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { EDITOR_BOTTOM_PADDING, EDITOR_TOOLBAR_HEIGHT, EDITOR_TOP_MARGIN, EDITOR_TOP_PADDING, NOTEBOOK_CELL_EDITABLE_CONTEXT_KEY, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE_CONTEXT_KEY, NOTEBOOK_CELL_TYPE_CONTEXT_KEY, NOTEBOOK_CELL_RUN_STATE_CONTEXT_KEY, NOTEBOOK_VIEW_TYPE, BOTTOM_CELL_TOOLBAR_HEIGHT } from 'vs/workbench/contrib/notebook/browser/constants'; -import { ExecuteCellAction, INotebookCellActionContext, CancelCellAction, InsertCodeCellAction, InsertMarkdownCellAction } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; -import { BaseCellRenderTemplate, CellEditState, CellRunState, CodeCellRenderTemplate, ICellViewModel, INotebookEditor, MarkdownCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { BOTTOM_CELL_TOOLBAR_HEIGHT, EDITOR_BOTTOM_PADDING, EDITOR_TOOLBAR_HEIGHT, EDITOR_TOP_MARGIN, EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; +import { CancelCellAction, ChangeCellLanguageAction, ExecuteCellAction, INotebookCellActionContext, InsertCodeCellAction, InsertMarkdownCellAction } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; +import { BaseCellRenderTemplate, CellEditState, CellRunState, CodeCellRenderTemplate, ICellViewModel, INotebookEditor, MarkdownCellRenderTemplate, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_RUNNABLE, NOTEBOOK_CELL_RUN_STATE, NOTEBOOK_CELL_TYPE, NOTEBOOK_VIEW_TYPE } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellMenus } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellMenus'; import { CodeCell } from 'vs/workbench/contrib/notebook/browser/view/renderers/codeCell'; import { StatefullMarkdownCell } from 'vs/workbench/contrib/notebook/browser/view/renderers/markdownCell'; +import { BaseCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; 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 { renderCodicons } from 'vs/base/common/codicons'; -import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { KeyCode } from 'vs/base/common/keyCodes'; -import { domEvent } from 'vs/base/browser/event'; -import { tokenizeLineToHTML } from 'vs/editor/common/modes/textToHtmlTokenizer'; -import { ITextModel } from 'vs/editor/common/model'; -import { BaseCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel'; const $ = DOM.$; @@ -94,46 +96,89 @@ export class CodiconActionViewItem extends ContextAwareMenuEntryActionViewItem { } } +export class CellEditorOptions { + + private static fixedEditorOptions: IEditorOptions = { + padding: { + top: EDITOR_TOP_PADDING, + bottom: EDITOR_BOTTOM_PADDING + }, + scrollBeyondLastLine: false, + scrollbar: { + verticalScrollbarSize: 14, + horizontal: 'auto', + useShadows: true, + verticalHasArrows: false, + horizontalHasArrows: false, + alwaysConsumeMouseWheel: false + }, + renderLineHighlightOnlyWhenFocus: true, + overviewRulerLanes: 0, + selectOnLineNumbers: false, + lineNumbers: 'off', + lineDecorationsWidth: 0, + glyphMargin: false, + fixedOverflowWidgets: true, + minimap: { enabled: false }, + renderValidationDecorations: 'on' + }; + + private _value: IEditorOptions; + private _disposable: IDisposable; + + private readonly _onDidChange = new Emitter(); + readonly onDidChange: Event = this._onDidChange.event; + + constructor(configurationService: IConfigurationService, language: string) { + + this._disposable = configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('editor')) { + this._value = computeEditorOptions(); + this._onDidChange.fire(this.value); + } + }); + + const computeEditorOptions = () => { + const editorOptions = deepClone(configurationService.getValue('editor', { overrideIdentifier: language })); + return { + ...editorOptions, + ...CellEditorOptions.fixedEditorOptions + }; + }; + + this._value = computeEditorOptions(); + } + + dispose(): void { + this._onDidChange.dispose(); + this._disposable.dispose(); + } + + get value(): IEditorOptions { + return this._value; + } +} + abstract class AbstractCellRenderer { - protected editorOptions: IEditorOptions; + protected editorOptions: CellEditorOptions; private actionRunner = new ActionRunner(); constructor( protected readonly instantiationService: IInstantiationService, protected readonly notebookEditor: INotebookEditor, protected readonly contextMenuService: IContextMenuService, - private readonly configurationService: IConfigurationService, + configurationService: IConfigurationService, private readonly keybindingService: IKeybindingService, private readonly notificationService: INotificationService, protected readonly contextKeyService: IContextKeyService, language: string, protected readonly dndController: CellDragAndDropController ) { - const editorOptions = deepClone(this.configurationService.getValue('editor', { overrideIdentifier: language })); - this.editorOptions = { - ...editorOptions, - padding: { - top: EDITOR_TOP_PADDING, - bottom: EDITOR_BOTTOM_PADDING - }, - scrollBeyondLastLine: false, - scrollbar: { - verticalScrollbarSize: 14, - horizontal: 'auto', - useShadows: true, - verticalHasArrows: false, - horizontalHasArrows: false, - alwaysConsumeMouseWheel: false - }, - renderLineHighlightOnlyWhenFocus: true, - overviewRulerLanes: 0, - selectOnLineNumbers: false, - lineNumbers: 'off', - lineDecorationsWidth: 0, - glyphMargin: false, - fixedOverflowWidgets: false, - minimap: { enabled: false }, - }; + this.editorOptions = new CellEditorOptions(configurationService, language); + } + + dispose() { + this.editorOptions.dispose(); } protected createBottomCellToolbar(container: HTMLElement): ToolBar { @@ -163,8 +208,13 @@ abstract class AbstractCellRenderer { addCodeCell.tabIndex = 0; const insertCellBelow = this.instantiationService.createInstance(InsertCodeCellAction); + const toolbarContext = { + ...context, + ui: true + }; + disposables.add(DOM.addDisposableListener(addCodeCell, DOM.EventType.CLICK, e => { - this.actionRunner.run(insertCellBelow, context); + this.actionRunner.run(insertCellBelow, toolbarContext); e.stopPropagation(); })); @@ -173,7 +223,7 @@ abstract class AbstractCellRenderer { if ((event.equals(KeyCode.Enter) || event.equals(KeyCode.Space))) { e.preventDefault(); e.stopPropagation(); - this.actionRunner.run(insertCellBelow, context); + this.actionRunner.run(insertCellBelow, toolbarContext); } }))); @@ -208,7 +258,7 @@ abstract class AbstractCellRenderer { })); } else { container.style.position = 'static'; - container.style.height = '22px'; + container.style.height = `${BOTTOM_CELL_TOOLBAR_HEIGHT}`; } } @@ -247,8 +297,10 @@ abstract class AbstractCellRenderer { if (templateData.focusIndicator) { if (actions.length) { + templateData.container.classList.add('cell-has-toolbar-actions'); templateData.focusIndicator.style.top = `${EDITOR_TOOLBAR_HEIGHT + EDITOR_TOP_MARGIN}px`; } else { + templateData.container.classList.remove('cell-has-toolbar-actions'); templateData.focusIndicator.style.top = `${EDITOR_TOP_MARGIN}px`; } } @@ -276,6 +328,7 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR contextKeyService: IContextKeyService, notehookEditor: INotebookEditor, dndController: CellDragAndDropController, + private renderedEditors: Map, @IInstantiationService instantiationService: IInstantiationService, @IConfigurationService configurationService: IConfigurationService, @IContextMenuService contextMenuService: IContextMenuService, @@ -293,35 +346,36 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR container.classList.add('markdown-cell-row'); const disposables = new DisposableStore(); const toolbar = disposables.add(this.createToolbar(container)); - - const dragHandle = DOM.prepend(container, $('.cell-drag-handle')); - dragHandle.innerHTML = renderCodicons('$(gripper)'); - dragHandle.setAttribute('draggable', 'true'); + const focusIndicator = DOM.append(container, DOM.$('.notebook-cell-focus-indicator')); + focusIndicator.setAttribute('draggable', 'true'); const codeInnerContent = DOM.append(container, $('.cell.code')); - const cellEditorPart = DOM.append(codeInnerContent, $('.cell-editor-part')); - const editingContainer = DOM.append(cellEditorPart, $('.markdown-editor-container')); - editingContainer.style.display = 'none'; + const editorPart = DOM.append(codeInnerContent, $('.cell-editor-part')); + const editorContainer = DOM.append(editorPart, $('.markdown-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 focusIndicator = DOM.append(container, DOM.$('.notebook-cell-focus-indicator')); const foldingIndicator = DOM.append(container, DOM.$('.notebook-folding-indicator')); const bottomCellContainer = DOM.append(container, $('.cell-bottom-toolbar-container')); + const statusBar = this.instantiationService.createInstance(CellEditorStatusBar, editorPart); + const templateData: MarkdownCellRenderTemplate = { insertionIndicatorTop, - dragHandle, container, cellContainer: innerContent, - editingContainer, + editorPart, + editorContainer, focusIndicator, foldingIndicator, disposables, elementDisposables: new DisposableStore(), toolbar, bottomCellContainer, + statusBarContainer: statusBar.statusBarContainer, + languageStatusBarItem: statusBar.languageStatusBarItem, toJSON: () => { return {}; } }; this.dndController.addListeners(templateData, () => this.getDragImage(templateData)); @@ -339,7 +393,7 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR this.commonRenderElement(element, index, templateData); templateData.currentRenderedCell = element; - templateData.editingContainer!.style.display = 'none'; + templateData.editorPart!.style.display = 'none'; templateData.cellContainer.innerHTML = ''; let renderedHTML = element.getHTML(); if (renderedHTML) { @@ -349,14 +403,28 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR if (height) { const elementDisposables = templateData.elementDisposables; - elementDisposables.add(new StatefullMarkdownCell(this.notebookEditor, element, templateData, this.editorOptions, this.instantiationService)); - + // render toolbar first const contextKeyService = this.contextKeyService.createScoped(templateData.container); - contextKeyService.createKey(NOTEBOOK_CELL_TYPE_CONTEXT_KEY, 'markdown'); - contextKeyService.createKey(NOTEBOOK_VIEW_TYPE, element.viewType); + this.setupCellToolbarActions(contextKeyService, templateData, elementDisposables); + const toolbarContext = { + cell: element, + notebookEditor: this.notebookEditor, + $mid: 12 + }; + templateData.toolbar.context = toolbarContext; + + this.setupBetweenCellToolbarActions(element, templateData, elementDisposables, toolbarContext); + + const markdownCell = new StatefullMarkdownCell(this.notebookEditor, element, templateData, this.editorOptions.value, this.renderedEditors, this.instantiationService); + elementDisposables.add(this.editorOptions.onDidChange(newValue => markdownCell.updateEditorOptions(newValue))); + elementDisposables.add(markdownCell); + + NOTEBOOK_CELL_TYPE.bindTo(contextKeyService).set('markdown'); + NOTEBOOK_VIEW_TYPE.bindTo(contextKeyService).set(element.viewType); const metadata = element.getEvaluatedMetadata(this.notebookEditor.viewModel!.notebookDocument.metadata); - const cellEditableKey = contextKeyService.createKey(NOTEBOOK_CELL_EDITABLE_CONTEXT_KEY, !!metadata.editable); + const cellEditableKey = NOTEBOOK_CELL_EDITABLE.bindTo(contextKeyService); + cellEditableKey.set(!!metadata.editable); const updateForMetadata = () => { const metadata = element.getEvaluatedMetadata(this.notebookEditor.viewModel!.notebookDocument.metadata); cellEditableKey.set(!!metadata.editable); @@ -369,26 +437,18 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR } })); - const editModeKey = contextKeyService.createKey(NOTEBOOK_CELL_MARKDOWN_EDIT_MODE_CONTEXT_KEY, element.editState === CellEditState.Editing); + const editModeKey = NOTEBOOK_CELL_MARKDOWN_EDIT_MODE.bindTo(contextKeyService); + editModeKey.set(element.editState === CellEditState.Editing); elementDisposables.add(element.onDidChangeState((e) => { if (e.editStateChanged) { editModeKey.set(element.editState === CellEditState.Editing); } })); - this.setupCellToolbarActions(contextKeyService, templateData, elementDisposables); - - const toolbarContext = { - cell: element, - notebookEditor: this.notebookEditor, - $mid: 12 - }; - templateData.toolbar.context = toolbarContext; - - this.setupBetweenCellToolbarActions(element, templateData, elementDisposables, toolbarContext); element.totalHeight = height; - } + templateData.languageStatusBarItem.update(element, this.notebookEditor); + } } disposeTemplate(templateData: MarkdownCellRenderTemplate): void { @@ -418,7 +478,7 @@ export class CellDragAndDropController { addListeners(templateData: BaseCellRenderTemplate, dragImageProvider: DragImageProvider): void { const container = templateData.container; - const dragHandle = templateData.dragHandle; + const dragHandle = templateData.focusIndicator; const dragCleanup = () => { if (this.currentDraggedCell) { @@ -480,6 +540,45 @@ export class CellDragAndDropController { } } +export class CellLanguageStatusBarItem extends Disposable { + private labelElement: HTMLElement; + + private _cell: BaseCellViewModel | undefined; + private _editor: INotebookEditor | undefined; + + private cellDisposables: DisposableStore; + + constructor( + readonly container: HTMLElement, + @IModeService private readonly modeService: IModeService, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { + super(); + this.labelElement = DOM.append(container, $('.cell-language-picker')); + this.labelElement.tabIndex = -1; // allows screen readers to read title, but still prevents tab focus. + + this._register(DOM.addDisposableListener(this.labelElement, DOM.EventType.CLICK, () => { + this.instantiationService.invokeFunction(accessor => { + new ChangeCellLanguageAction().run(accessor, { notebookEditor: this._editor!, cell: this._cell! }); + }); + })); + this._register(this.cellDisposables = new DisposableStore()); + } + + update(cell: BaseCellViewModel, editor: INotebookEditor): void { + this.cellDisposables.clear(); + this._cell = cell; + this._editor = editor; + + this.render(); + this.cellDisposables.add(this._cell.model.onDidChangeLanguage(() => this.render())); + } + + private render(): void { + this.labelElement.textContent = this.modeService.getLanguageName(this._cell!.language!); + } +} + class EditorTextRenderer { getRichText(editor: ICodeEditor, modelRange: Range): string | null { @@ -580,6 +679,25 @@ class CodeCellDragImageRenderer { } } +class CellEditorStatusBar { + readonly cellStatusMessageContainer: HTMLElement; + readonly cellRunStatusContainer: HTMLElement; + readonly statusBarContainer: HTMLElement; + readonly languageStatusBarItem: CellLanguageStatusBarItem; + + constructor( + container: HTMLElement, + @IInstantiationService instantiationService: IInstantiationService + ) { + this.statusBarContainer = DOM.append(container, $('.cell-statusbar-container')); + const leftStatusBarItems = DOM.append(this.statusBarContainer, $('.cell-status-left')); + const rightStatusBarItems = DOM.append(this.statusBarContainer, $('.cell-status-right')); + this.cellRunStatusContainer = DOM.append(leftStatusBarItems, $('.cell-run-status')); + this.cellStatusMessageContainer = DOM.append(leftStatusBarItems, $('.cell-status-message')); + this.languageStatusBarItem = instantiationService.createInstance(CellLanguageStatusBarItem, rightStatusBarItems); + } +} + export class CodeCellRenderer extends AbstractCellRenderer implements IListRenderer { static readonly TEMPLATE_ID = 'code_cell'; @@ -603,53 +721,49 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende renderTemplate(container: HTMLElement): CodeCellRenderTemplate { container.classList.add('code-cell-row'); + container.tabIndex = 0; const disposables = new DisposableStore(); const toolbar = disposables.add(this.createToolbar(container)); + const focusIndicator = DOM.append(container, DOM.$('.notebook-cell-focus-indicator')); + focusIndicator.setAttribute('draggable', 'true'); const cellContainer = DOM.append(container, $('.cell.code')); const runButtonContainer = DOM.append(cellContainer, $('.run-button-container')); const runToolbar = this.createToolbar(runButtonContainer); disposables.add(runToolbar); - const dragHandle = DOM.prepend(container, $('.cell-drag-handle')); - dragHandle.innerHTML = renderCodicons('$(gripper)'); - dragHandle.setAttribute('draggable', 'true'); - const executionOrderLabel = DOM.append(runButtonContainer, $('div.execution-count-label')); const editorPart = DOM.append(cellContainer, $('.cell-editor-part')); const editorContainer = DOM.append(editorPart, $('.cell-editor-container')); const editor = this.instantiationService.createInstance(CodeEditorWidget, editorContainer, { - ...this.editorOptions, + ...this.editorOptions.value, dimension: { width: 0, height: 0 } }, {}); + disposables.add(this.editorOptions.onDidChange(newValue => editor.updateOptions(newValue))); + const progressBar = new ProgressBar(editorPart); progressBar.hide(); disposables.add(progressBar); - const statusBarContainer = DOM.append(editorPart, $('.cell-statusbar-container')); - const cellRunStatusContainer = DOM.append(statusBarContainer, $('.cell-run-status')); - const cellStatusMessageContainer = DOM.append(statusBarContainer, $('.cell-status-message')); - const cellStatusPlaceholderContainer = DOM.append(statusBarContainer, $('.cell-status-placeholder')); + const statusBar = this.instantiationService.createInstance(CellEditorStatusBar, editorPart); const insertionIndicatorTop = DOM.append(container, DOM.$('.notebook-cell-insertion-indicator-top')); - const focusIndicator = DOM.append(container, DOM.$('.notebook-cell-focus-indicator')); const outputContainer = DOM.append(container, $('.output')); const bottomCellContainer = DOM.append(container, $('.cell-bottom-toolbar-container')); const templateData: CodeCellRenderTemplate = { insertionIndicatorTop, - dragHandle, container, cellContainer, - statusBarContainer, - cellRunStatusContainer, - cellStatusMessageContainer, - cellStatusPlaceholderContainer, + statusBarContainer: statusBar.statusBarContainer, + cellRunStatusContainer: statusBar.cellRunStatusContainer, + cellStatusMessageContainer: statusBar.cellStatusMessageContainer, + languageStatusBarItem: statusBar.languageStatusBarItem, progressBar, focusIndicator, toolbar, @@ -685,11 +799,12 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende } } - private updateForMetadata(element: CodeCellViewModel, templateData: CodeCellRenderTemplate, cellEditableKey: IContextKey): void { + private updateForMetadata(element: CodeCellViewModel, templateData: CodeCellRenderTemplate, cellEditableKey: IContextKey, cellRunnableKey: IContextKey): void { const metadata = element.getEvaluatedMetadata(this.notebookEditor.viewModel!.notebookDocument.metadata); DOM.toggleClass(templateData.cellContainer, 'runnable', !!metadata.runnable); this.renderExecutionOrder(element, templateData); cellEditableKey.set(!!metadata.editable); + cellRunnableKey.set(!!metadata.runnable); templateData.cellStatusMessageContainer.textContent = metadata?.statusMessage || ''; if (metadata.runState === NotebookCellRunState.Success) { @@ -703,19 +818,13 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende } else { templateData.cellRunStatusContainer.innerHTML = ''; } - - if (!metadata.statusMessage && (typeof metadata.runState === 'undefined' || metadata.runState === NotebookCellRunState.Idle)) { - templateData.cellStatusPlaceholderContainer.textContent = platform.isWindows ? 'Ctrl + Alt + Enter to run' : 'Ctrl + Enter to run'; - } else { - templateData.cellStatusPlaceholderContainer.textContent = ''; - } } private renderExecutionOrder(element: CodeCellViewModel, templateData: CodeCellRenderTemplate): void { const hasExecutionOrder = this.notebookEditor.viewModel!.notebookDocument.metadata?.hasExecutionOrder; if (hasExecutionOrder) { - const executionOrdeerLabel = typeof element.metadata?.executionOrder === 'number' ? `[ ${element.metadata.executionOrder} ]` : - '[ ]'; + const executionOrdeerLabel = typeof element.metadata?.executionOrder === 'number' ? `[${element.metadata.executionOrder}]` : + '[ ]'; templateData.executionOrderLabel.innerText = executionOrdeerLabel; } else { templateData.executionOrderLabel.innerText = ''; @@ -749,7 +858,8 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende const contextKeyService = this.contextKeyService.createScoped(templateData.container); - const runStateKey = contextKeyService.createKey(NOTEBOOK_CELL_RUN_STATE_CONTEXT_KEY, CellRunState[element.runState]); + const runStateKey = NOTEBOOK_CELL_RUN_STATE.bindTo(contextKeyService); + runStateKey.set(CellRunState[element.runState]); this.updateForRunState(element, templateData, runStateKey); elementDisposables.add(element.onDidChangeState((e) => { if (e.runStateChanged) { @@ -757,14 +867,23 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende } })); - contextKeyService.createKey(NOTEBOOK_CELL_TYPE_CONTEXT_KEY, 'code'); - contextKeyService.createKey(NOTEBOOK_VIEW_TYPE, element.viewType); + const cellHasOutputsContext = NOTEBOOK_CELL_HAS_OUTPUTS.bindTo(contextKeyService); + cellHasOutputsContext.set(element.outputs.length > 0); + elementDisposables.add(element.onDidChangeOutputs(() => { + cellHasOutputsContext.set(element.outputs.length > 0); + })); + + NOTEBOOK_CELL_TYPE.bindTo(contextKeyService).set('code'); + NOTEBOOK_VIEW_TYPE.bindTo(contextKeyService).set(element.viewType); const metadata = element.getEvaluatedMetadata(this.notebookEditor.viewModel!.notebookDocument.metadata); - const cellEditableKey = contextKeyService.createKey(NOTEBOOK_CELL_EDITABLE_CONTEXT_KEY, !!metadata.editable); - this.updateForMetadata(element, templateData, cellEditableKey); + const cellEditableKey = NOTEBOOK_CELL_EDITABLE.bindTo(contextKeyService); + cellEditableKey.set(!!metadata.editable); + const cellRunnableKey = NOTEBOOK_CELL_RUNNABLE.bindTo(contextKeyService); + cellRunnableKey.set(!!metadata.runnable); + this.updateForMetadata(element, templateData, cellEditableKey, cellRunnableKey); elementDisposables.add(element.onDidChangeState((e) => { if (e.metadataChanged) { - this.updateForMetadata(element, templateData, cellEditableKey); + this.updateForMetadata(element, templateData, cellEditableKey, cellRunnableKey); } if (e.outputIsHoveredChanged) { @@ -784,6 +903,8 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende templateData.runToolbar.context = toolbarContext; this.setupBetweenCellToolbarActions(element, templateData, elementDisposables, toolbarContext); + + templateData.languageStatusBarItem.update(element, this.notebookEditor); } disposeTemplate(templateData: CodeCellRenderTemplate): void { 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 fb322c2d2b..8f7aab9a0d 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/codeCell.ts @@ -286,7 +286,7 @@ export class CodeCell extends Disposable { const mimeTypePicker = DOM.$('.multi-mimetype-output'); DOM.addClasses(mimeTypePicker, 'codicon', 'codicon-code'); mimeTypePicker.tabIndex = 0; - mimeTypePicker.title = nls.localize('mimeTypePicker', "Choose a different output mimetype"); + mimeTypePicker.title = nls.localize('mimeTypePicker', "Choose a different output mimetype, available mimetypes: {0}", transformedDisplayOutput.orderedMimeTypes.map(mimeType => mimeType.mimeType).join(', ')); outputItemDiv.appendChild(mimeTypePicker); this.outputResizeListeners.get(currOutput)!.add(DOM.addStandardDisposableListener(mimeTypePicker, 'mousedown', async e => { if (e.leftButton) { 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 6a5099cd4d..6ca83f53e0 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts @@ -7,21 +7,24 @@ import { hide, IDimension, show, toggleClass } from 'vs/base/browser/dom'; import { raceCancellation } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { renderCodicons } from 'vs/base/common/codicons'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ITextModel } from 'vs/editor/common/model'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { EDITOR_BOTTOM_PADDING, EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; -import { CellEditState, CellFocusMode, INotebookEditor, MarkdownCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { EDITOR_BOTTOM_PADDING, EDITOR_TOP_PADDING, CELL_STATUSBAR_HEIGHT } from 'vs/workbench/contrib/notebook/browser/constants'; +import { CellEditState, CellFocusMode, INotebookEditor, MarkdownCellRenderTemplate, ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/renderers/sizeObserver'; 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'; export class StatefullMarkdownCell extends Disposable { + private editor: CodeEditorWidget | null = null; + private editorOptions: IEditorOptions; private markdownContainer: HTMLElement; - private editingContainer: HTMLElement; + private editorPart: HTMLElement; private localDisposables: DisposableStore; private foldingState: CellFoldingState; @@ -31,24 +34,27 @@ export class StatefullMarkdownCell extends Disposable { private viewCell: MarkdownCellViewModel, private templateData: MarkdownCellRenderTemplate, editorOptions: IEditorOptions, + renderedEditors: Map, instantiationService: IInstantiationService ) { super(); this.markdownContainer = templateData.cellContainer; - this.editingContainer = templateData.editingContainer; + this.editorPart = templateData.editorPart; + this.editorOptions = editorOptions; this.localDisposables = new DisposableStore(); this._register(this.localDisposables); + this._register(toDisposable(() => renderedEditors.delete(this.viewCell))); const viewUpdate = () => { if (viewCell.editState === CellEditState.Editing) { // switch to editing mode - let totalHeight: number; + let editorHeight: number; - show(this.editingContainer); + show(this.editorPart); hide(this.markdownContainer); if (this.editor) { - totalHeight = this.editor!.getContentHeight(); + editorHeight = this.editor!.getContentHeight(); // not first time, we don't need to create editor or bind listeners viewCell.attachTextEditor(this.editor); @@ -61,14 +67,14 @@ export class StatefullMarkdownCell extends Disposable { const width = viewCell.layoutInfo.editorWidth; const lineNum = viewCell.lineCount; const lineHeight = viewCell.layoutInfo.fontInfo?.lineHeight || 17; - totalHeight = Math.max(lineNum, 1) * lineHeight + EDITOR_TOP_PADDING + EDITOR_BOTTOM_PADDING; + editorHeight = Math.max(lineNum, 1) * lineHeight + EDITOR_TOP_PADDING + EDITOR_BOTTOM_PADDING; - this.editingContainer.innerHTML = ''; - this.editor = instantiationService.createInstance(CodeEditorWidget, this.editingContainer, { - ...editorOptions, + this.templateData.editorContainer.innerHTML = ''; + this.editor = instantiationService.createInstance(CodeEditorWidget, this.templateData.editorContainer, { + ...this.editorOptions, dimension: { width: width, - height: totalHeight + height: editorHeight } }, {}); @@ -85,7 +91,7 @@ export class StatefullMarkdownCell extends Disposable { } const realContentHeight = this.editor!.getContentHeight(); - if (realContentHeight !== totalHeight) { + if (realContentHeight !== editorHeight) { this.editor!.layout( { width: width, @@ -102,19 +108,22 @@ export class StatefullMarkdownCell extends Disposable { this.bindEditorListeners(model, { width: width, - height: totalHeight + height: editorHeight }); }); } const clientHeight = this.markdownContainer.clientHeight; - this.viewCell.totalHeight = totalHeight + 32 + clientHeight; - notebookEditor.layoutNotebookCell(viewCell, totalHeight + 32 + clientHeight); + const totalHeight = editorHeight + 32 + clientHeight + CELL_STATUSBAR_HEIGHT; + this.viewCell.totalHeight = totalHeight; + notebookEditor.layoutNotebookCell(viewCell, totalHeight); this.editor.focus(); + renderedEditors.set(this.viewCell, this.editor!); } else { this.viewCell.detachTextEditor(); - hide(this.editingContainer); + hide(this.editorPart); show(this.markdownContainer); + renderedEditors.delete(this.viewCell); if (this.editor) { // switch from editing mode const clientHeight = templateData.container.clientHeight; @@ -146,6 +155,10 @@ export class StatefullMarkdownCell extends Disposable { this.markdownContainer.appendChild(renderedHTML); } })); + + const clientHeight = templateData.container.clientHeight; + this.viewCell.totalHeight = clientHeight; + notebookEditor.layoutNotebookCell(viewCell, clientHeight); } } }; @@ -192,6 +205,13 @@ export class StatefullMarkdownCell extends Disposable { viewUpdate(); } + updateEditorOptions(newValue: IEditorOptions): any { + this.editorOptions = newValue; + if (this.editor) { + this.editor.updateOptions(this.editorOptions); + } + } + setFoldingIndicator() { switch (this.foldingState) { case CellFoldingState.None: @@ -220,7 +240,7 @@ export class StatefullMarkdownCell extends Disposable { clientHeight = this.markdownContainer.clientHeight; } - this.viewCell.totalHeight = this.editor!.getContentHeight() + 32 + clientHeight; + this.viewCell.totalHeight = this.editor!.getContentHeight() + 32 + clientHeight + CELL_STATUSBAR_HEIGHT; this.notebookEditor.layoutNotebookCell(this.viewCell, this.viewCell.layoutInfo.totalHeight); })); @@ -253,7 +273,7 @@ export class StatefullMarkdownCell extends Disposable { } })); - let cellWidthResizeObserver = getResizesObserver(this.templateData.editingContainer, dimension, () => { + let cellWidthResizeObserver = getResizesObserver(this.templateData.editorContainer, dimension, () => { let newWidth = cellWidthResizeObserver.getWidth(); let realContentHeight = this.editor!.getContentHeight(); let layoutInfo = this.editor!.getLayoutInfo(); diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts index be8fd29732..b7a5fb20a3 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts @@ -175,7 +175,7 @@ export abstract class BaseCellViewModel extends Disposable implements ICellViewM } detachTextEditor() { - this._editorViewStates = this.saveViewState(); + this.saveViewState(); // decorations need to be cleared first as editors can be resued. this._resolvedDecorations.forEach(value => { let resolvedid = value.id; @@ -199,17 +199,19 @@ export abstract class BaseCellViewModel extends Disposable implements ICellViewM return this.model.source.join('\n'); } - private saveViewState(): editorCommon.ICodeEditorViewState | null { + abstract save(): void; + + private saveViewState(): void { if (!this._textEditor) { - return null; + return; } - return this._textEditor.saveViewState(); + this._editorViewStates = this._textEditor.saveViewState(); } saveEditorViewState() { if (this._textEditor) { - this._editorViewStates = this.saveViewState(); + this._editorViewStates = this._textEditor.saveViewState(); } return this._editorViewStates; diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/cellEdit.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/cellEdit.ts index 1ef1130b8f..d3629e7b4d 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/cellEdit.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/cellEdit.ts @@ -17,6 +17,7 @@ export interface ICellEditingDelegate { deleteCell?(index: number): void; moveCell?(fromIndex: number, toIndex: number): void; createCellViewModel?(cell: ICell): BaseCellViewModel; + setSelections(selections: number[]): void; } export class InsertCellEdit implements IResourceUndoRedoElement { @@ -26,7 +27,9 @@ export class InsertCellEdit implements IResourceUndoRedoElement { public resource: URI, private insertIndex: number, private cell: BaseCellViewModel, - private editingDelegate: ICellEditingDelegate + private editingDelegate: ICellEditingDelegate, + private beforedSelections: number[], + private endSelections: number[] ) { } @@ -36,6 +39,7 @@ export class InsertCellEdit implements IResourceUndoRedoElement { } this.editingDelegate.deleteCell(this.insertIndex); + this.editingDelegate.setSelections(this.beforedSelections); } redo(): void | Promise { if (!this.editingDelegate.insertCell) { @@ -43,6 +47,7 @@ export class InsertCellEdit implements IResourceUndoRedoElement { } this.editingDelegate.insertCell(this.insertIndex, this.cell); + this.editingDelegate.setSelections(this.endSelections); } } @@ -55,7 +60,9 @@ export class DeleteCellEdit implements IResourceUndoRedoElement { public resource: URI, private insertIndex: number, cell: BaseCellViewModel, - private editingDelegate: ICellEditingDelegate + private editingDelegate: ICellEditingDelegate, + private beforedSelections: number[], + private endSelections: number[] ) { this._rawCell = cell.model; @@ -70,6 +77,7 @@ export class DeleteCellEdit implements IResourceUndoRedoElement { const cell = this.editingDelegate.createCellViewModel(this._rawCell); this.editingDelegate.insertCell(this.insertIndex, cell); + this.editingDelegate.setSelections(this.beforedSelections); } redo(): void | Promise { @@ -78,6 +86,7 @@ export class DeleteCellEdit implements IResourceUndoRedoElement { } this.editingDelegate.deleteCell(this.insertIndex); + this.editingDelegate.setSelections(this.endSelections); } } @@ -89,7 +98,9 @@ export class MoveCellEdit implements IResourceUndoRedoElement { public resource: URI, private fromIndex: number, private toIndex: number, - private editingDelegate: ICellEditingDelegate + private editingDelegate: ICellEditingDelegate, + private beforedSelections: number[], + private endSelections: number[] ) { } @@ -99,6 +110,7 @@ export class MoveCellEdit implements IResourceUndoRedoElement { } this.editingDelegate.moveCell(this.toIndex, this.fromIndex); + this.editingDelegate.setSelections(this.beforedSelections); } redo(): void | Promise { @@ -107,6 +119,7 @@ export class MoveCellEdit implements IResourceUndoRedoElement { } this.editingDelegate.moveCell(this.fromIndex, this.toIndex); + this.editingDelegate.setSelections(this.endSelections); } } @@ -116,7 +129,9 @@ export class SpliceCellsEdit implements IResourceUndoRedoElement { constructor( public resource: URI, private diffs: [number, CellViewModel[], CellViewModel[]][], - private editingDelegate: ICellEditingDelegate + private editingDelegate: ICellEditingDelegate, + private beforeHandles: number[], + private endHandles: number[] ) { } @@ -134,6 +149,7 @@ export class SpliceCellsEdit implements IResourceUndoRedoElement { this.editingDelegate.insertCell!(diff[0], cell); }); }); + this.editingDelegate.setSelections(this.beforeHandles); } redo(): void | Promise { @@ -151,5 +167,6 @@ export class SpliceCellsEdit implements IResourceUndoRedoElement { }); }); + this.editingDelegate.setSelections(this.endHandles); } } diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts index d9a1b6b19a..ba56276cf9 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts @@ -86,7 +86,7 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod this._layoutInfo = { fontInfo: initialNotebookLayoutInfo?.fontInfo || null, editorHeight: 0, - editorWidth: initialNotebookLayoutInfo ? initialNotebookLayoutInfo!.width - CELL_MARGIN * 2 - CELL_RUN_GUTTER : 0, + editorWidth: initialNotebookLayoutInfo ? this.computeEditorWidth(initialNotebookLayoutInfo!.width) : 0, outputContainerOffset: 0, outputTotalHeight: 0, totalHeight: 0, @@ -95,6 +95,10 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod }; } + private computeEditorWidth(outerWidth: number): number { + return outerWidth - (CELL_MARGIN * 2 + CELL_RUN_GUTTER); + } + layoutChange(state: CodeCellLayoutChangeEvent) { // recompute this._ensureOutputsTop(); @@ -103,7 +107,7 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod const indicatorHeight = this.editorHeight + CELL_STATUSBAR_HEIGHT + outputTotalHeight; const outputContainerOffset = EDITOR_TOOLBAR_HEIGHT + EDITOR_TOP_MARGIN + this.editorHeight + CELL_STATUSBAR_HEIGHT; const bottomToolbarOffset = totalHeight - BOTTOM_CELL_TOOLBAR_HEIGHT; - const editorWidth = state.outerWidth !== undefined ? state.outerWidth - CELL_MARGIN * 2 - CELL_RUN_GUTTER : this._layoutInfo?.editorWidth; + const editorWidth = state.outerWidth !== undefined ? this.computeEditorWidth(state.outerWidth) : this._layoutInfo?.editorWidth; this._layoutInfo = { fontInfo: state.font || null, editorHeight: this._editorHeight, diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts index 6da56252c0..d1e441133c 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts @@ -71,7 +71,7 @@ export class MarkdownCellViewModel extends BaseCellViewModel implements ICellVie } private computeEditorWidth(outerWidth: number) { - return outerWidth - CELL_MARGIN * 2 - CELL_RUN_GUTTER; + return outerWidth - (CELL_MARGIN * 2) - CELL_RUN_GUTTER; } layoutChange(state: MarkdownCellLayoutChangeEvent) { diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts index 377a4b95ed..fb288192e4 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts @@ -127,6 +127,20 @@ function _normalizeOptions(options: IModelDecorationOptions): ModelDecorationOpt return ModelDecorationOptions.createDynamic(options); } +function selectionsEqual(a: number[], b: number[]) { + if (a.length !== b.length) { + return false; + } + + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) { + return false; + } + } + + return true; +} + let MODEL_ID = 0; @@ -197,15 +211,24 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD return this._layoutInfo; } + private readonly _onDidChangeSelection = new Emitter(); + get onDidChangeSelection(): Event { return this._onDidChangeSelection.event; } + private _selections: number[] = []; - get selections() { + get selectionHandles() { return this._selections; } - set selections(selections: number[]) { + set selectionHandles(selections: number[]) { + selections = selections.sort(); + if (selectionsEqual(selections, this.selectionHandles)) { + return; + } + this._selections = selections; this._model.notebook.selections = selections; + this._onDidChangeSelection.fire(); } private _decorationsTree = new DecorationsTree(); @@ -257,10 +280,39 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD splices: diffs }); + let endSelectionHandles: number[] = []; + if (this.selectionHandles.length) { + const primaryHandle = this.selectionHandles[0]; + const primarySelectionIndex = this._viewCells.indexOf(this.getCellByHandle(primaryHandle)!); + endSelectionHandles = [primaryHandle]; + let delta = 0; + + for (let i = 0; i < diffs.length; i++) { + const diff = diffs[0]; + if (diff[0] + diff[1] <= primarySelectionIndex) { + delta += diff[2].length - diff[1]; + continue; + } + + if (diff[0] > primarySelectionIndex) { + endSelectionHandles = [primaryHandle]; + break; + } + + if (diff[0] + diff[1] > primaryHandle) { + endSelectionHandles = [this._viewCells[diff[0] + delta].handle]; + break; + } + } + } + this.undoService.pushElement(new SpliceCellsEdit(this.uri, undoDiff, { insertCell: this._insertCellDelegate.bind(this), - deleteCell: this._deleteCellDelegate.bind(this) - })); + deleteCell: this._deleteCellDelegate.bind(this), + setSelections: this._setSelectionsDelegate.bind(this) + }, this.selectionHandles, endSelectionHandles)); + + this.selectionHandles = endSelectionHandles; })); this._register(this._model.notebook.onDidChangeMetadata(e => { @@ -379,10 +431,35 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD }); } + getCellByHandle(handle: number) { + return this._handleToViewCellMapping.get(handle); + } + getCellIndex(cell: ICellViewModel) { return this._viewCells.indexOf(cell as CellViewModel); } + getNextVisibleCellIndex(index: number) { + for (let i = 0; i < this._hiddenRanges.length; i++) { + const cellRange = this._hiddenRanges[i]; + const foldStart = cellRange.start - 1; + const foldEnd = cellRange.end; + + if (foldEnd < index) { + continue; + } + + // foldEnd >= index + if (foldStart <= index) { + return foldEnd + 1; + } + + break; + } + + return index + 1; + } + hasCell(cell: ICellViewModel) { return this._handleToViewCellMapping.has(cell.handle); } @@ -517,6 +594,10 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD this._onDidChangeViewCells.fire({ synchronous: true, splices: [[deleteIndex, 1, []]] }); } + private _setSelectionsDelegate(selections: number[]) { + this.selectionHandles = selections; + } + createCell(index: number, source: string[], language: string, type: CellKind, synchronous: boolean) { const cell = this._model.notebook.createCellTextModel(source, language, type, [], undefined); let newCell: CellViewModel = createCellViewModel(this.instantiationService, this, cell); @@ -524,10 +605,12 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD this._handleToViewCellMapping.set(newCell.handle, newCell); this._model.insertCell(cell, index); this._localStore.add(newCell); + this.undoService.pushElement(new InsertCellEdit(this.uri, index, newCell, { insertCell: this._insertCellDelegate.bind(this), - deleteCell: this._deleteCellDelegate.bind(this) - })); + deleteCell: this._deleteCellDelegate.bind(this), + setSelections: this._setSelectionsDelegate.bind(this) + }, this.selectionHandles, this.selectionHandles)); this._decorationsTree.acceptReplace(index, 0, 1, true); this._onDidChangeViewCells.fire({ synchronous: synchronous, splices: [[index, 0, [newCell]]] }); @@ -543,8 +626,9 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD this._localStore.add(newCell); this.undoService.pushElement(new InsertCellEdit(this.uri, index, newCell, { insertCell: this._insertCellDelegate.bind(this), - deleteCell: this._deleteCellDelegate.bind(this) - })); + deleteCell: this._deleteCellDelegate.bind(this), + setSelections: this._setSelectionsDelegate.bind(this) + }, this.selectionHandles, this.selectionHandles)); this._decorationsTree.acceptReplace(index, 0, 1, true); this._onDidChangeViewCells.fire({ synchronous: synchronous, splices: [[index, 0, [newCell]]] }); @@ -552,20 +636,41 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD } deleteCell(index: number, synchronous: boolean) { + const primarySelectionIndex = this.selectionHandles.length ? this._viewCells.indexOf(this.getCellByHandle(this.selectionHandles[0])!) : null; + let viewCell = this._viewCells[index]; this._viewCells.splice(index, 1); this._handleToViewCellMapping.delete(viewCell.handle); this._model.deleteCell(index); + let endSelections: number[] = []; + if (this.selectionHandles.length) { + const primarySelectionHandle = this.selectionHandles[0]; + + if (index === primarySelectionIndex) { + if (primarySelectionIndex < this.length - 1) { + endSelections = [this._viewCells[primarySelectionIndex + 1].handle]; + } else if (primarySelectionIndex === this.length - 1 && this.length > 1) { + endSelections = [this._viewCells[primarySelectionIndex - 1].handle]; + } else { + endSelections = []; + } + } else { + endSelections = [primarySelectionHandle]; + } + } + this.undoService.pushElement(new DeleteCellEdit(this.uri, index, viewCell, { insertCell: this._insertCellDelegate.bind(this), deleteCell: this._deleteCellDelegate.bind(this), createCellViewModel: (cell: NotebookCellTextModel) => { return createCellViewModel(this.instantiationService, this, cell); - } - })); + }, + setSelections: this._setSelectionsDelegate.bind(this) + }, this.selectionHandles, endSelections)); + this.selectionHandles = endSelections; this._decorationsTree.acceptReplace(index, 1, 0, true); @@ -580,19 +685,20 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD } this.viewCells.splice(index, 1); - this._model.deleteCell(index); - this.viewCells!.splice(newIdx, 0, viewCell); - this._model.insertCell(viewCell.model, newIdx); + this._model.moveCellToIdx(index, newIdx); if (pushedToUndoStack) { this.undoService.pushElement(new MoveCellEdit(this.uri, index, newIdx, { moveCell: (fromIndex: number, toIndex: number) => { this.moveCellToIdx(fromIndex, toIndex, true, false); - } - })); + }, + setSelections: this._setSelectionsDelegate.bind(this) + }, this.selectionHandles, this.selectionHandles)); } + this.selectionHandles = this.selectionHandles; + this._onDidChangeViewCells.fire({ synchronous: synchronous, splices: [[index, 1, []]] }); this._onDidChangeViewCells.fire({ synchronous: synchronous, splices: [[newIdx, 0, [viewCell]]] }); diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index a970b2cae0..324084ccfc 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -7,7 +7,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; -import { INotebookTextModel, NotebookCellOutputsSplice, NotebookCellTextModelSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, CellEditType, CellUri, ICellInsertEdit, NotebookCellsChangedEvent, CellKind, IOutput, notebookDocumentMetadataDefaults, diff, ICellDeleteEdit } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookTextModel, NotebookCellOutputsSplice, NotebookCellTextModelSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, CellEditType, CellUri, ICellInsertEdit, NotebookCellsChangedEvent, CellKind, IOutput, notebookDocumentMetadataDefaults, diff, ICellDeleteEdit, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; function compareRangesUsingEnds(a: [number, number], b: [number, number]): number { if (a[1] === b[1]) { @@ -192,6 +192,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel this._onDidChangeContent.fire(); this._onDidModelChangeProxy.fire({ + kind: NotebookCellsChangeType.ModelChange, versionId: this._versionId, changes: [ [ 0, @@ -228,6 +229,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel this._onDidChangeContent.fire(); this._increaseVersionId(); this._onDidModelChangeProxy.fire({ + kind: NotebookCellsChangeType.ModelChange, versionId: this._versionId, changes: [ [ index, @@ -258,7 +260,24 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel this._onDidChangeContent.fire(); this._increaseVersionId(); - this._onDidModelChangeProxy.fire({ versionId: this._versionId, changes: [[index, 1, []]] }); + this._onDidModelChangeProxy.fire({ kind: NotebookCellsChangeType.ModelChange, versionId: this._versionId, changes: [[index, 1, []]] }); + } + + moveCellToIdx(index: number, newIdx: number) { + this.assertIndex(index); + this.assertIndex(newIdx); + + const cells = this.cells.splice(index, 1); + this.cells.splice(newIdx, 0, ...cells); + + this._increaseVersionId(); + this._onDidModelChangeProxy.fire({ kind: NotebookCellsChangeType.Move, versionId: this._versionId, index, newIdx }); + } + + assertIndex(index: number) { + if (index < 0 || index >= this.cells.length) { + throw new Error(`model index out of range ${index}`); + } } // TODO@rebornix should this trigger content change event? @@ -267,6 +286,38 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel cell?.spliceNotebookCellOutputs(splices); } + clearCellOutput(handle: number) { + let cell = this._mapping.get(handle); + if (cell) { + cell.spliceNotebookCellOutputs([ + [0, cell.outputs.length, []] + ]); + + this._increaseVersionId(); + this._onDidModelChangeProxy.fire({ kind: NotebookCellsChangeType.CellClearOutput, versionId: this._versionId, index: this.cells.indexOf(cell) }); + } + } + + changeCellLanguage(handle: number, languageId: string) { + let cell = this._mapping.get(handle); + if (cell) { + cell.language = languageId; + + this._increaseVersionId(); + this._onDidModelChangeProxy.fire({ kind: NotebookCellsChangeType.ChangeLanguage, versionId: this._versionId, index: this.cells.indexOf(cell), language: languageId }); + } + } + + clearAllCellOutputs() { + this.cells.forEach(cell => { + cell.spliceNotebookCellOutputs([ + [0, cell.outputs.length, []] + ]); + }); + this._increaseVersionId(); + this._onDidModelChangeProxy.fire({ kind: NotebookCellsChangeType.CellsClearOutput, versionId: this._versionId }); + } + dispose() { this._onWillDispose.fire(); this._cellListeners.forEach(val => val.dispose()); diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 8c39b34f73..a91523b590 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -36,8 +36,19 @@ export const NOTEBOOK_DISPLAY_ORDER = [ 'text/plain' ]; +export const ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER = [ + 'text/markdown', + 'application/json', + 'text/plain', + 'text/html', + 'image/svg+xml', + 'image/png', + 'image/jpeg', +]; + export const notebookDocumentMetadataDefaults: NotebookDocumentMetadata = { editable: true, + runnable: true, cellEditable: true, cellRunnable: true, hasExecutionOrder: true @@ -45,6 +56,7 @@ export const notebookDocumentMetadataDefaults: NotebookDocumentMetadata = { export interface NotebookDocumentMetadata { editable: boolean; + runnable: boolean; cellEditable: boolean; cellRunnable: boolean; hasExecutionOrder: boolean; @@ -215,11 +227,46 @@ export type NotebookCellsSplice2 = [ IMainCellDto[] ]; -export interface NotebookCellsChangedEvent { +export enum NotebookCellsChangeType { + ModelChange = 1, + Move = 2, + CellClearOutput = 3, + CellsClearOutput = 4, + ChangeLanguage = 5 +} + +export interface NotebookCellsModelChangedEvent { + readonly kind: NotebookCellsChangeType.ModelChange; readonly changes: NotebookCellsSplice2[]; readonly versionId: number; } +export interface NotebookCellsModelMoveEvent { + readonly kind: NotebookCellsChangeType.Move; + readonly index: number; + readonly newIdx: number; + readonly versionId: number; +} + +export interface NotebookCellClearOutputEvent { + readonly kind: NotebookCellsChangeType.CellClearOutput; + readonly index: number; + readonly versionId: number; +} + +export interface NotebookCellsClearOutputEvent { + readonly kind: NotebookCellsChangeType.CellsClearOutput; + readonly versionId: number; +} + +export interface NotebookCellsChangeLanguageEvent { + readonly kind: NotebookCellsChangeType.ChangeLanguage; + readonly versionId: number; + readonly index: number; + readonly language: string; +} + +export type NotebookCellsChangedEvent = NotebookCellsModelChangedEvent | NotebookCellsModelMoveEvent | NotebookCellClearOutputEvent | NotebookCellsClearOutputEvent | NotebookCellsChangeLanguageEvent; export enum CellEditType { Insert = 1, Delete = 2 diff --git a/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts b/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts index 9b06ad9fdf..7040516ba6 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts @@ -155,7 +155,7 @@ suite('NotebookViewModel', () => { [['var e = 5;'], 'javascript', CellKind.Code, [], { editable: false, runnable: false }], ], (editor, viewModel) => { - viewModel.notebookDocument.metadata = { editable: true, cellRunnable: true, cellEditable: true, hasExecutionOrder: true }; + viewModel.notebookDocument.metadata = { editable: true, runnable: true, cellRunnable: true, cellEditable: true, hasExecutionOrder: true }; const defaults = { runState: undefined, @@ -193,7 +193,7 @@ suite('NotebookViewModel', () => { ...defaults }); - viewModel.notebookDocument.metadata = { editable: true, cellRunnable: false, cellEditable: true, hasExecutionOrder: true }; + viewModel.notebookDocument.metadata = { editable: true, runnable: true, cellRunnable: false, cellEditable: true, hasExecutionOrder: true }; assert.deepEqual(viewModel.viewCells[0].getEvaluatedMetadata(viewModel.metadata), { editable: true, @@ -225,7 +225,7 @@ suite('NotebookViewModel', () => { ...defaults }); - viewModel.notebookDocument.metadata = { editable: true, cellRunnable: false, cellEditable: false, hasExecutionOrder: true }; + viewModel.notebookDocument.metadata = { editable: true, runnable: true, cellRunnable: false, cellEditable: false, hasExecutionOrder: true }; assert.deepEqual(viewModel.viewCells[0].getEvaluatedMetadata(viewModel.metadata), { editable: false, @@ -425,4 +425,8 @@ suite('NotebookViewModel Decorations', () => { return original.indexOf(a) >= 0; }), [{ start: 1, deleteCount: 1, toInsert: [2, 6] }]); }); + + test('hidden ranges', function () { + + }); }); diff --git a/src/vs/workbench/contrib/output/browser/output.contribution.ts b/src/vs/workbench/contrib/output/browser/output.contribution.ts index 580880eeef..f3436e3749 100644 --- a/src/vs/workbench/contrib/output/browser/output.contribution.ts +++ b/src/vs/workbench/contrib/output/browser/output.contribution.ts @@ -62,6 +62,7 @@ const toggleOutputActionKeybindings = { const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: OUTPUT_VIEW_ID, name: nls.localize('output', "Output"), + icon: Codicon.output.classNames, order: 1, ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [OUTPUT_VIEW_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), storageId: OUTPUT_VIEW_ID, diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index 8c944ea650..d8c1856b13 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts @@ -1111,10 +1111,10 @@ class AccessibilityProvider implements IListAccessibilityProvider .settings-body .settings-tree-container .monaco-list-row .mouseover .setting-toolbar-container > .monaco-toolbar .codicon-more, -.settings-editor > .settings-body .settings-tree-container .monaco-list-row .setting-item-contents.focused .setting-toolbar-container > .monaco-toolbar .codicon-more, -.settings-editor > .settings-body .settings-tree-container .monaco-list-row .setting-toolbar-container:hover > .monaco-toolbar .codicon-more, -.settings-editor > .settings-body .settings-tree-container .monaco-list-row .setting-toolbar-container > .monaco-toolbar .active .codicon-more { +.settings-editor > .settings-body .settings-tree-container .monaco-list-row .mouseover .setting-toolbar-container > .monaco-toolbar .codicon, +.settings-editor > .settings-body .settings-tree-container .monaco-list-row .setting-item-contents.focused .setting-toolbar-container > .monaco-toolbar .codicon, +.settings-editor > .settings-body .settings-tree-container .monaco-list-row .setting-toolbar-container:hover > .monaco-toolbar .codicon, +.settings-editor > .settings-body .settings-tree-container .monaco-list-row .setting-toolbar-container > .monaco-toolbar .active .codicon { opacity: 1; } -.settings-editor > .settings-body .settings-tree-container .setting-toolbar-container > .monaco-toolbar .codicon-more { +.settings-editor > .settings-body .settings-tree-container .setting-toolbar-container > .monaco-toolbar .codicon { opacity: 0; transition: opacity .3s; width: 22px; @@ -414,21 +414,21 @@ -moz-appearance: textfield !important; } -.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown * { +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-markdown * { margin: 0px; } -.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown a:focus { +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-markdown a:focus { outline: 1px solid -webkit-focus-ring-color; outline-offset: -1px; text-decoration: underline; } -.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown a:hover { +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-markdown a:hover { text-decoration: underline; } -.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown code { +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-markdown code { line-height: 15px; /** For some reason, this is needed, otherwise will take up 20px height */ font-family: var(--monaco-monospace-font); diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css b/src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css index da23fbabe0..2a5bf7d699 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css @@ -89,6 +89,7 @@ } .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .monaco-text-button.setting-list-addButton { + display: inline-block; margin-top: 4px; margin-right: 10px; } diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts index 20c5596d16..3c626cfc91 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts @@ -609,7 +609,7 @@ class PreferencesRenderersController extends Disposable { const message = getErrorMessage(err).trim(); if (message && message !== 'Error') { // "Error" = any generic network error - this.telemetryService.publicLog('defaultSettings.searchError', { message }, true); + this.telemetryService.publicLogError('defaultSettings.searchError', { message }); this.logService.info('Setting search error: ' + message); } return undefined; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 9d17f235dd..c5f41febe5 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -501,14 +501,14 @@ export class SettingsEditor2 extends BaseEditor { private onDidClickSetting(evt: ISettingLinkClickEvent, recursed?: boolean): void { const elements = this.currentSettingsModel.getElementsByName(evt.targetKey); if (elements && elements[0]) { - let sourceTop = this.settingsTree.getRelativeTop(evt.source); - if (typeof sourceTop !== 'number') { - return; - } - - if (sourceTop < 0) { + let sourceTop = 0.5; + try { + const _sourceTop = this.settingsTree.getRelativeTop(evt.source); + if (_sourceTop !== null) { + sourceTop = _sourceTop; + } + } catch { // e.g. clicked a searched element, now the search has been cleared - sourceTop = 0.5; } this.settingsTree.reveal(elements[0], sourceTop); @@ -1366,7 +1366,7 @@ export class SettingsEditor2 extends BaseEditor { const message = getErrorMessage(err).trim(); if (message && message !== 'Error') { // "Error" = any generic network error - this.telemetryService.publicLog('settingsEditor.searchError', { message }, true); + this.telemetryService.publicLogError('settingsEditor.searchError', { message }); this.logService.info('Setting search error: ' + message); } return null; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 2c65a82cb7..7a75da327e 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -26,7 +26,7 @@ import { Color, RGBA } from 'vs/base/common/color'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { Disposable, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; import { isIOS } from 'vs/base/common/platform'; import { ISpliceable } from 'vs/base/common/sequence'; import { escapeRegExpCharacters, startsWith } from 'vs/base/common/strings'; @@ -193,7 +193,7 @@ function getFlatSettings(settingsGroups: ISettingsGroup[]) { } interface IDisposableTemplate { - toDispose: IDisposable[]; + toDispose: DisposableStore; } interface ISettingItemTemplate extends IDisposableTemplate { @@ -209,7 +209,7 @@ interface ISettingItemTemplate extends IDisposableTemplate { otherOverridesElement: HTMLElement; syncIgnoredElement: HTMLElement; toolbar: ToolBar; - elementDisposables: IDisposable[]; + elementDisposables: DisposableStore; } interface ISettingBoolItemTemplate extends ISettingItemTemplate { @@ -370,14 +370,14 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre const deprecationWarningElement = DOM.append(container, $('.setting-item-deprecation-message')); - const toDispose: IDisposable[] = []; + const toDispose = new DisposableStore(); const toolbarContainer = DOM.append(container, $('.setting-toolbar-container')); const toolbar = this.renderSettingToolbar(toolbarContainer); const template: ISettingItemTemplate = { toDispose, - elementDisposables: [], + elementDisposables: new DisposableStore(), containerElement: container, categoryElement, @@ -391,17 +391,17 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre }; // Prevent clicks from being handled by list - toDispose.push(DOM.addDisposableListener(controlElement, 'mousedown', e => e.stopPropagation())); + toDispose.add(DOM.addDisposableListener(controlElement, 'mousedown', e => e.stopPropagation())); - toDispose.push(DOM.addDisposableListener(titleElement, DOM.EventType.MOUSE_ENTER, e => container.classList.add('mouseover'))); - toDispose.push(DOM.addDisposableListener(titleElement, DOM.EventType.MOUSE_LEAVE, e => container.classList.remove('mouseover'))); + toDispose.add(DOM.addDisposableListener(titleElement, DOM.EventType.MOUSE_ENTER, e => container.classList.add('mouseover'))); + toDispose.add(DOM.addDisposableListener(titleElement, DOM.EventType.MOUSE_LEAVE, e => container.classList.remove('mouseover'))); return template; } protected addSettingElementFocusHandler(template: ISettingItemTemplate): void { const focusTracker = DOM.trackFocus(template.containerElement); - template.toDispose.push(focusTracker); + template.toDispose.add(focusTracker); focusTracker.onDidBlur(() => { if (template.containerElement.classList.contains('focused')) { template.containerElement.classList.remove('focused'); @@ -431,12 +431,13 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre } private fixToolbarIcon(toolbar: ToolBar): void { - const button = toolbar.getContainer().querySelector('.codicon-more'); + const button = toolbar.getContainer().querySelector('.codicon-toolbar-more'); if (button) { (button).tabIndex = -1; // change icon from ellipsis to gear (button).classList.add('codicon-gear'); + (button).classList.remove('codicon-toolbar-more'); } } @@ -445,7 +446,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre template.context = element; template.toolbar.context = element; const actions = this.disposableActionFactory(element.setting); - template.elementDisposables?.push(...actions); + actions.forEach(a => template.elementDisposables?.add(a)); template.toolbar.setActions([], [...this.settingActions, ...actions])(); this.fixToolbarIcon(template.toolbar); @@ -465,8 +466,8 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre template.descriptionElement.innerHTML = ''; if (element.setting.descriptionIsMarkdown) { const disposables = new DisposableStore(); - template.toDispose.push(disposables); - const renderedDescription = this.renderDescriptionMarkdown(element, element.description, disposables); + template.toDispose.add(disposables); + const renderedDescription = this.renderSettingMarkdown(element, element.description, disposables); template.descriptionElement.appendChild(renderedDescription); } else { template.descriptionElement.innerText = element.description; @@ -495,7 +496,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre DOM.append(template.otherOverridesElement, $('span', undefined, ')')); } - template.elementDisposables.push( + template.elementDisposables.add( DOM.addStandardDisposableListener(view, DOM.EventType.CLICK, (e: IMouseEvent) => { this._onDidClickOverrideElement.fire({ targetKey: element.setting.key, @@ -509,7 +510,14 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre const onChange = (value: any) => this._onDidChangeSetting.fire({ key: element.setting.key, value, type: template.context!.valueType }); const deprecationText = element.setting.deprecationMessage || ''; - template.deprecationWarningElement.innerText = deprecationText; + if (deprecationText && element.setting.deprecationMessageIsMarkdown) { + const disposables = new DisposableStore(); + template.elementDisposables.add(disposables); + template.deprecationWarningElement.innerHTML = ''; + template.deprecationWarningElement.appendChild(this.renderSettingMarkdown(element, element.setting.deprecationMessage!, template.elementDisposables)); + } else { + template.deprecationWarningElement.innerText = deprecationText; + } DOM.toggleClass(template.containerElement, 'is-deprecated', !!deprecationText); this.renderValue(element, template, onChange); @@ -518,12 +526,12 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre template.syncIgnoredElement.style.display = this.ignoredSettings.includes(element.setting.key) ? 'inline' : 'none'; }; update(); - template.elementDisposables.push(this.onDidChangeIgnoredSettings(() => { + template.elementDisposables.add(this.onDidChangeIgnoredSettings(() => { update(); })); } - private renderDescriptionMarkdown(element: SettingsTreeSettingElement, text: string, disposeables: DisposableStore): HTMLElement { + private renderSettingMarkdown(element: SettingsTreeSettingElement, text: string, disposeables: DisposableStore): HTMLElement { // Rewrite `#editor.fontSize#` to link format text = fixSettingLinks(text); @@ -544,7 +552,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre } }); - renderedMarkdown.classList.add('setting-item-description-markdown'); + renderedMarkdown.classList.add('setting-item-markdown'); cleanRenderedMarkdown(renderedMarkdown); return renderedMarkdown; } @@ -614,7 +622,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre disposeElement(_element: ITreeNode, _index: number, template: IDisposableTemplate, _height: number | undefined): void { if ((template as ISettingItemTemplate).elementDisposables) { - dispose((template as ISettingItemTemplate).elementDisposables); + (template as ISettingItemTemplate).elementDisposables.clear(); } } } @@ -625,10 +633,9 @@ export class SettingGroupRenderer implements ITreeRenderer { + toDispose.add(button); + toDispose.add(button.onDidClick(() => { if (template.context) { this._commandService.executeCommand('workbench.extensions.action.showExtensionsWithIds', template.context.extensionIds); } })); button.label = localize('newExtensionsButtonLabel', "Show matching extensions"); button.element.classList.add('settings-new-extensions-button'); - toDispose.push(attachButtonStyler(button, this._themeService)); + toDispose.add(attachButtonStyler(button, this._themeService)); const template: ISettingNewExtensionsTemplate = { button, @@ -698,12 +705,12 @@ export class SettingComplexRenderer extends AbstractSettingRenderer implements I const common = this.renderCommonTemplate(null, container, 'complex'); const openSettingsButton = new Button(common.controlElement, { title: true, buttonBackground: undefined, buttonHoverBackground: undefined }); - common.toDispose.push(openSettingsButton); - common.toDispose.push(openSettingsButton.onDidClick(() => template.onChange!())); + common.toDispose.add(openSettingsButton); + common.toDispose.add(openSettingsButton.onDidClick(() => template.onChange!())); openSettingsButton.label = localize('editInSettingsJson', "Edit in settings.json"); openSettingsButton.element.classList.add('edit-in-settings-button'); - common.toDispose.push(attachButtonStyler(openSettingsButton, this._themeService, { + common.toDispose.add(attachButtonStyler(openSettingsButton, this._themeService, { buttonBackground: Color.transparent.toString(), buttonHoverBackground: Color.transparent.toString(), buttonForeground: 'foreground' @@ -755,7 +762,7 @@ export class SettingArrayRenderer extends AbstractSettingRenderer implements ITr const listWidget = this._instantiationService.createInstance(ListSettingWidget, common.controlElement); listWidget.domNode.classList.add(AbstractSettingRenderer.CONTROL_CLASS); - common.toDispose.push(listWidget); + common.toDispose.add(listWidget); const template: ISettingListItemTemplate = { ...common, @@ -765,7 +772,7 @@ export class SettingArrayRenderer extends AbstractSettingRenderer implements ITr this.addSettingElementFocusHandler(template); - common.toDispose.push( + common.toDispose.add( listWidget.onDidChangeList(e => { const newList = this.computeNewList(template, e); this.onDidChangeList(template, newList); @@ -861,7 +868,7 @@ export class SettingExcludeRenderer extends AbstractSettingRenderer implements I const excludeWidget = this._instantiationService.createInstance(ExcludeSettingWidget, common.controlElement); excludeWidget.domNode.classList.add(AbstractSettingRenderer.CONTROL_CLASS); - common.toDispose.push(excludeWidget); + common.toDispose.add(excludeWidget); const template: ISettingExcludeItemTemplate = { ...common, @@ -870,7 +877,7 @@ export class SettingExcludeRenderer extends AbstractSettingRenderer implements I this.addSettingElementFocusHandler(template); - common.toDispose.push(excludeWidget.onDidChangeList(e => this.onDidChangeExclude(template, e))); + common.toDispose.add(excludeWidget.onDidChangeList(e => this.onDidChangeExclude(template, e))); return template; } @@ -937,19 +944,19 @@ export class SettingTextRenderer extends AbstractSettingRenderer implements ITre const validationErrorMessageElement = DOM.append(common.containerElement, $('.setting-item-validation-message')); const inputBox = new InputBox(common.controlElement, this._contextViewService); - common.toDispose.push(inputBox); - common.toDispose.push(attachInputBoxStyler(inputBox, this._themeService, { + common.toDispose.add(inputBox); + common.toDispose.add(attachInputBoxStyler(inputBox, this._themeService, { inputBackground: settingsTextInputBackground, inputForeground: settingsTextInputForeground, inputBorder: settingsTextInputBorder })); - common.toDispose.push( + common.toDispose.add( inputBox.onDidChange(e => { if (template.onChange) { template.onChange(e); } })); - common.toDispose.push(inputBox); + common.toDispose.add(inputBox); inputBox.inputElement.classList.add(AbstractSettingRenderer.CONTROL_CLASS); const template: ISettingTextItemTemplate = { @@ -988,8 +995,8 @@ export class SettingEnumRenderer extends AbstractSettingRenderer implements ITre useCustomDrawn: !(isIOS && BrowserFeatures.pointerEvents) }); - common.toDispose.push(selectBox); - common.toDispose.push(attachSelectBoxStyler(selectBox, this._themeService, { + common.toDispose.add(selectBox); + common.toDispose.add(attachSelectBoxStyler(selectBox, this._themeService, { selectBackground: settingsSelectBackground, selectForeground: settingsSelectForeground, selectBorder: settingsSelectBorder, @@ -1001,7 +1008,7 @@ export class SettingEnumRenderer extends AbstractSettingRenderer implements ITre selectElement.classList.add(AbstractSettingRenderer.CONTROL_CLASS); } - common.toDispose.push( + common.toDispose.add( selectBox.onDidSelect(e => { if (template.onChange) { template.onChange(e.index); @@ -1030,7 +1037,7 @@ export class SettingEnumRenderer extends AbstractSettingRenderer implements ITre const enumDescriptionsAreMarkdown = dataElement.setting.enumDescriptionsAreMarkdown; const disposables = new DisposableStore(); - template.toDispose.push(disposables); + template.toDispose.add(disposables); const displayOptions = dataElement.setting.enum! .map(String) @@ -1077,19 +1084,19 @@ export class SettingNumberRenderer extends AbstractSettingRenderer implements IT const validationErrorMessageElement = DOM.append(common.containerElement, $('.setting-item-validation-message')); const inputBox = new InputBox(common.controlElement, this._contextViewService, { type: 'number' }); - common.toDispose.push(inputBox); - common.toDispose.push(attachInputBoxStyler(inputBox, this._themeService, { + common.toDispose.add(inputBox); + common.toDispose.add(attachInputBoxStyler(inputBox, this._themeService, { inputBackground: settingsNumberInputBackground, inputForeground: settingsNumberInputForeground, inputBorder: settingsNumberInputBorder })); - common.toDispose.push( + common.toDispose.add( inputBox.onDidChange(e => { if (template.onChange) { template.onChange(e); } })); - common.toDispose.push(inputBox); + common.toDispose.add(inputBox); inputBox.inputElement.classList.add(AbstractSettingRenderer.CONTROL_CLASS); const template: ISettingNumberItemTemplate = { @@ -1183,8 +1190,8 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre toDispose.add(toolbar); const template: ISettingBoolItemTemplate = { - toDispose: [toDispose], - elementDisposables: [], + toDispose, + elementDisposables: new DisposableStore(), containerElement: container, categoryElement, @@ -1626,7 +1633,7 @@ export class SettingsTree extends ObjectTree { const focusBorderColor = theme.getColor(focusBorder); if (focusBorderColor) { - collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown a:focus { outline-color: ${focusBorderColor} }`); + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-markdown a:focus { outline-color: ${focusBorderColor} }`); } })); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts index 5c37b43f5e..e675e2599c 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts @@ -69,16 +69,16 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) = const link = theme.getColor(textLinkForeground); if (link) { - collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown a { color: ${link}; }`); - collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown a > code { color: ${link}; }`); + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-markdown a { color: ${link}; }`); + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-markdown a > code { color: ${link}; }`); collector.addRule(`.monaco-select-box-dropdown-container > .select-box-details-pane > .select-box-description-markdown a { color: ${link}; }`); collector.addRule(`.monaco-select-box-dropdown-container > .select-box-details-pane > .select-box-description-markdown a > code { color: ${link}; }`); } const activeLink = theme.getColor(textLinkActiveForeground); if (activeLink) { - collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown a:hover, .settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown a:active { color: ${activeLink}; }`); - collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown a:hover > code, .settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown a:active > code { color: ${activeLink}; }`); + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-markdown a:hover, .settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-markdown a:active { color: ${activeLink}; }`); + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-markdown a:hover > code, .settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-markdown a:active > code { color: ${activeLink}; }`); collector.addRule(`.monaco-select-box-dropdown-container > .select-box-details-pane > .select-box-description-markdown a:hover, .monaco-select-box-dropdown-container > .select-box-details-pane > .select-box-description-markdown a:active { color: ${activeLink}; }`); collector.addRule(`.monaco-select-box-dropdown-container > .select-box-details-pane > .select-box-description-markdown a:hover > code, .monaco-select-box-dropdown-container > .select-box-details-pane > .select-box-description-markdown a:active > code { color: ${activeLink}; }`); } @@ -126,7 +126,7 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) = const codeTextForegroundColor = theme.getColor(textPreformatForeground); if (codeTextForegroundColor) { - collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description-markdown code { color: ${codeTextForegroundColor} }`); + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-markdown code { color: ${codeTextForegroundColor} }`); collector.addRule(`.monaco-select-box-dropdown-container > .select-box-details-pane > .select-box-description-markdown code { color: ${codeTextForegroundColor} }`); } diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index cee591baea..9131ce5db4 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -693,7 +693,7 @@ namespace LabelTunnelAction { if (context instanceof TunnelItem) { const remoteExplorerService = accessor.get(IRemoteExplorerService); remoteExplorerService.setEditable(context, { - onFinish: (value, success) => { + onFinish: async (value, success) => { if (success) { remoteExplorerService.tunnelModel.name(context.remoteHost, context.remotePort, value); } @@ -752,7 +752,7 @@ namespace ForwardPortAction { remoteExplorerService.forward({ host: arg.remoteHost, port: arg.remotePort }).then(tunnel => error(notificationService, tunnel, arg.remoteHost, arg.remotePort)); } else { remoteExplorerService.setEditable(undefined, { - onFinish: (value, success) => { + onFinish: async (value, success) => { let parsed: { host: string, port: number } | undefined; if (success && (parsed = parseInput(value))) { remoteExplorerService.forward({ host: parsed.host, port: parsed.port }).then(tunnel => error(notificationService, tunnel, parsed!.host, parsed!.port)); diff --git a/src/vs/workbench/contrib/scm/browser/activity.ts b/src/vs/workbench/contrib/scm/browser/activity.ts index ae3bd442d0..85345ff90c 100644 --- a/src/vs/workbench/contrib/scm/browser/activity.ts +++ b/src/vs/workbench/contrib/scm/browser/activity.ts @@ -184,7 +184,7 @@ export class SCMStatusController implements IWorkbenchContribution { if (count > 0) { const badge = new NumberBadge(count, num => localize('scmPendingChangesBadge', '{0} pending changes', num)); - this.badgeDisposable.value = this.activityService.showActivity(VIEWLET_ID, badge, 'scm-viewlet-label'); + this.badgeDisposable.value = this.activityService.showViewContainerActivity(VIEWLET_ID, { badge, clazz: 'scm-viewlet-label' }); } else { this.badgeDisposable.clear(); } diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index d85d8f7de7..175d71ea29 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -300,7 +300,7 @@ class DirtyDiffWidget extends PeekViewWidget { fixedOverflowWidgets: true, minimap: { enabled: false }, renderSideBySide: false, - readOnly: true, + readOnly: false, ignoreTrimWhitespace: false }; @@ -357,6 +357,10 @@ class DirtyDiffWidget extends PeekViewWidget { protected revealLine(lineNumber: number) { this.editor.revealLineInCenterIfOutsideViewport(lineNumber, ScrollType.Smooth); } + + hasFocus(): boolean { + return this.diffEditor.hasTextFocus(); + } } export class ShowPreviousChangeAction extends EditorAction { @@ -690,9 +694,10 @@ export class DirtyDiffController extends Disposable implements IEditorContributi } private onDidModelChange(splices: ISplice[]): void { - if (!this.model) { + if (!this.model || !this.widget || this.widget.hasFocus()) { return; } + for (const splice of splices) { if (splice.start <= this.currentIndex) { if (this.currentIndex < splice.start + splice.deleteCount) { diff --git a/src/vs/workbench/contrib/scm/browser/mainPane.ts b/src/vs/workbench/contrib/scm/browser/mainPane.ts index 678085d004..faf861e976 100644 --- a/src/vs/workbench/contrib/scm/browser/mainPane.ts +++ b/src/vs/workbench/contrib/scm/browser/mainPane.ts @@ -249,6 +249,10 @@ export class MainPane extends ViewPane { this.updateBodySize(); } + focus(): void { + this.list.domFocus(); + } + protected layoutBody(height: number, width: number): void { super.layoutBody(height, width); this.list.layout(height, width); diff --git a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts index 9051be3821..bb31c51814 100644 --- a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts +++ b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts @@ -865,13 +865,7 @@ export class RepositoryPane extends ViewPane { const actionRunner = new RepositoryPaneActionRunner(() => this.getSelectedResources()); this._register(actionRunner); - this._register(actionRunner.onDidRun(() => { - if (this.repository.input.visible && this.inputEditor.hasWidgetFocus()) { - return; - } - - this.tree.domFocus(); - })); + this._register(actionRunner.onDidBeforeRun(() => this.tree.domFocus())); const renderers = [ new ResourceGroupRenderer(actionViewItemProvider, this.themeService, this.menus), @@ -1083,7 +1077,7 @@ export class RepositoryPane extends ViewPane { } const actionRunner = new RepositoryPaneActionRunner(() => this.getSelectedResources()); - actionRunner.onDidRun(() => this.tree.domFocus()); + actionRunner.onDidBeforeRun(() => this.tree.domFocus()); this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index ee730795ab..a58bc7837b 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -588,12 +588,22 @@ MenuRegistry.appendMenuItem(MenuId.MenubarEditMenu, { order: 2 }); -KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({ - id: Constants.ToggleCaseSensitiveCommandId, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(Constants.SearchViewFocusedKey, Constants.FileMatchOrFolderMatchFocusKey.toNegated()), - handler: toggleCaseSensitiveCommand -}, ToggleCaseSensitiveKeybinding)); +if (platform.isMacintosh) { + // Register this with a more restrictive `when` on mac to avoid conflict with "copy path" + KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({ + id: Constants.ToggleCaseSensitiveCommandId, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(Constants.SearchViewFocusedKey, Constants.FileMatchOrFolderMatchFocusKey.toNegated()), + handler: toggleCaseSensitiveCommand + }, ToggleCaseSensitiveKeybinding)); +} else { + KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({ + id: Constants.ToggleCaseSensitiveCommandId, + weight: KeybindingWeight.WorkbenchContrib, + when: Constants.SearchViewFocusedKey, + handler: toggleCaseSensitiveCommand + }, ToggleCaseSensitiveKeybinding)); +} KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({ id: Constants.ToggleWholeWordCommandId, diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index 37b01f1dc2..c395dff9df 100644 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -765,7 +765,16 @@ export class ReplaceAction extends AbstractSearchAndReplaceAction { } } -export const copyPathCommand: ICommandHandler = async (accessor, fileMatch: FileMatch | FolderMatchWithResource) => { +export const copyPathCommand: ICommandHandler = async (accessor, fileMatch: FileMatch | FolderMatchWithResource | undefined) => { + if (!fileMatch) { + const selection = getSelectedRow(accessor); + if (!(selection instanceof FileMatch || selection instanceof FolderMatchWithResource)) { + return; + } + + fileMatch = selection; + } + const clipboardService = accessor.get(IClipboardService); const labelService = accessor.get(ILabelService); @@ -832,7 +841,16 @@ function folderMatchToString(folderMatch: FolderMatchWithResource | FolderMatch, } const maxClipboardMatches = 1e4; -export const copyMatchCommand: ICommandHandler = async (accessor, match: RenderableMatch) => { +export const copyMatchCommand: ICommandHandler = async (accessor, match: RenderableMatch | undefined) => { + if (!match) { + const selection = getSelectedRow(accessor); + if (!selection) { + return; + } + + match = selection; + } + const clipboardService = accessor.get(IClipboardService); const labelService = accessor.get(ILabelService); @@ -865,6 +883,12 @@ function allFolderMatchesToString(folderMatches: Array { const viewsService = accessor.get(IViewsService); const clipboardService = accessor.get(IClipboardService); diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 1ed6bde769..6c30989d8b 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -1657,7 +1657,7 @@ export class SearchView extends ViewPane { revealIfVisible: true } }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(editor => { - if (editor && element instanceof Match && preserveFocus) { + if (element instanceof Match && preserveFocus && isCodeEditor(editor)) { this.viewModel.searchResult.rangeHighlightDecorations.highlightRange( (editor.getControl()).getModel()!, element.range() diff --git a/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts b/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts index 5d0130e31f..5a891a2976 100644 --- a/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts @@ -144,7 +144,7 @@ export class SymbolsQuickAccessProvider extends PickerQuickAccessProvider inputs.delete(modelUri.toString())); + inputs.set(cacheKey, input); + input.onDispose(() => inputs.delete(cacheKey)); return input; }; diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 89b503c7c2..01e73822a7 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -84,6 +84,7 @@ import { isWorkspaceFolder, TaskQuickPickEntry, QUICKOPEN_DETAIL_CONFIG, TaskQui const QUICKOPEN_HISTORY_LIMIT_CONFIG = 'task.quickOpen.history'; const PROBLEM_MATCHER_NEVER_CONFIG = 'task.problemMatchers.neverPrompt'; +const USE_SLOW_PICKER = 'task.quickOpen.showAll'; export namespace ConfigureTaskAction { export const ID = 'workbench.action.tasks.configureTaskRunner'; @@ -599,7 +600,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer let result: Task[] = []; map.forEach((tasks) => { for (let task of tasks) { - if (ContributedTask.is(task) && task.defines.type === filter.type) { + if (ContributedTask.is(task) && ((task.defines.type === filter.type) || (task._source.label === filter.type))) { result.push(task); } else if (CustomTask.is(task)) { if (task.type === filter.type) { @@ -1402,12 +1403,52 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } private executeTask(task: Task, resolver: ITaskResolver): Promise { - return ProblemMatcherRegistry.onReady().then(() => { - return this.editorService.saveAll().then(() => { // make sure all dirty editors are saved + enum SaveBeforeRunConfigOptions { + Always = 'always', + Never = 'never', + Prompt = 'prompt' + } + + const saveBeforeRunTaskConfig: SaveBeforeRunConfigOptions = this.configurationService.getValue('task.saveBeforeRun'); + + const execTask = async (task: Task, resolver: ITaskResolver): Promise => { + return ProblemMatcherRegistry.onReady().then(() => { let executeResult = this.getTaskSystem().run(task, resolver); return this.handleExecuteResult(executeResult); }); - }); + }; + + const saveAllEditorsAndExecTask = async (task: Task, resolver: ITaskResolver): Promise => { + return this.editorService.saveAll().then(() => { + return execTask(task, resolver); + }); + }; + + const promptAsk = async (task: Task, resolver: ITaskResolver): Promise => { + const dialogOptions = await this.dialogService.show( + Severity.Info, + nls.localize('TaskSystem.saveBeforeRun.prompt.title', 'Save all editors?'), + [nls.localize('saveBeforeRun.save', 'Save'), nls.localize('saveBeforeRun.dontSave', 'Don\'t save')], + { + detail: nls.localize('detail', "Do you want to save all editors before running the task?"), + cancelId: 1 + } + ); + + if (dialogOptions.choice === 0) { + return saveAllEditorsAndExecTask(task, resolver); + } else { + return execTask(task, resolver); + } + }; + + if (saveBeforeRunTaskConfig === SaveBeforeRunConfigOptions.Never) { + return execTask(task, resolver); + } else if (saveBeforeRunTaskConfig === SaveBeforeRunConfigOptions.Prompt) { + return promptAsk(task, resolver); + } else { + return saveAllEditorsAndExecTask(task, resolver); + } } private async handleExecuteResult(executeResult: ITaskExecuteResult): Promise { @@ -1533,7 +1574,17 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer for (const [handle, provider] of this._providers) { if ((type === undefined) || (type === this._providerTypes.get(handle))) { counter++; - provider.provideTasks(validTypes).then(done, error); + provider.provideTasks(validTypes).then((taskSet: TaskSet) => { + // Check that the tasks provided are of the correct type + for (const task of taskSet.tasks) { + if (task.type !== this._providerTypes.get(handle)) { + this._outputChannel.append(nls.localize('unexpectedTaskType', "The task provider for \"{0}\" tasks unexpectedly provided a task of type \"{1}\".\n", this._providerTypes.get(handle), task.type)); + this.showOutput(); + break; + } + } + return done(taskSet); + }, error); } } } else { @@ -2088,7 +2139,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return this.configurationService.getValue(QUICKOPEN_DETAIL_CONFIG); } - private createTaskQuickPickEntries(tasks: Task[], group: boolean = false, sort: boolean = false, selectedEntry?: TaskQuickPickEntry, includeRecents: boolean = true): TaskQuickPickEntry[] { + private async createTaskQuickPickEntries(tasks: Task[], group: boolean = false, sort: boolean = false, selectedEntry?: TaskQuickPickEntry, includeRecents: boolean = true): Promise { let count: { [key: string]: number; } = {}; if (tasks === undefined || tasks === null || tasks.length === 0) { return []; @@ -2124,26 +2175,31 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (tasks.length === 1) { entries.push(TaskQuickPickEntry(tasks[0])); } else { - let recentlyUsedTasks = this.getRecentlyUsedTasks(); + let recentlyUsedTasks = await this.readRecentTasks(); let recent: Task[] = []; + let recentSet: Set = new Set(); let configured: Task[] = []; let detected: Task[] = []; let taskMap: IStringDictionary = Object.create(null); tasks.forEach(task => { - let key = task.getRecentlyUsedKey(); + let key = task.getCommonTaskId(); if (key) { taskMap[key] = task; } }); - recentlyUsedTasks.keys().reverse().forEach(key => { - let task = taskMap[key]; - if (task) { - recent.push(task); + recentlyUsedTasks.reverse().forEach(recentTask => { + const key = recentTask.getCommonTaskId(); + if (key) { + recentSet.add(key); + let task = taskMap[key]; + if (task) { + recent.push(task); + } } }); for (let task of tasks) { - let key = task.getRecentlyUsedKey(); - if (!key || !recentlyUsedTasks.has(key)) { + let key = task.getCommonTaskId(); + if (!key || !recentSet.has(key)) { if ((task._source.kind === TaskSourceKind.Workspace) || (task._source.kind === TaskSourceKind.User)) { configured.push(task); } else { @@ -2172,7 +2228,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } private async showTwoLevelQuickPick(placeHolder: string, defaultEntry?: TaskQuickPickEntry) { - return TaskQuickPick.show(this, this.configurationService, this.quickInputService, placeHolder, defaultEntry); + return TaskQuickPick.show(this, this.configurationService, this.quickInputService, this.notificationService, placeHolder, defaultEntry); } private async showQuickPick(tasks: Promise | Task[], placeHolder: string, defaultEntry?: TaskQuickPickEntry, group: boolean = false, sort: boolean = false, selectedEntry?: TaskQuickPickEntry, additionalEntries?: TaskQuickPickEntry[]): Promise { @@ -2215,7 +2271,6 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer const picker: IQuickPick = this.quickInputService.createQuickPick(); picker.placeholder = placeHolder; picker.matchOnDescription = true; - picker.ignoreFocusOut = true; picker.onDidTriggerItemButton(context => { let task = context.item.task; @@ -2327,26 +2382,76 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } } + private tasksAndGroupedTasks(filter?: TaskFilter): { tasks: Promise, grouped: Promise } { + if (!this.versionAndEngineCompatible(filter)) { + return { tasks: Promise.resolve([]), grouped: Promise.resolve(new TaskMap()) }; + } + const grouped = this.getGroupedTasks(filter ? filter.type : undefined); + const tasks = grouped.then((map) => { + if (!filter || !filter.type) { + return map.all(); + } + let result: Task[] = []; + map.forEach((tasks) => { + for (let task of tasks) { + if (ContributedTask.is(task) && task.defines.type === filter.type) { + result.push(task); + } else if (CustomTask.is(task)) { + if (task.type === filter.type) { + result.push(task); + } else { + let customizes = task.customizes(); + if (customizes && customizes.type === filter.type) { + result.push(task); + } + } + } + } + }); + return result; + }); + return { tasks, grouped }; + } + private doRunTaskCommand(tasks?: Task[]): void { - this.showIgnoredFoldersMessage().then(() => { - this.showTwoLevelQuickPick( - nls.localize('TaskService.pickRunTask', 'Select the task to run'), - { - label: nls.localize('TaskService.noEntryToRun', 'No configured tasks. Configure Tasks...'), - task: null - }). - then((task) => { - if (task === undefined) { - return; - } - if (task === null) { - this.runConfigureTasks(); - } else { - this.run(task, { attachProblemMatcher: true }, TaskRunSource.User).then(undefined, reason => { - // eat the error, it has already been surfaced to the user and we don't care about it here - }); - } + const pickThen = (task: Task | undefined | null) => { + if (task === undefined) { + return; + } + if (task === null) { + this.runConfigureTasks(); + } else { + this.run(task, { attachProblemMatcher: true }, TaskRunSource.User).then(undefined, reason => { + // eat the error, it has already been surfaced to the user and we don't care about it here }); + } + }; + + const placeholder = nls.localize('TaskService.pickRunTask', 'Select the task to run'); + + this.showIgnoredFoldersMessage().then(() => { + if (this.configurationService.getValue(USE_SLOW_PICKER)) { + let taskResult: { tasks: Promise, grouped: Promise } | undefined = undefined; + if (!tasks) { + taskResult = this.tasksAndGroupedTasks(); + } + this.showQuickPick(tasks ? tasks : taskResult!.tasks, placeholder, + { + label: nls.localize('TaskService.noEntryToRunSlow', 'No task to run found. Configure Tasks...'), + task: null + }, + true). + then((entry) => { + return pickThen(entry ? entry.task : undefined); + }); + } else { + this.showTwoLevelQuickPick(placeholder, + { + label: nls.localize('TaskService.noEntryToRun', 'No configured tasks. Configure Tasks...'), + task: null + }). + then(pickThen); + } }); } diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index ad66347fbf..7a78dc502d 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -376,6 +376,25 @@ configurationRegistry.registerConfiguration({ type: 'boolean', description: nls.localize('task.quickOpen.skip', "Controls whether the task quick pick is skipped when there is only one task to pick from."), default: false - } + }, + 'task.quickOpen.showAll': { + type: 'boolean', + description: nls.localize('task.quickOpen.showAll', "Causes the Tasks: Run Task command to use the slower \"show all\" behavior instead of the faster two level picker where tasks are grouped by provider."), + default: false + }, + 'task.saveBeforeRun': { + markdownDescription: nls.localize( + 'task.saveBeforeRun', + 'Save all dirty editors before running a task.' + ), + type: 'string', + enum: ['always', 'never', 'prompt'], + enumDescriptions: [ + nls.localize('task.saveBeforeRun.always', 'Always saves all editors before running.'), + nls.localize('task.saveBeforeRun.never', 'Never saves editors before running.'), + nls.localize('task.SaveBeforeRun.prompt', 'Prompts whether to save editors before running.'), + ], + default: 'always', + }, } }); diff --git a/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts b/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts index fa747fc63f..da38a1e432 100644 --- a/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts +++ b/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import * as Objects from 'vs/base/common/objects'; -import { Task, ContributedTask, CustomTask, ConfiguringTask, TaskSorter } from 'vs/workbench/contrib/tasks/common/tasks'; +import { Task, ContributedTask, CustomTask, ConfiguringTask, TaskSorter, KeyedTaskIdentifier } from 'vs/workbench/contrib/tasks/common/tasks'; import { IWorkspace, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import * as Types from 'vs/base/common/types'; import { ITaskService, WorkspaceFolderTaskResult } from 'vs/workbench/contrib/tasks/common/taskService'; @@ -14,6 +14,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { Disposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; +import { INotificationService } from 'vs/platform/notification/common/notification'; export const QUICKOPEN_DETAIL_CONFIG = 'task.quickOpen.detail'; export const QUICKOPEN_SKIP_CONFIG = 'task.quickOpen.skip'; @@ -37,7 +38,8 @@ export class TaskQuickPick extends Disposable { constructor( private taskService: ITaskService, private configurationService: IConfigurationService, - private quickInputService: IQuickInputService) { + private quickInputService: IQuickInputService, + private notificationService: INotificationService) { super(); this.sorter = this.taskService.createSorter(); } @@ -52,7 +54,7 @@ export class TaskQuickPick extends Disposable { } if (ConfiguringTask.is(task)) { let label: string = task.configures.type; - const configures = Objects.deepClone(task.configures); + const configures: Partial = Objects.deepClone(task.configures); delete configures['_key']; delete configures['type']; Object.keys(configures).forEach(key => label += `: ${configures[key]}`); @@ -262,11 +264,16 @@ export class TaskQuickPick extends Disposable { return task; } - return this.taskService.tryResolveTask(task); + const resolvedTask = await this.taskService.tryResolveTask(task); + + if (!resolvedTask) { + this.notificationService.error(nls.localize('noProviderForTask', "There is no task provider registered for tasks of type \"{0}\".", task.type)); + } + return resolvedTask; } - static async show(taskService: ITaskService, configurationService: IConfigurationService, quickInputService: IQuickInputService, placeHolder: string, defaultEntry?: TaskQuickPickEntry) { - const taskQuickPick = new TaskQuickPick(taskService, configurationService, quickInputService); + static async show(taskService: ITaskService, configurationService: IConfigurationService, quickInputService: IQuickInputService, notificationService: INotificationService, placeHolder: string, defaultEntry?: TaskQuickPickEntry) { + const taskQuickPick = new TaskQuickPick(taskService, configurationService, quickInputService, notificationService); return taskQuickPick.show(placeHolder, defaultEntry); } } diff --git a/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts b/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts index 71c3cd77e2..8f9596fbc7 100644 --- a/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts +++ b/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts @@ -15,6 +15,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { TaskQuickPick, TaskTwoLevelQuickPickEntry } from 'vs/workbench/contrib/tasks/browser/taskQuickPick'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { isString } from 'vs/base/common/types'; +import { INotificationService } from 'vs/platform/notification/common/notification'; export class TasksQuickAccessProvider extends PickerQuickAccessProvider { @@ -26,7 +27,8 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider = []; diff --git a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts index 38c4af8c73..c52244a22c 100644 --- a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts @@ -1197,7 +1197,7 @@ namespace ProblemMatcherConverter { if (global) { return Objects.deepClone(global); } - let localProblemMatcher = context.namedProblemMatchers[variableName]; + let localProblemMatcher: ProblemMatcher & Partial = context.namedProblemMatchers[variableName]; if (localProblemMatcher) { localProblemMatcher = Objects.deepClone(localProblemMatcher); // remove the name diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts index b6ba912885..7c441d806b 100644 --- a/src/vs/workbench/contrib/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -588,6 +588,18 @@ export abstract class CommonTask { return undefined; } + protected abstract getFolderId(): string | undefined; + + public getCommonTaskId(): string { + interface RecentTaskKey { + folder: string | undefined; + id: string; + } + + const key: RecentTaskKey = { folder: this.getFolderId(), id: this._id }; + return JSON.stringify(key); + } + public clone(): Task { return this.fromObject(Objects.assign({}, this)); } @@ -728,13 +740,21 @@ export class CustomTask extends CommonTask { return workspaceFolder ? `${workspaceFolder.uri.toString()}|${this._id}|${this.instance}` : `${this._id}|${this.instance}`; } + protected getFolderId(): string | undefined { + return this._source.kind === TaskSourceKind.User ? USER_TASKS_GROUP_KEY : this._source.config.workspaceFolder?.uri.toString(); + } + + public getCommonTaskId(): string { + return this._source.customizes ? super.getCommonTaskId() : (this.getRecentlyUsedKey() ?? super.getCommonTaskId()); + } + public getRecentlyUsedKey(): string | undefined { interface CustomKey { type: string; folder: string; id: string; } - let workspaceFolder = this._source.kind === TaskSourceKind.User ? USER_TASKS_GROUP_KEY : this._source.config.workspaceFolder?.uri.toString(); + let workspaceFolder = this.getFolderId(); if (!workspaceFolder) { return undefined; } @@ -803,13 +823,17 @@ export class ConfiguringTask extends CommonTask { return this._source.config.workspaceFolder; } + protected getFolderId(): string | undefined { + return this._source.kind === TaskSourceKind.User ? USER_TASKS_GROUP_KEY : this._source.config.workspaceFolder?.uri.toString(); + } + public getRecentlyUsedKey(): string | undefined { interface CustomKey { type: string; folder: string; id: string; } - let workspaceFolder = this._source.kind === TaskSourceKind.User ? USER_TASKS_GROUP_KEY : this._source.config.workspaceFolder?.uri.toString(); + let workspaceFolder = this.getFolderId(); if (!workspaceFolder) { return undefined; } @@ -865,6 +889,13 @@ export class ContributedTask extends CommonTask { : `${this._source.scope.toString()}|${this._id}|${this.instance}`; } + protected getFolderId(): string | undefined { + if (this._source.scope === TaskScope.Folder && this._source.workspaceFolder) { + return this._source.workspaceFolder.uri.toString(); + } + return undefined; + } + public getRecentlyUsedKey(): string | undefined { interface ContributedKey { type: string; @@ -874,9 +905,7 @@ export class ContributedTask extends CommonTask { } let key: ContributedKey = { type: 'contributed', scope: this._source.scope, id: this._id }; - if (this._source.scope === TaskScope.Folder && this._source.workspaceFolder) { - key.folder = this._source.workspaceFolder.uri.toString(); - } + key.folder = this.getFolderId(); return JSON.stringify(key); } @@ -921,6 +950,10 @@ export class InMemoryTask extends CommonTask { return `${this._id}|${this.instance}`; } + protected getFolderId(): undefined { + return undefined; + } + protected fromObject(object: InMemoryTask): InMemoryTask { return new InMemoryTask(object._id, object._source, object._label, object.type, object.runOptions, object.configurationProperties); } diff --git a/src/vs/workbench/contrib/terminal/browser/environmentVariableInfo.ts b/src/vs/workbench/contrib/terminal/browser/environmentVariableInfo.ts index 37bbcbcb9d..4692660fd0 100644 --- a/src/vs/workbench/contrib/terminal/browser/environmentVariableInfo.ts +++ b/src/vs/workbench/contrib/terminal/browser/environmentVariableInfo.ts @@ -19,8 +19,37 @@ export class EnvironmentVariableInfoStale implements IEnvironmentVariableInfo { } getInfo(): string { - let info = localize('extensionEnvironmentContribution', "Extensions want to make the follow changes to the terminal's environment:"); - info += `\n\n${this._summarizeDiff()}`; + const addsAndChanges: string[] = []; + const removals: string[] = []; + this._diff.added.forEach((mutators, variable) => { + mutators.forEach(mutator => addsAndChanges.push(mutatorTypeLabel(mutator.type, mutator.value, variable))); + }); + this._diff.changed.forEach((mutators, variable) => { + mutators.forEach(mutator => addsAndChanges.push(mutatorTypeLabel(mutator.type, mutator.value, variable))); + }); + this._diff.removed.forEach((mutators, variable) => { + mutators.forEach(mutator => removals.push(mutatorTypeLabel(mutator.type, mutator.value, variable))); + }); + + let info: string = ''; + + if (addsAndChanges.length > 0) { + info = localize('extensionEnvironmentContributionChanges', "Extensions want to make the following changes to the terminal's environment:"); + info += '\n\n'; + info += '```\n'; + info += addsAndChanges.join('\n'); + info += '\n```'; + } + + if (removals.length > 0) { + info += info.length > 0 ? '\n\n' : ''; + info += localize('extensionEnvironmentContributionRemoval', "Extensions want to remove these existing changes from the terminal's environment:"); + info += '\n\n'; + info += '```\n'; + info += removals.join('\n'); + info += '\n```'; + } + return info; } @@ -35,27 +64,6 @@ export class EnvironmentVariableInfoStale implements IEnvironmentVariableInfo { commandId: TERMINAL_COMMAND_ID.RELAUNCH }]; } - - private _summarizeDiff(): string { - const summary: string[] = []; - this._diff.added.forEach((mutators, variable) => { - mutators.forEach(mutator => { - summary.push(`- ${mutatorTypeLabel(mutator.type, mutator.value, variable)}`); - }); - }); - this._diff.changed.forEach((mutators, variable) => { - mutators.forEach(mutator => { - summary.push(`- "${mutatorTypeLabel(mutator.type, mutator.value, variable)}"`); - }); - }); - this._diff.removed.forEach((mutators, variable) => { - mutators.forEach(mutator => { - const removePrefixText = localize('removeEnvironmentVariableChange', "Remove the change {0}", mutatorTypeLabel(mutator.type, mutator.value, variable)); - summary.push(`- ${removePrefixText}`); - }); - }); - return summary.join('\n'); - } } export class EnvironmentVariableInfoChangesActive implements IEnvironmentVariableInfo { @@ -67,13 +75,12 @@ export class EnvironmentVariableInfoChangesActive implements IEnvironmentVariabl } getInfo(): string { - const info: string[] = ['Extensions have made changes to this terminal\'s environment:', '']; + const changes: string[] = []; this._collection.map.forEach((mutators, variable) => { - mutators.forEach(mutator => { - info.push(`- ${mutatorTypeLabel(mutator.type, mutator.value, variable)}`); - }); + mutators.forEach(mutator => changes.push(mutatorTypeLabel(mutator.type, mutator.value, variable))); }); - return info.join('\n'); + const message = localize('extensionEnvironmentContributionInfo', "Extensions have made changes to this terminal's environment"); + return message + '\n\n```\n' + changes.join('\n') + '\n```'; } getIcon(): string { @@ -83,8 +90,8 @@ export class EnvironmentVariableInfoChangesActive implements IEnvironmentVariabl function mutatorTypeLabel(type: EnvironmentVariableMutatorType, value: string, variable: string): string { switch (type) { - case EnvironmentVariableMutatorType.Prepend: return localize('prependValueToEnvironmentVariableMarkdown', "Add `{0}` to the beginning of `{1}`", value, variable); - case EnvironmentVariableMutatorType.Append: return localize('appendValueToEnvironmentVariableMarkdown', "Add `{0}` to the end of `{1}`", value, variable); - default: return localize('replaceEnvironmentVariableWithValueMarkdown', "Replace `{1}`\'s value with `{0}`", value, variable); + case EnvironmentVariableMutatorType.Prepend: return `${variable}=${value}\${env:${variable}}`; + case EnvironmentVariableMutatorType.Append: return `${variable}=\${env:${variable}}${value}`; + default: return `${variable}=${value}`; } } diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts index 8ab37c6712..8893033632 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts @@ -37,6 +37,7 @@ const excludedPathCharactersClause = '[^\\0\\s!$`&*()\\[\\]+\'":;\\\\]'; /** A regex that matches paths in the form /foo, ~/foo, ./foo, ../foo, foo/bar */ const unixLocalLinkClause = '((' + pathPrefix + '|(' + excludedPathCharactersClause + ')+)?(' + pathSeparatorClause + '(' + excludedPathCharactersClause + ')+)+)'; +// Valid absolute formats: C:, \\?\C: and \\?\%VAR% const winDrivePrefix = '(?:\\\\\\\\\\?\\\\)?[a-zA-Z]:'; const winPathPrefix = '(' + winDrivePrefix + '|\\.\\.?|\\~)'; const winPathSeparatorClause = '(\\\\|\\/)'; @@ -464,7 +465,7 @@ export class TerminalLinkManager extends DisposableStore { } else if (link.charAt(0) !== '/' && link.charAt(0) !== '~') { // Resolve workspace path . | .. | -> /. | /.. | / if (this._processManager.os === OperatingSystem.Windows) { - if (!link.match('^' + winDrivePrefix)) { + if (!link.match('^' + winDrivePrefix) && !link.startsWith('\\\\?\\')) { if (!this._processCwd) { // Abort if no workspace is open return null; diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalValidatedLocalLinkProvider.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalValidatedLocalLinkProvider.ts index c42e654a5a..0bf1e769f1 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalValidatedLocalLinkProvider.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalValidatedLocalLinkProvider.ts @@ -168,10 +168,10 @@ export class TerminalValidatedLocalLinkProvider implements ILinkProvider { this._hostService.openWindow([{ folderUri: uri }], { forceNewWindow: true }); } - private async _isDirectoryInsideWorkspace(uri: URI) { + private _isDirectoryInsideWorkspace(uri: URI) { const folders = this._workspaceContextService.getWorkspace().folders; for (let i = 0; i < folders.length; i++) { - if (isEqualOrParent(uri, folders[0].uri)) { + if (isEqualOrParent(uri, folders[i].uri)) { return true; } } diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalWordLinkProvider.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalWordLinkProvider.ts index 84841cd65f..0b22b6cc28 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalWordLinkProvider.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalWordLinkProvider.ts @@ -37,21 +37,21 @@ export class TerminalWordLinkProvider implements ILinkProvider { const end: IBufferCellPosition = { x: position.x, y: position.y }; // TODO: Support wrapping - // Expand to the left until a word separator is hit const line = this._xterm.buffer.active.getLine(position.y - 1)!; let text = ''; start.x++; // The hovered cell is considered first for (let x = position.x; x > 0; x--) { - const char = line.getCell(x - 1)?.getChars(); - if (!char) { + const cell = line.getCell(x - 1); + if (!cell) { break; } + const char = cell.getChars(); const config = this._configurationService.getValue(TERMINAL_CONFIG_SECTION); - if (config.wordSeparators.indexOf(char) >= 0) { + if (cell.getWidth() !== 0 && config.wordSeparators.indexOf(char) >= 0) { break; } - start.x--; + start.x = x; text = char + text; } @@ -62,17 +62,17 @@ export class TerminalWordLinkProvider implements ILinkProvider { } // Expand to the right until a word separator is hit - // end.x++; // The hovered cell is considered first for (let x = position.x + 1; x <= line.length; x++) { - const char = line.getCell(x - 1)?.getChars(); - if (!char) { + const cell = line.getCell(x - 1); + if (!cell) { break; } + const char = cell.getChars(); const config = this._configurationService.getValue(TERMINAL_CONFIG_SECTION); - if (config.wordSeparators.indexOf(char) >= 0) { + if (cell.getWidth() !== 0 && config.wordSeparators.indexOf(char) >= 0) { break; } - end.x++; + end.x = x; text += char; } diff --git a/src/vs/workbench/contrib/terminal/browser/media/widgets.css b/src/vs/workbench/contrib/terminal/browser/media/widgets.css index 8b1eeb4f7c..1f282dac8d 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/widgets.css +++ b/src/vs/workbench/contrib/terminal/browser/media/widgets.css @@ -26,6 +26,15 @@ color: #3794ff; } +.monaco-workbench .terminal-hover-widget.right-aligned .hover-row.status-bar .actions { + flex-direction: row-reverse; +} + +.monaco-workbench .terminal-hover-widget.right-aligned .hover-row.status-bar .actions .action-container { + margin-right: 0; + margin-left: 16px; +} + .monaco-workbench .terminal-overlay-widget { position: absolute; left: 0; @@ -45,7 +54,7 @@ width: 28px; height: 28px; text-align: center; - z-index: 25; + z-index: 10; opacity: 0.5; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 17001c5ef5..b294a23488 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -68,6 +68,7 @@ if (platform.isWeb) { const VIEW_CONTAINER = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: TERMINAL_VIEW_ID, name: nls.localize('terminal', "Terminal"), + icon: 'codicon-terminal', ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [TERMINAL_VIEW_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), storageId: TERMINAL_VIEW_ID, focusCommand: { id: TERMINAL_COMMAND_ID.FOCUS }, diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 235c6bfe2d..aadb7e0e37 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -35,7 +35,7 @@ export interface ITerminalInstanceService { getXtermUnicode11Constructor(): Promise; getXtermWebLinksConstructor(): Promise; getXtermWebglConstructor(): Promise; - createWindowsShellHelper(shellProcessId: number, instance: ITerminalInstance, xterm: XTermTerminal): IWindowsShellHelper; + createWindowsShellHelper(shellProcessId: number, xterm: XTermTerminal): IWindowsShellHelper; createTerminalProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, windowsEnableConpty: boolean): ITerminalChildProcess; getDefaultShellAndArgs(useAutomationShell: boolean, platformOverride?: Platform): Promise<{ shell: string, args: string[] | string | undefined }>; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 7d88237bee..6e9bb437c0 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -1219,7 +1219,7 @@ export function registerTerminalActions() { constructor() { super({ id: TERMINAL_COMMAND_ID.FIND_NEXT, - title: localize('workbench.action.terminal.findNext', "Find next"), + title: localize('workbench.action.terminal.findNext', "Find Next"), f1: true, category, keybinding: [ @@ -1245,7 +1245,7 @@ export function registerTerminalActions() { constructor() { super({ id: TERMINAL_COMMAND_ID.FIND_PREVIOUS, - title: localize('workbench.action.terminal.findPrevious', "Find previous"), + title: localize('workbench.action.terminal.findPrevious', "Find Previous"), f1: true, category, keybinding: [ diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 63043c1eec..b9f3f61e05 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -30,7 +30,7 @@ import { ansiColorIdentifiers, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_BACKGR import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { TerminalLinkManager } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { ITerminalInstanceService, ITerminalInstance, TerminalShellType, ITerminalBeforeHandleLinkEvent } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalInstanceService, ITerminalInstance, TerminalShellType, WindowsShellType, ITerminalBeforeHandleLinkEvent } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalProcessManager } from 'vs/workbench/contrib/terminal/browser/terminalProcessManager'; import { Terminal as XTermTerminal, IBuffer, ITerminalAddon } from 'xterm'; import { SearchAddon, ISearchOptions } from 'xterm-addon-search'; @@ -902,7 +902,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } this._xtermReadyPromise.then(xterm => { if (!this._isDisposed && this._processManager && this._processManager.shellProcessId) { - this._windowsShellHelper = this._terminalInstanceService.createWindowsShellHelper(this._processManager.shellProcessId, this, xterm); + this._windowsShellHelper = this._terminalInstanceService.createWindowsShellHelper(this._processManager.shellProcessId, xterm); + this._windowsShellHelper.onShellNameChange(title => { + this.setShellType(this.getShellType(title)); + if (this.isTitleSetByProcess) { + this.setTitle(title, TitleEventSource.Process); + } + }); } }); }); @@ -915,6 +921,28 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { }, 0); } + private getShellType(executable: string): TerminalShellType { + switch (executable.toLowerCase()) { + case 'cmd.exe': + return WindowsShellType.CommandPrompt; + case 'powershell.exe': + case 'pwsh.exe': + return WindowsShellType.PowerShell; + case 'bash.exe': + return WindowsShellType.GitBash; + case 'wsl.exe': + case 'ubuntu.exe': + case 'ubuntu1804.exe': + case 'kali.exe': + case 'debian.exe': + case 'opensuse-42.exe': + case 'sles-12.exe': + return WindowsShellType.Wsl; + default: + return undefined; + } + } + private _onProcessData(data: string): void { this._xterm?.write(data); } @@ -1049,6 +1077,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } } + // Dispose the environment info widget if it exists + this._environmentInfo?.disposable.dispose(); + if (!reset) { // HACK: Force initialText to be non-falsy for reused terminals such that the // conptyInheritCursor flag is passed to the node-pty, this flag can cause a Window to hang diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index a36ab0e657..c8fd956bc9 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -84,10 +84,6 @@ export class TerminalViewPane extends ViewPane { this._register(this.themeService.onDidColorThemeChange(theme => this._updateTheme(theme))); this._register(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('terminal.integrated') || e.affectsConfiguration('editor.fontFamily')) { - this._updateFont(); - } - if (e.affectsConfiguration('terminal.integrated.fontFamily') || e.affectsConfiguration('editor.fontFamily')) { const configHelper = this._terminalService.configHelper; if (!configHelper.configFontIsMonospace()) { @@ -99,7 +95,6 @@ export class TerminalViewPane extends ViewPane { } } })); - this._updateFont(); this._updateTheme(); this._register(this.onDidChangeBodyVisibility(visible => { @@ -108,7 +103,6 @@ export class TerminalViewPane extends ViewPane { if (!hadTerminals) { this._terminalService.createTerminal(); } - this._updateFont(); this._updateTheme(); if (hadTerminals) { this._terminalService.getActiveTab()?.setVisible(visible); @@ -310,6 +304,7 @@ export class TerminalViewPane extends ViewPane { if (terminal) { const preparedPath = await this._terminalService.preparePathForTerminalAsync(path, terminal.shellLaunchConfig.executable, terminal.title, terminal.shellType); terminal.sendText(preparedPath, false); + terminal.focus(); } } })); @@ -334,15 +329,6 @@ export class TerminalViewPane extends ViewPane { this._findWidget.updateTheme(theme); } } - - private _updateFont(): void { - if (this._terminalService.terminalInstances.length === 0 || !this._parentDomElement) { - return; - } - // TODO: Can we support ligatures? - // dom.toggleClass(this._parentDomElement, 'enable-ligatures', this._terminalService.configHelper.config.fontLigatures); - this.layoutBody(this._parentDomElement.offsetHeight, this._parentDomElement.offsetWidth); - } } registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { diff --git a/src/vs/workbench/contrib/terminal/browser/widgets/environmentVariableInfoWidget.ts b/src/vs/workbench/contrib/terminal/browser/widgets/environmentVariableInfoWidget.ts index 8a4872f126..dc55f48f4c 100644 --- a/src/vs/workbench/contrib/terminal/browser/widgets/environmentVariableInfoWidget.ts +++ b/src/vs/workbench/contrib/terminal/browser/widgets/environmentVariableInfoWidget.ts @@ -5,11 +5,14 @@ import { Widget } from 'vs/base/browser/ui/widget'; import { IEnvironmentVariableInfo } from 'vs/workbench/contrib/terminal/common/environmentVariable'; -import { getDomNodePagePosition } from 'vs/base/browser/dom'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { ITerminalWidget, IHoverTarget, IHoverAnchor, HorizontalAnchorSide, VerticalAnchorSide } from 'vs/workbench/contrib/terminal/browser/widgets/widgets'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { HoverWidget } from 'vs/workbench/contrib/terminal/browser/widgets/hoverWidget'; +import { RunOnceScheduler } from 'vs/base/common/async'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import * as dom from 'vs/base/browser/dom'; +import { IDisposable } from 'vs/base/common/lifecycle'; export class EnvironmentVariableInfoWidget extends Widget implements ITerminalWidget { readonly id = 'env-var-info'; @@ -17,12 +20,14 @@ export class EnvironmentVariableInfoWidget extends Widget implements ITerminalWi private _domNode: HTMLElement | undefined; private _container: HTMLElement | undefined; private _hoverWidget: HoverWidget | undefined; + private _mouseMoveListener: IDisposable | undefined; get requiresAction() { return this._info.requiresAction; } constructor( private _info: IEnvironmentVariableInfo, - @IInstantiationService private readonly _instantiationService: IInstantiationService + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IConfigurationService private readonly _configurationService: IConfigurationService ) { super(); } @@ -32,12 +37,37 @@ export class EnvironmentVariableInfoWidget extends Widget implements ITerminalWi this._domNode = document.createElement('div'); this._domNode.classList.add('terminal-env-var-info', 'codicon', `codicon-${this._info.getIcon()}`); container.appendChild(this._domNode); - this.onmouseover(this._domNode, () => this._showHover()); + + + const timeout = this._configurationService.getValue('editor.hover.delay'); + const scheduler: RunOnceScheduler = new RunOnceScheduler(() => this._showHover(), timeout); + this._register(scheduler); + let origin = { x: 0, y: 0 }; + + this.onmouseover(this._domNode, e => { + origin.x = e.browserEvent.pageX; + origin.y = e.browserEvent.pageY; + scheduler.schedule(); + + this._mouseMoveListener = dom.addDisposableListener(this._domNode!, dom.EventType.MOUSE_MOVE, e => { + // Reset the scheduler if the mouse moves too much + if (Math.abs(e.pageX - origin.x) > window.devicePixelRatio * 2 || Math.abs(e.pageY - origin.y) > window.devicePixelRatio * 2) { + origin.x = e.pageX; + origin.y = e.pageY; + scheduler.schedule(); + } + }); + }); + this.onnonbubblingmouseout(this._domNode, () => { + scheduler.cancel(); + this._mouseMoveListener?.dispose(); + }); } dispose() { super.dispose(); this._domNode?.parentElement?.removeChild(this._domNode); + this._mouseMoveListener?.dispose(); } focus() { @@ -67,7 +97,7 @@ class ElementHoverTarget implements IHoverTarget { } get anchor(): IHoverAnchor { - const position = getDomNodePagePosition(this._element); + const position = dom.getDomNodePagePosition(this._element); return { x: position.left, horizontalAnchorSide: HorizontalAnchorSide.Left, diff --git a/src/vs/workbench/contrib/terminal/browser/widgets/hoverWidget.ts b/src/vs/workbench/contrib/terminal/browser/widgets/hoverWidget.ts index 5490fa3d14..39e9a28fb8 100644 --- a/src/vs/workbench/contrib/terminal/browser/widgets/hoverWidget.ts +++ b/src/vs/workbench/contrib/terminal/browser/widgets/hoverWidget.ts @@ -14,18 +14,23 @@ import * as dom from 'vs/base/browser/dom'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IHoverTarget, HorizontalAnchorSide, VerticalAnchorSide } from 'vs/workbench/contrib/terminal/browser/widgets/widgets'; import { KeyCode } from 'vs/base/common/keyCodes'; +import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/editorOptions'; const $ = dom.$; export class HoverWidget extends Widget { + private readonly _containerDomNode: HTMLElement; private readonly _domNode: HTMLElement; private readonly _messageListeners = new DisposableStore(); private readonly _mouseTracker: CompositeMouseTracker; + private readonly _scrollbar: DomScrollableElement; private _isDisposed: boolean = false; get isDisposed(): boolean { return this._isDisposed; } - get domNode(): HTMLElement { return this._domNode; } + get domNode(): HTMLElement { return this._containerDomNode; } private readonly _onDispose = new Emitter(); get onDispose(): Event { return this._onDispose.event; } @@ -36,20 +41,28 @@ export class HoverWidget extends Widget { private _text: IMarkdownString, private _linkHandler: (url: string) => void, private _actions: { label: string, iconClass?: string, run: (target: HTMLElement) => void, commandId: string }[] | undefined, - @IKeybindingService private readonly _keybindingService: IKeybindingService + @IKeybindingService private readonly _keybindingService: IKeybindingService, + @IConfigurationService private readonly _configurationService: IConfigurationService ) { super(); + this._containerDomNode = document.createElement('div'); + this._containerDomNode.classList.add('terminal-hover-widget', 'fadeIn', 'monaco-editor-hover', 'xterm-hover'); + this._containerDomNode.tabIndex = 0; + this._containerDomNode.setAttribute('role', 'tooltip'); + this._domNode = document.createElement('div'); - this._domNode.classList.add('terminal-hover-widget', 'fadeIn', 'monaco-editor-hover', 'xterm-hover'); - this._domNode.tabIndex = 0; - this._domNode.setAttribute('role', 'tooltip'); + this._domNode.className = 'monaco-editor-hover-content'; + + this._scrollbar = new DomScrollableElement(this._domNode, {}); + this._register(this._scrollbar); + this._containerDomNode.appendChild(this._scrollbar.getDomNode()); // Don't allow mousedown out of the widget, otherwise preventDefault will call and text will // not be selected. - this.onmousedown(this._domNode, e => e.stopPropagation()); + this.onmousedown(this._containerDomNode, e => e.stopPropagation()); // Hide hover on escape - this.onkeydown(this._domNode, e => { + this.onkeydown(this._containerDomNode, e => { if (e.equals(KeyCode.Escape)) { this.dispose(); } @@ -61,6 +74,14 @@ export class HoverWidget extends Widget { actionHandler: { callback: (content) => this._linkHandler(content), disposeables: this._messageListeners + }, + codeBlockRenderer: async (_, value) => { + const fontFamily = this._configurationService.getValue('editor').fontFamily || EDITOR_FONT_DEFAULTS.fontFamily; + return `${value.replace(/\n/g, '
')}
`; + }, + codeBlockRenderCallback: () => { + contentsElement.classList.add('code-hover-contents'); + this.layout(); } }); contentsElement.appendChild(markdownElement); @@ -75,11 +96,11 @@ export class HoverWidget extends Widget { this._domNode.appendChild(statusBarElement); } - this._mouseTracker = new CompositeMouseTracker([this._domNode, ..._target.targetElements]); + this._mouseTracker = new CompositeMouseTracker([this._containerDomNode, ..._target.targetElements]); this._register(this._mouseTracker.onMouseOut(() => this.dispose())); this._register(this._mouseTracker); - this._container.appendChild(this._domNode); + this._container.appendChild(this._containerDomNode); this.layout(); } @@ -106,45 +127,55 @@ export class HoverWidget extends Widget { public layout(): void { const anchor = this._target.anchor; + this._containerDomNode.classList.remove('right-aligned'); + this._domNode.style.maxHeight = ''; if (anchor.horizontalAnchorSide === HorizontalAnchorSide.Left) { - if (anchor.x + this._domNode.clientWidth * 0.75 > document.documentElement.clientWidth) { - // Shift the hover to the left when > 25% would be cut off - const width = Math.round(this._domNode.clientWidth * 0.75); - this._domNode.style.width = `${width - 1}px`; - this._domNode.style.maxWidth = ''; - this._domNode.style.left = `${document.documentElement.clientWidth - width - 1}px`; + if (anchor.x + this._containerDomNode.clientWidth > document.documentElement.clientWidth) { + // Shift the hover to the left when part of it would get cut off + const width = Math.round(this._containerDomNode.clientWidth); + this._containerDomNode.style.width = `${width - 1}px`; + this._containerDomNode.style.maxWidth = ''; + const left = document.documentElement.clientWidth - width - 1; + this._containerDomNode.style.left = `${left}px`; + // Right align if the right edge is closer to the anchor than the left edge + if (left + width / 2 < anchor.x) { + this._containerDomNode.classList.add('right-aligned'); + } } else { - this._domNode.style.width = ''; - this._domNode.style.maxWidth = `${document.documentElement.clientWidth - anchor.x - 1}px`; - this._domNode.style.left = `${anchor.x}px`; + this._containerDomNode.style.width = ''; + this._containerDomNode.style.maxWidth = `${document.documentElement.clientWidth - anchor.x - 1}px`; + this._containerDomNode.style.left = `${anchor.x}px`; } } else { - this._domNode.style.right = `${anchor.x}px`; + this._containerDomNode.style.right = `${anchor.x}px`; } // Use fallback y value if there is not enough vertical space if (anchor.verticalAnchorSide === VerticalAnchorSide.Bottom) { - if (anchor.y + this._domNode.clientHeight > document.documentElement.clientHeight) { - this._domNode.style.top = `${anchor.fallbackY}px`; + if (anchor.y + this._containerDomNode.clientHeight > document.documentElement.clientHeight) { + this._containerDomNode.style.top = `${anchor.fallbackY}px`; + this._domNode.style.maxHeight = `${document.documentElement.clientHeight - anchor.fallbackY}px`; } else { - this._domNode.style.bottom = `${anchor.y}px`; + this._containerDomNode.style.bottom = `${anchor.y}px`; + this._containerDomNode.style.maxHeight = ''; } } else { - if (anchor.y + this._domNode.clientHeight > document.documentElement.clientHeight) { - this._domNode.style.bottom = `${anchor.fallbackY}px`; + if (anchor.y + this._containerDomNode.clientHeight > document.documentElement.clientHeight) { + this._containerDomNode.style.bottom = `${anchor.fallbackY}px`; } else { - this._domNode.style.top = `${anchor.y}px`; + this._containerDomNode.style.top = `${anchor.y}px`; } } + this._scrollbar.scanDomNode(); } public focus() { - this._domNode.focus(); + this._containerDomNode.focus(); } public dispose(): void { if (!this._isDisposed) { this._onDispose.fire(); - this._domNode.parentElement?.removeChild(this.domNode); + this._containerDomNode.parentElement?.removeChild(this.domNode); this._messageListeners.dispose(); this._target.dispose(); super.dispose(); diff --git a/src/vs/workbench/contrib/terminal/common/environmentVariable.ts b/src/vs/workbench/contrib/terminal/common/environmentVariable.ts index 3509d7d5d7..62b1ed8c36 100644 --- a/src/vs/workbench/contrib/terminal/common/environmentVariable.ts +++ b/src/vs/workbench/contrib/terminal/common/environmentVariable.ts @@ -92,11 +92,7 @@ export interface IEnvironmentVariableService { delete(extensionIdentifier: string): void; } -/** - * First: Variable - * Second: Value - * Third: Type - */ +/** [variable, mutator] */ export type ISerializableEnvironmentVariableCollection = [string, IEnvironmentVariableMutator][]; export interface IEnvironmentVariableInfo { diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 7db1126ac6..ff38f5b2db 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -397,6 +397,8 @@ export enum TitleEventSource { } export interface IWindowsShellHelper extends IDisposable { + readonly onShellNameChange: Event; + getShellName(): Promise; } diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index ca38796f26..ad3dbf46cf 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -264,7 +264,7 @@ export const terminalConfiguration: IConfigurationNode = { localize('terminal.integrated.environmentChangesIndicator.on', "Enable the indicator."), localize('terminal.integrated.environmentChangesIndicator.warnonly', "Only show the warning indicator when a terminal's environment is 'stale', not the information indicator that shows a terminal has had its environment modified by an extension."), ], - default: 'on' + default: 'warnonly' }, 'terminal.integrated.showExitAlert': { description: localize('terminal.integrated.showExitAlert', "Controls whether to show the alert \"The terminal process terminated with exit code\" when exit code is non-zero."), diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts index 423ded8e09..67812fbcc6 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ITerminalInstanceService, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IWindowsShellHelper, IShellLaunchConfig, ITerminalChildProcess, IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY } from 'vs/workbench/contrib/terminal/common/terminal'; import { WindowsShellHelper } from 'vs/workbench/contrib/terminal/electron-browser/windowsShellHelper'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -79,8 +79,8 @@ export class TerminalInstanceService implements ITerminalInstanceService { return WebglAddon; } - public createWindowsShellHelper(shellProcessId: number, instance: ITerminalInstance, xterm: XTermTerminal): IWindowsShellHelper { - return new WindowsShellHelper(shellProcessId, instance, xterm); + public createWindowsShellHelper(shellProcessId: number, xterm: XTermTerminal): IWindowsShellHelper { + return new WindowsShellHelper(shellProcessId, xterm); } public createTerminalProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, windowsEnableConpty: boolean): ITerminalChildProcess { diff --git a/src/vs/workbench/contrib/terminal/electron-browser/windowsShellHelper.ts b/src/vs/workbench/contrib/terminal/electron-browser/windowsShellHelper.ts index 665343e281..50e2e997e8 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/windowsShellHelper.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/windowsShellHelper.ts @@ -5,11 +5,10 @@ import * as platform from 'vs/base/common/platform'; import { Emitter, Event } from 'vs/base/common/event'; -import { IWindowsShellHelper, TitleEventSource } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IWindowsShellHelper } from 'vs/workbench/contrib/terminal/common/terminal'; import { Terminal as XTermTerminal } from 'xterm'; import * as WindowsProcessTreeType from 'windows-process-tree'; import { Disposable } from 'vs/base/common/lifecycle'; -import { ITerminalInstance, TerminalShellType, WindowsShellType } from 'vs/workbench/contrib/terminal/browser/terminal'; import { timeout } from 'vs/base/common/async'; const SHELL_EXECUTABLES = [ @@ -34,9 +33,11 @@ export class WindowsShellHelper extends Disposable implements IWindowsShellHelpe private _currentRequest: Promise | undefined; private _newLineFeed: boolean = false; + private readonly _onShellNameChange = new Emitter(); + public get onShellNameChange(): Event { return this._onShellNameChange.event; } + public constructor( private _rootProcessId: number, - private _terminalInstance: ITerminalInstance, private _xterm: XTermTerminal ) { super(); @@ -84,13 +85,9 @@ export class WindowsShellHelper extends Disposable implements IWindowsShellHelpe } private checkShell(): void { - if (platform.isWindows && this._terminalInstance.isTitleSetByProcess) { - this.getShellName().then(title => { - if (!this._isDisposed) { - this._terminalInstance.setShellType(this.getShellType(title)); - this._terminalInstance.setTitle(title, TitleEventSource.Process); - } - }); + if (platform.isWindows) { + // TODO: Only fire when it's different + this.getShellName().then(title => this._onShellNameChange.fire(title)); } } @@ -145,26 +142,4 @@ export class WindowsShellHelper extends Disposable implements IWindowsShellHelpe }); return this._currentRequest; } - - public getShellType(executable: string): TerminalShellType { - switch (executable.toLowerCase()) { - case 'cmd.exe': - return WindowsShellType.CommandPrompt; - case 'powershell.exe': - case 'pwsh.exe': - return WindowsShellType.PowerShell; - case 'bash.exe': - return WindowsShellType.GitBash; - case 'wsl.exe': - case 'ubuntu.exe': - case 'ubuntu1804.exe': - case 'kali.exe': - case 'debian.exe': - case 'opensuse-42.exe': - case 'sles-12.exe': - return WindowsShellType.Wsl; - default: - return undefined; - } - } } diff --git a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkManager.test.ts b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkManager.test.ts index e1628693ef..d7b465b2ae 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkManager.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkManager.test.ts @@ -113,10 +113,10 @@ suite('Workbench - TerminalLinkHandler', () => { if (lineNo) { const lineColumnInfo: LineColumnInfo = terminalLinkHandler.extractLineColumnInfo(link); - assert.equal(lineColumnInfo.lineNumber, lineNo); + assert.equal(lineColumnInfo.lineNumber, lineNo, `For link ${link}, expected line number ${lineNo}, actual ${lineColumnInfo.lineNumber}`); if (columnNo) { - assert.equal(lineColumnInfo.columnNumber, columnNo); + assert.equal(lineColumnInfo.columnNumber, columnNo, `For link ${link}, expected column number ${columnNo}, actual ${lineColumnInfo.columnNumber}`); } } } @@ -190,10 +190,10 @@ suite('Workbench - TerminalLinkHandler', () => { if (lineNo) { const lineColumnInfo: LineColumnInfo = terminalLinkHandler.extractLineColumnInfo(link); - assert.equal(lineColumnInfo.lineNumber, lineNo); + assert.equal(lineColumnInfo.lineNumber, lineNo, `For link ${link}, expected line number ${lineNo}, actual ${lineColumnInfo.lineNumber}`); if (columnNo) { - assert.equal(lineColumnInfo.columnNumber, columnNo); + assert.equal(lineColumnInfo.columnNumber, columnNo, `For link ${link}, expected column number ${columnNo}, actual ${lineColumnInfo.columnNumber}`); } } } diff --git a/src/vs/workbench/contrib/terminal/test/browser/links/terminalWordLinkProvider.test.ts b/src/vs/workbench/contrib/terminal/test/browser/links/terminalWordLinkProvider.test.ts index 039fb48ac7..3de22b0b3d 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/links/terminalWordLinkProvider.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/links/terminalWordLinkProvider.test.ts @@ -23,7 +23,7 @@ suite('Workbench - TerminalWordLinkProvider', () => { async function assertLink(text: string, expected: { text: string, range: [number, number][] }) { const xterm = new Terminal(); - const provider = instantiationService.createInstance(TerminalWordLinkProvider, xterm, () => { }, () => { }, () => { }); + const provider = instantiationService.createInstance(TerminalWordLinkProvider, xterm, () => { }, () => { }); // Write the text and wait for the parser to finish await new Promise(r => xterm.write(text, r)); @@ -75,4 +75,14 @@ suite('Workbench - TerminalWordLinkProvider', () => { await assertLink('[foo]', { range: [[1, 1], [5, 1]], text: '[foo]' }); await assertLink('{foo}', { range: [[1, 1], [5, 1]], text: '{foo}' }); }); + + test('should support wide characters', async () => { + await configurationService.setUserConfiguration('terminal', { integrated: { wordSeparators: ' []' } }); + await assertLink('aabbccdd.txt ', { range: [[1, 1], [12, 1]], text: 'aabbccdd.txt' }); + await assertLink('我是学生.txt ', { range: [[1, 1], [12, 1]], text: '我是学生.txt' }); + await assertLink(' aabbccdd.txt ', { range: [[2, 1], [13, 1]], text: 'aabbccdd.txt' }); + await assertLink(' 我是学生.txt ', { range: [[2, 1], [13, 1]], text: '我是学生.txt' }); + await assertLink(' [aabbccdd.txt] ', { range: [[3, 1], [14, 1]], text: 'aabbccdd.txt' }); + await assertLink(' [我是学生.txt] ', { range: [[3, 1], [14, 1]], text: '我是学生.txt' }); + }); }); diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index 692f70e1a1..1a635634f5 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -660,7 +660,7 @@ export class TimelinePane extends ViewPane { let count = 0; if (this.timelinesBySource.size === 1) { - const [source, timeline] = Iterable.first(this.timelinesBySource); + const [source, timeline] = Iterable.first(this.timelinesBySource)!; timeline.lastRenderedIndex = -1; diff --git a/src/vs/workbench/contrib/update/browser/update.ts b/src/vs/workbench/contrib/update/browser/update.ts index eebae8868b..4f1b188668 100644 --- a/src/vs/workbench/contrib/update/browser/update.ts +++ b/src/vs/workbench/contrib/update/browser/update.ts @@ -10,7 +10,6 @@ import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IActivityService, NumberBadge, IBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; @@ -257,7 +256,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu this.badgeDisposable.clear(); if (badge) { - this.badgeDisposable.value = this.activityService.showActivity(GLOBAL_ACTIVITY_ID, badge, clazz, priority); + this.badgeDisposable.value = this.activityService.showGlobalActivity({ badge, clazz, priority }); } this.state = state; diff --git a/src/vs/workbench/contrib/url/common/trustedDomainsValidator.ts b/src/vs/workbench/contrib/url/common/trustedDomainsValidator.ts index 4b48a3bc7c..96fd545e22 100644 --- a/src/vs/workbench/contrib/url/common/trustedDomainsValidator.ts +++ b/src/vs/workbench/contrib/url/common/trustedDomainsValidator.ts @@ -222,6 +222,10 @@ function pathMatches(open: string, rule: string) { return true; } + if (rule[rule.length - 1] === '/') { + rule = rule.slice(0, -1); + } + const openSegments = open.split('/'); const ruleSegments = rule.split('/'); for (let i = 0; i < ruleSegments.length; i++) { diff --git a/src/vs/workbench/contrib/url/test/browser/linkProtection.test.ts b/src/vs/workbench/contrib/url/test/browser/trustedDomains.test.ts similarity index 93% rename from src/vs/workbench/contrib/url/test/browser/linkProtection.test.ts rename to src/vs/workbench/contrib/url/test/browser/trustedDomains.test.ts index b29705e97f..4a393daff6 100644 --- a/src/vs/workbench/contrib/url/test/browser/linkProtection.test.ts +++ b/src/vs/workbench/contrib/url/test/browser/trustedDomains.test.ts @@ -56,6 +56,11 @@ suite('Link protection domain matching', () => { test('sub paths', () => { linkAllowedByRules('https://x.org/foo', ['https://x.org/foo']); + linkAllowedByRules('https://x.org/foo/bar', ['https://x.org/foo']); + + linkAllowedByRules('https://x.org/foo', ['https://x.org/foo/']); + linkAllowedByRules('https://x.org/foo/bar', ['https://x.org/foo/']); + linkAllowedByRules('https://x.org/foo', ['x.org/foo']); linkAllowedByRules('https://x.org/foo', ['*.org/foo']); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index b947c552c1..75e9b19c6b 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -29,19 +29,18 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/ import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { - CONTEXT_SYNC_STATE, ISyncConfiguration, IUserDataAutoSyncService, IUserDataSyncService, registerConfiguration, + CONTEXT_SYNC_STATE, IUserDataAutoSyncService, IUserDataSyncService, registerConfiguration, SyncResource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, IUserDataSyncEnablementService, CONTEXT_SYNC_ENABLEMENT, SyncResourceConflicts, Conflict, getSyncResourceFromLocalPreview } from 'vs/platform/userDataSync/common/userDataSync'; import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets'; -import { GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IEditorInput, toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import * as Constants from 'vs/workbench/contrib/logs/common/logConstants'; import { IOutputService } from 'vs/workbench/contrib/output/common/output'; import { UserDataSyncTrigger } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger'; -import { IActivityService, IBadge, NumberBadge } from 'vs/workbench/services/activity/common/activity'; +import { IActivityService, IBadge, NumberBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; @@ -76,36 +75,33 @@ type FirstTimeSyncClassification = { action: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; }; -const getActivityTitle = (label: string, userDataSyncService: IUserDataSyncService): string => { - if (userDataSyncService.status === SyncStatus.Syncing) { - return localize('sync is on with syncing', "{0} (syncing)", label); - } - if (userDataSyncService.lastSyncTime) { - return localize('sync is on with time', "{0} (synced {1})", label, fromNow(userDataSyncService.lastSyncTime, true)); - } - return label; -}; -const getIdentityTitle = (label: string, userDataSyncAccountService: UserDataSyncAccounts, authenticationService: IAuthenticationService) => { - const account = userDataSyncAccountService.current; - return account ? `${label} (${authenticationService.getDisplayName(account.authenticationProviderId)}:${account.accountName})` : label; -}; const turnOnSyncCommand = { id: 'workbench.userData.actions.syncStart', title: localize('turn on sync with category', "Preferences Sync: Turn On...") }; -const stopSyncCommand = { id: 'workbench.userData.actions.stopSync', title(userDataSyncAccountService: UserDataSyncAccounts, authenticationService: IAuthenticationService) { return getIdentityTitle(localize('stop sync', "Preferences Sync: Turn Off"), userDataSyncAccountService, authenticationService); } }; +const stopSyncCommand = { id: 'workbench.userData.actions.stopSync', title: localize('stop sync', "Preferences Sync: Turn Off") }; const resolveSettingsConflictsCommand = { id: 'workbench.userData.actions.resolveSettingsConflicts', title: localize('showConflicts', "Preferences Sync: Show Settings Conflicts") }; const resolveKeybindingsConflictsCommand = { id: 'workbench.userData.actions.resolveKeybindingsConflicts', title: localize('showKeybindingsConflicts', "Preferences Sync: Show Keybindings Conflicts") }; const resolveSnippetsConflictsCommand = { id: 'workbench.userData.actions.resolveSnippetsConflicts', title: localize('showSnippetsConflicts', "Preferences Sync: Show User Snippets Conflicts") }; const configureSyncCommand = { id: 'workbench.userData.actions.configureSync', title: localize('configure sync', "Preferences Sync: Configure...") }; const showSyncActivityCommand = { - id: 'workbench.userData.actions.showSyncActivity', title(userDataSyncService: IUserDataSyncService): string { - return getActivityTitle(localize('show sync log', "Preferences Sync: Show Log"), userDataSyncService); + id: 'workbench.userData.actions.showSyncActivity', + title: localize('show sync log', "Preferences Sync: Show Log"), + description(userDataSyncService: IUserDataSyncService): string | undefined { + if (userDataSyncService.status === SyncStatus.Syncing) { + return localize('sync is on with syncing', "syncing"); + } + if (userDataSyncService.lastSyncTime) { + return localize('sync is on with time', "synced {0}", fromNow(userDataSyncService.lastSyncTime, true)); + } + return undefined; } }; const showSyncSettingsCommand = { id: 'workbench.userData.actions.syncSettings', title: localize('sync settings', "Preferences Sync: Show Settings"), }; +const CONTEXT_TURNING_ON_STATE = new RawContextKey('userDataSyncTurningOn', false); const CONTEXT_ACCOUNT_STATE = new RawContextKey('userDataSyncAccountStatus', AccountStatus.Uninitialized); export class UserDataSyncWorkbenchContribution extends Disposable implements IWorkbenchContribution { + private readonly turningOnSyncContext: IContextKey; private readonly syncEnablementContext: IContextKey; private readonly syncStatusContext: IContextKey; private readonly accountStatusContext: IContextKey; @@ -138,6 +134,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo ) { super(); + this.turningOnSyncContext = CONTEXT_TURNING_ON_STATE.bindTo(contextKeyService); this.syncEnablementContext = CONTEXT_SYNC_ENABLEMENT.bindTo(contextKeyService); this.syncStatusContext = CONTEXT_SYNC_STATE.bindTo(contextKeyService); this.accountStatusContext = CONTEXT_ACCOUNT_STATE.bindTo(contextKeyService); @@ -391,34 +388,67 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo badge = new NumberBadge(1, () => localize('sign in to sync preferences', "Sign in to Sync Preferences")); } else if (this.userDataSyncService.conflicts.length) { badge = new NumberBadge(this.userDataSyncService.conflicts.reduce((result, syncResourceConflict) => { return result + syncResourceConflict.conflicts.length; }, 0), () => localize('has conflicts', "Preferences Sync: Conflicts Detected")); + } else if (this.turningOnSync) { + badge = new ProgressBadge(() => localize('turning on syncing', "Turning on Preferences Sync...")); + clazz = 'progress-badge'; + priority = 1; } if (badge) { - this.badgeDisposable.value = this.activityService.showActivity(GLOBAL_ACTIVITY_ID, badge, clazz, priority); + this.badgeDisposable.value = this.activityService.showGlobalActivity({ badge, clazz, priority }); } } - private async turnOn(): Promise { - if (!this.storageService.getBoolean('sync.donotAskPreviewConfirmation', StorageScope.GLOBAL, false)) { - const result = await this.dialogService.show( - Severity.Info, - localize('sync preview message', "Synchronizing your preferences is a preview feature, please read the documentation before turning it on."), - [ - localize('open doc', "Open Documentation"), - localize('turn on', "Turn On"), - localize('cancel', "Cancel"), - ], - { - cancelId: 2 - } - ); - switch (result.choice) { - case 0: this.openerService.open(URI.parse('https://aka.ms/vscode-settings-sync-help')); return; - case 2: return; - } - } + private get turningOnSync(): boolean { + return !!this.turningOnSyncContext.get(); + } - return new Promise((c, e) => { + private set turningOnSync(turningOn: boolean) { + this.turningOnSyncContext.set(turningOn); + this.updateBadge(); + } + + private async turnOn(): Promise { + this.turningOnSync = true; + try { + if (!this.storageService.getBoolean('sync.donotAskPreviewConfirmation', StorageScope.GLOBAL, false)) { + if (!await this.askForConfirmation()) { + return; + } + } + const turnOn = await this.askToConfigure(); + if (!turnOn) { + return; + } + await this.doTurnOn(); + this.storageService.store('sync.donotAskPreviewConfirmation', true, StorageScope.GLOBAL); + } finally { + this.turningOnSync = false; + } + } + + private async askForConfirmation(): Promise { + const result = await this.dialogService.show( + Severity.Info, + localize('sync preview message', "Synchronizing your preferences is a preview feature, please read the documentation before turning it on."), + [ + localize('open doc', "Open Documentation"), + localize('turn on', "Turn On"), + localize('cancel', "Cancel"), + ], + { + cancelId: 2 + } + ); + switch (result.choice) { + case 0: this.openerService.open(URI.parse('https://aka.ms/vscode-settings-sync-help')); return false; + case 2: return false; + } + return true; + } + + private async askToConfigure(): Promise { + return new Promise((c, e) => { const disposables: DisposableStore = new DisposableStore(); const quickPick = this.quickInputService.createQuickPick(); disposables.add(quickPick); @@ -441,14 +471,23 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo const items = this.getConfigureSyncQuickPickItems(); quickPick.items = items; quickPick.selectedItems = items.filter(item => this.userDataSyncEnablementService.isResourceEnabled(item.id)); - disposables.add(Event.any(quickPick.onDidAccept, quickPick.onDidCustom)(async () => { - if (quickPick.selectedItems.length) { - this.updateConfiguration(items, quickPick.selectedItems); - this.doTurnOn().then(c, e); - quickPick.hide(); + let accepted: boolean = false; + disposables.add(Event.any(quickPick.onDidAccept, quickPick.onDidCustom)(() => { + accepted = true; + quickPick.hide(); + })); + disposables.add(quickPick.onDidHide(() => { + try { + if (accepted) { + this.updateConfiguration(items, quickPick.selectedItems); + } + c(accepted); + } catch (error) { + e(error); + } finally { + disposables.dispose(); } })); - disposables.add(quickPick.onDidHide(() => disposables.dispose())); quickPick.show(); }); } @@ -467,7 +506,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo await this.handleFirstTimeSync(); this.userDataSyncEnablementService.setEnablement(true); this.notificationService.info(localize('sync turned on', "Preferences sync is turned on")); - this.storageService.store('sync.donotAskPreviewConfirmation', true, StorageScope.GLOBAL); } private getConfigureSyncQuickPickItems(): ConfigureSyncQuickPickItem[] { @@ -499,7 +537,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } - private async configureSyncOptions(): Promise { + private async configureSyncOptions(): Promise { return new Promise((c, e) => { const disposables: DisposableStore = new DisposableStore(); const quickPick = this.quickInputService.createQuickPick(); @@ -514,7 +552,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo quickPick.selectedItems = items.filter(item => this.userDataSyncEnablementService.isResourceEnabled(item.id)); disposables.add(quickPick.onDidAccept(async () => { if (quickPick.selectedItems.length) { - await this.updateConfiguration(items, quickPick.selectedItems); + this.updateConfiguration(items, quickPick.selectedItems); quickPick.hide(); } })); @@ -656,6 +694,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo this.registerTurnOnSyncAction(); this.registerTurnOffSyncAction(); } + this.registerTurninOnSyncAction(); this.registerSignInAction(); // When Sync is turned on from CLI this.registerShowSettingsConflictsAction(); this.registerShowKeybindingsConflictsAction(); @@ -668,7 +707,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } private registerTurnOnSyncAction(): void { - const turnOnSyncWhenContext = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT.toNegated(), CONTEXT_ACCOUNT_STATE.notEqualsTo(AccountStatus.Uninitialized)); + const turnOnSyncWhenContext = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT.toNegated(), CONTEXT_ACCOUNT_STATE.notEqualsTo(AccountStatus.Uninitialized), CONTEXT_TURNING_ON_STATE.negate()); CommandsRegistry.registerCommand(turnOnSyncCommand.id, async () => { try { await this.turnOn(); @@ -709,6 +748,30 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo }); } + private registerTurninOnSyncAction(): void { + const when = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT.toNegated(), CONTEXT_ACCOUNT_STATE.notEqualsTo(AccountStatus.Uninitialized), CONTEXT_TURNING_ON_STATE); + this._register(registerAction2(class TurningOnSyncAction extends Action2 { + constructor() { + super({ + id: 'workbench.userData.actions.turningOn', + title: localize('turnin on sync', "Turning on Preferences Sync..."), + precondition: ContextKeyExpr.false(), + menu: [{ + group: '5_sync', + id: MenuId.GlobalActivity, + when, + order: 2 + }, { + group: '1_sync', + id: MenuId.AccountsContext, + when, + }] + }); + } + async run(): Promise { } + })); + } + private registerSignInAction(): void { const that = this; const id = 'workbench.userData.actions.signin'; @@ -883,10 +946,11 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } items.push({ id: configureSyncCommand.id, label: configureSyncCommand.title }); items.push({ id: showSyncSettingsCommand.id, label: showSyncSettingsCommand.title }); - items.push({ id: showSyncActivityCommand.id, label: showSyncActivityCommand.title(that.userDataSyncService) }); + items.push({ id: showSyncActivityCommand.id, label: showSyncActivityCommand.title, description: showSyncActivityCommand.description(that.userDataSyncService) }); items.push({ type: 'separator' }); if (that.userDataSyncEnablementService.canToggleEnablement()) { - items.push({ id: stopSyncCommand.id, label: stopSyncCommand.title(that.userDataSyncAccounts, that.authenticationService) }); + const account = that.userDataSyncAccounts.current; + items.push({ id: stopSyncCommand.id, label: stopSyncCommand.title, description: account ? `${account.accountName} (${that.authenticationService.getDisplayName(account.authenticationProviderId)})` : undefined }); } quickPick.items = items; disposables.add(quickPick.onDidAccept(() => { @@ -911,7 +975,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo constructor() { super({ id: stopSyncCommand.id, - title: stopSyncCommand.title(that.userDataSyncAccounts, that.authenticationService), + title: stopSyncCommand.title, menu: { id: MenuId.CommandPalette, when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT), @@ -953,7 +1017,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo constructor() { super({ id: showSyncActivityCommand.id, - get title() { return showSyncActivityCommand.title(that.userDataSyncService); }, + title: showSyncActivityCommand.title, menu: { id: MenuId.CommandPalette, when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)), diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncAccount.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncAccount.ts index 4ec4d66538..2e8629cf53 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncAccount.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncAccount.ts @@ -29,12 +29,16 @@ type UserAccountEvent = { id: string; }; -type AccountQuickPickItem = { label: string, authenticationProvider: IAuthenticationProvider, account?: IUserDataSyncAccount, detail?: string }; +type AccountQuickPickItem = { label: string, authenticationProvider: IAuthenticationProvider, account?: UserDataSyncAccount, description?: string }; -export interface IUserDataSyncAccount { - readonly authenticationProviderId: string; - readonly sessionId: string; - readonly accountName: string; +export class UserDataSyncAccount { + + constructor(readonly authenticationProviderId: string, private readonly session: AuthenticationSession) { } + + get sessionId(): string { return this.session.id; } + get accountName(): string { return this.session.account.displayName; } + get accountId(): string { return this.session.account.id; } + getToken(): Thenable { return this.session.getAccessToken(); } } export const enum AccountStatus { @@ -60,10 +64,10 @@ export class UserDataSyncAccounts extends Disposable { private readonly _onDidSignOut = this._register(new Emitter()); readonly onDidSignOut = this._onDidSignOut.event; - private _all: Map = new Map(); - get all(): IUserDataSyncAccount[] { return flatten(values(this._all)); } + private _all: Map = new Map(); + get all(): UserDataSyncAccount[] { return flatten(values(this._all)); } - get current(): IUserDataSyncAccount | undefined { return this.all.filter(account => this.isCurrentAccount(account))[0]; } + get current(): UserDataSyncAccount | undefined { return this.all.filter(account => this.isCurrentAccount(account))[0]; } constructor( @IAuthenticationService private readonly authenticationService: IAuthenticationService, @@ -123,19 +127,58 @@ export class UserDataSyncAccounts extends Disposable { } private async update(): Promise { - const allAccounts: Map = new Map(); + const allAccounts: Map = new Map(); for (const { id } of this.authenticationProviders) { const accounts = await this.getAccounts(id); allAccounts.set(id, accounts); } this._all = allAccounts; - const status = this.current ? AccountStatus.Available : AccountStatus.Unavailable; + const current = this.current; + await this.updateToken(current); + this.updateStatus(current); + } - if (this._status === AccountStatus.Unavailable) { - await this.authenticationTokenService.setToken(undefined); + private async getAccounts(authenticationProviderId: string): Promise { + let accounts: Map = new Map(); + let currentAccount: UserDataSyncAccount | null = null; + + const sessions = await this.authenticationService.getSessions(authenticationProviderId) || []; + for (const session of sessions) { + const account: UserDataSyncAccount = new UserDataSyncAccount(authenticationProviderId, session); + accounts.set(account.accountName, account); + if (this.isCurrentAccount(account)) { + currentAccount = account; + } } + if (currentAccount) { + // Always use current account if available + accounts.set(currentAccount.accountName, currentAccount); + } + + return values(accounts); + } + + private async updateToken(current: UserDataSyncAccount | undefined): Promise { + let value: { token: string, authenticationProviderId: string } | undefined = undefined; + if (current) { + try { + this.logService.trace('Preferences Sync: Updating the token for the account', current.accountName); + const token = await current.getToken(); + this.logService.trace('Preferences Sync: Token updated for the account', current.accountName); + value = { token, authenticationProviderId: current.authenticationProviderId }; + } catch (e) { + this.logService.error(e); + } + } + await this.authenticationTokenService.setToken(value); + } + + private updateStatus(current: UserDataSyncAccount | undefined): void { + // set status + const status: AccountStatus = current ? AccountStatus.Available : AccountStatus.Unavailable; + if (this._status !== status) { const previous = this._status; this.logService.debug('Sync account status changed', previous, status); @@ -149,41 +192,7 @@ export class UserDataSyncAccounts extends Disposable { } } - private async getAccounts(authenticationProviderId: string): Promise { - - let accounts: Map = new Map(); - let currentAccount: IUserDataSyncAccount | null = null; - let currentSession: AuthenticationSession | undefined = undefined; - - const sessions = await this.authenticationService.getSessions(authenticationProviderId) || []; - for (const session of sessions) { - const account: IUserDataSyncAccount = { authenticationProviderId, sessionId: session.id, accountName: session.account.displayName }; - accounts.set(account.accountName, account); - if (this.isCurrentAccount(account)) { - currentAccount = account; - currentSession = session; - } - } - - if (currentAccount) { - // Always use current account if available - accounts.set(currentAccount.accountName, currentAccount); - } - - // update access token - if (currentSession) { - try { - const token = await currentSession.getAccessToken(); - await this.authenticationTokenService.setToken({ token, authenticationProviderId }); - } catch (e) { - this.logService.error(e); - } - } - - return values(accounts); - } - - private isCurrentAccount(account: IUserDataSyncAccount): boolean { + private isCurrentAccount(account: UserDataSyncAccount): boolean { return account.sessionId === this.currentSessionId; } @@ -192,20 +201,22 @@ export class UserDataSyncAccounts extends Disposable { if (!result) { return false; } - let sessionId: string, accountName: string; + let sessionId: string, accountName: string, accountId: string; if (isAuthenticationProvider(result)) { const session = await this.authenticationService.login(result.id, result.scopes); sessionId = session.id; accountName = session.account.displayName; + accountId = session.account.id; } else { sessionId = result.sessionId; accountName = result.accountName; + accountId = result.accountId; } - await this.switch(sessionId, accountName); + await this.switch(sessionId, accountName, accountId); return true; } - private async doPick(): Promise { + private async doPick(): Promise { if (this.authenticationProviders.length === 0) { return undefined; } @@ -217,8 +228,8 @@ export class UserDataSyncAccounts extends Disposable { return this.authenticationProviders[0]; } - return new Promise(async (c, e) => { - let result: IUserDataSyncAccount | IAuthenticationProvider | undefined; + return new Promise(async (c, e) => { + let result: UserDataSyncAccount | IAuthenticationProvider | undefined; const disposables: DisposableStore = new DisposableStore(); const quickPick = this.quickInputService.createQuickPick(); disposables.add(quickPick); @@ -243,38 +254,42 @@ export class UserDataSyncAccounts extends Disposable { private createQuickpickItems(): (AccountQuickPickItem | IQuickPickSeparator)[] { const quickPickItems: (AccountQuickPickItem | IQuickPickSeparator)[] = []; - const authenticationProviders = [...this.authenticationProviders].sort(({ id }) => id === this.current?.authenticationProviderId ? -1 : 1); - for (const authenticationProvider of authenticationProviders) { - const providerName = this.authenticationService.getDisplayName(authenticationProvider.id); - if (this.all.length) { - quickPickItems.push({ type: 'separator', label: providerName }); - const accounts = this._all.get(authenticationProvider.id) || []; + + // Signed in Accounts + if (this.all.length) { + const authenticationProviders = [...this.authenticationProviders].sort(({ id }) => id === this.current?.authenticationProviderId ? -1 : 1); + quickPickItems.push({ type: 'separator', label: localize('signed in', "Signed in") }); + for (const authenticationProvider of authenticationProviders) { + const accounts = (this._all.get(authenticationProvider.id) || []).sort(({ sessionId }) => sessionId === this.current?.sessionId ? -1 : 1); + const providerName = this.authenticationService.getDisplayName(authenticationProvider.id); for (const account of accounts) { quickPickItems.push({ - label: account.accountName, - detail: account.sessionId === this.current?.sessionId ? localize('last used', "Last Used") : undefined, + label: `${account.accountName} (${providerName})`, + description: account.sessionId === this.current?.sessionId ? localize('last used', "Last Used with Sync") : undefined, account, authenticationProvider, }); } - quickPickItems.push({ - label: accounts.length ? localize('use another', "Use another {0} Account", providerName) : localize('use provider account', "Use {0} Account", providerName), - authenticationProvider, - }); - } else { - quickPickItems.push({ label: providerName, authenticationProvider }); } + quickPickItems.push({ type: 'separator', label: localize('others', "Others") }); } + + // Account proviers + for (const authenticationProvider of this.authenticationProviders) { + const providerName = this.authenticationService.getDisplayName(authenticationProvider.id); + quickPickItems.push({ label: localize('sign in using account', "Sign in with {0}", providerName), authenticationProvider }); + } + return quickPickItems; } - private async switch(sessionId: string, accountName: string): Promise { + private async switch(sessionId: string, accountName: string, accountId: string): Promise { const currentAccount = this.current; if (this.userDataSyncEnablementService.isEnabled() && (currentAccount && currentAccount.accountName !== accountName)) { // accounts are switched while sync is enabled. } this.currentSessionId = sessionId; - this.telemetryService.publicLog2('sync.userAccount', { id: sessionId.split('/')[1] }); + this.telemetryService.publicLog2('sync.userAccount', { id: accountId }); await this.update(); } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts index 3c9408abc2..06dc2cb2fc 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts @@ -5,7 +5,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IViewsRegistry, Extensions, ITreeViewDescriptor, ITreeViewDataProvider, ITreeItem, TreeItemCollapsibleState, IViewsService, TreeViewItemHandleArg, IViewContainersRegistry, ViewContainerLocation, ViewContainer } from 'vs/workbench/common/views'; +import { IViewsRegistry, Extensions, ITreeViewDescriptor, ITreeViewDataProvider, ITreeItem, TreeItemCollapsibleState, IViewsService, TreeViewItemHandleArg, IViewContainersRegistry, ViewContainerLocation, ViewContainer, IViewDescriptorService } from 'vs/workbench/common/views'; import { localize } from 'vs/nls'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { TreeViewPane, TreeView } from 'vs/workbench/browser/parts/views/treeView'; @@ -89,8 +89,24 @@ export class UserDataSyncViewContribution implements IWorkbenchContribution { }); } async run(accessor: ServicesAccessor): Promise { + const viewDescriptorService = accessor.get(IViewDescriptorService); + const viewsService = accessor.get(IViewsService); viewEnablementContext.set(true); - accessor.get(IViewsService).openView(id, true); + const viewContainer = viewDescriptorService.getViewContainerByViewId(id); + if (viewContainer) { + const model = viewDescriptorService.getViewContainerModel(viewContainer); + if (model.activeViewDescriptors.some(viewDescriptor => viewDescriptor.id === id)) { + viewsService.openView(id, true); + } else { + const disposable = model.onDidChangeActiveViewDescriptors(e => { + if (e.added.some(viewDescriptor => viewDescriptor.id === id)) { + disposable.dispose(); + viewsService.openView(id, true); + } + }); + } + + } } }); diff --git a/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts b/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts index 65f4e0004b..10949fb502 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts @@ -267,13 +267,13 @@ export class WebviewEditorService implements IWebviewWorkbenchService { public shouldPersist( webview: WebviewInput ): boolean { - if (Iterable.some(this._revivers.values(), reviver => canRevive(reviver, webview))) { + // Revived webviews may not have an actively registered reviver but we still want to presist them + // since a reviver should exist when it is actually needed. + if (webview instanceof LazilyResolvedWebviewEditorInput) { return true; } - // Revived webviews may not have an actively registered reviver but we still want to presist them - // since a reviver should exist when it is actually needed. - return webview instanceof LazilyResolvedWebviewEditorInput; + return Iterable.some(this._revivers.values(), reviver => canRevive(reviver, webview)); } private async tryRevive( diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index 935170e195..9f56897625 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -215,7 +215,9 @@ class WebviewKeyboardHandler { private setIgnoreMenuShortcutsForWebview(webview: WebviewTagHandle, value: boolean) { if (this.shouldToggleMenuShortcutsEnablement) { const contents = webview.webContents; - contents?.setIgnoreMenuShortcuts(value); + if (!contents?.isDestroyed()) { + contents?.setIgnoreMenuShortcuts(value); + } } } } diff --git a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts index 6cc22c39bb..4853b88c26 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts @@ -45,6 +45,8 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import 'sql/workbench/contrib/welcome/page/browser/az_data_welcome_page'; // {{SQL CARBON EDIT}} import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IProductService } from 'vs/platform/product/common/productService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorOptions } from 'vs/platform/editor/common/editor'; const configurationKey = 'workbench.startupEditor'; const oldConfigurationKey = 'workbench.welcome.enabled'; @@ -60,13 +62,13 @@ export class WelcomePageContribution implements IWorkbenchContribution { @IFileService fileService: IFileService, @IWorkspaceContextService contextService: IWorkspaceContextService, @ILifecycleService lifecycleService: ILifecycleService, + @IEditorService editorGroupsService: IEditorGroupsService, @ICommandService private readonly commandService: ICommandService, ) { const enabled = isWelcomePageEnabled(configurationService, contextService); if (enabled && lifecycleService.startupKind !== StartupKind.ReloadedWindow) { backupFileService.hasBackups().then(hasBackups => { - const activeEditor = editorService.activeEditor; - if (!activeEditor && !hasBackups) { + if (!editorGroupsService.willRestoreEditors && !hasBackups) { const openWithReadme = configurationService.getValue(configurationKey) === 'readme'; if (openWithReadme) { return Promise.all(contextService.getWorkspace().folders.map(folder => { @@ -98,7 +100,18 @@ export class WelcomePageContribution implements IWorkbenchContribution { return undefined; }); } else { - return instantiationService.createInstance(WelcomePage).openEditor(); + let options: IEditorOptions; + let editor = editorService.activeEditor; + if (editor) { + // Ensure that the welcome editor won't get opened more than once + if (editor.getTypeId() === welcomeInputTypeId || editorService.editors.some(e => e.getTypeId() === welcomeInputTypeId)) { + return undefined; + } + options = { pinned: false, index: 0 }; + } else { + options = { pinned: false }; + } + return instantiationService.createInstance(WelcomePage).openEditor(options); } } return undefined; @@ -289,8 +302,8 @@ class WelcomePage extends Disposable { }); } - public openEditor() { - return this.editorService.openEditor(this.editorInput, { pinned: false }); + public openEditor(options: IEditorOptions = { pinned: false }) { + return this.editorService.openEditor(this.editorInput, options); } private onReady(container: HTMLElement, recentlyOpened: Promise, installedExtensions: Promise): void { diff --git a/src/vs/workbench/services/activity/browser/activityService.ts b/src/vs/workbench/services/activity/browser/activityService.ts index c71a257c66..9419fb8b67 100644 --- a/src/vs/workbench/services/activity/browser/activityService.ts +++ b/src/vs/workbench/services/activity/browser/activityService.ts @@ -4,10 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { IActivityService, IBadge } from 'vs/workbench/services/activity/common/activity'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { IActivityService, IActivity } from 'vs/workbench/services/activity/common/activity'; +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; +import { GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity'; export class ActivityService implements IActivityService { @@ -15,16 +17,27 @@ export class ActivityService implements IActivityService { constructor( @IPanelService private readonly panelService: IPanelService, - @IActivityBarService private readonly activityBarService: IActivityBarService + @IActivityBarService private readonly activityBarService: IActivityBarService, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, ) { } - showActivity(compositeOrActionId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable { - if (this.panelService.getPanels().filter(p => p.id === compositeOrActionId).length) { - return this.panelService.showActivity(compositeOrActionId, badge, clazz); + showViewContainerActivity(viewContainerId: string, { badge, clazz, priority }: IActivity): IDisposable { + const viewContainer = this.viewDescriptorService.getViewContainerById(viewContainerId); + if (viewContainer) { + const location = this.viewDescriptorService.getViewContainerLocation(viewContainer); + switch (location) { + case ViewContainerLocation.Panel: + return this.panelService.showActivity(viewContainer.id, badge, clazz); + case ViewContainerLocation.Sidebar: + return this.activityBarService.showActivity(viewContainer.id, badge, clazz, priority); + } } + return Disposable.None; + } - return this.activityBarService.showActivity(compositeOrActionId, badge, clazz, priority); + showGlobalActivity({ badge, clazz, priority }: IActivity): IDisposable { + return this.activityBarService.showActivity(GLOBAL_ACTIVITY_ID, badge, clazz, priority); } } -registerSingleton(IActivityService, ActivityService, true); \ No newline at end of file +registerSingleton(IActivityService, ActivityService, true); diff --git a/src/vs/workbench/services/activity/common/activity.ts b/src/vs/workbench/services/activity/common/activity.ts index aa0e64f4b6..0942a6dd07 100644 --- a/src/vs/workbench/services/activity/common/activity.ts +++ b/src/vs/workbench/services/activity/common/activity.ts @@ -3,8 +3,16 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; +import { Event } from 'vs/base/common/event'; + +export interface IActivity { + readonly badge: IBadge; + readonly clazz?: string; + readonly priority?: number; +} export const IActivityService = createDecorator('activityService'); @@ -13,9 +21,47 @@ export interface IActivityService { _serviceBrand: undefined; /** - * Show activity in the panel for the given panel or in the activitybar for the given viewlet or global action. + * Show activity for the given view container */ - showActivity(compositeOrActionId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable; + showViewContainerActivity(viewContainerId: string, badge: IActivity): IDisposable; + + /** + * Show global activity + */ + showGlobalActivity(activity: IActivity): IDisposable; +} + +export class ViewContaierActivityByView extends Disposable { + + private activity: IActivity | undefined = undefined; + private activityDisposable: IDisposable = Disposable.None; + + constructor( + private readonly viewId: string, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, + @IActivityService private readonly activityService: IActivityService, + ) { + super(); + this._register(Event.filter(this.viewDescriptorService.onDidChangeContainer, e => e.views.some(view => view.id === viewId))(() => this.update())); + this._register(Event.filter(this.viewDescriptorService.onDidChangeLocation, e => e.views.some(view => view.id === viewId))(() => this.update())); + } + + setActivity(activity: IActivity): void { + this.activity = activity; + this.update(); + } + + private update(): void { + this.activityDisposable.dispose(); + const container = this.viewDescriptorService.getViewContainerByViewId(this.viewId); + if (container && this.activity) { + this.activityDisposable = this.activityService.showViewContainerActivity(container.id, this.activity); + } + } + + dispose() { + this.activityDisposable.dispose(); + } } export interface IBadge { diff --git a/src/vs/workbench/services/configurationResolver/common/variableResolver.ts b/src/vs/workbench/services/configurationResolver/common/variableResolver.ts index a54f09b52a..1912876413 100644 --- a/src/vs/workbench/services/configurationResolver/common/variableResolver.ts +++ b/src/vs/workbench/services/configurationResolver/common/variableResolver.ts @@ -225,7 +225,7 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe return normalizeDriveLetter(getFolderUri().fsPath); case 'cwd': - return (folderUri ? normalizeDriveLetter(getFolderUri().fsPath) : process.cwd()); + return ((folderUri || argument) ? normalizeDriveLetter(getFolderUri().fsPath) : process.cwd()); case 'workspaceRootFolderName': case 'workspaceFolderBasename': @@ -249,14 +249,14 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe return getFilePath(); case 'relativeFile': - if (folderUri) { + if (folderUri || argument) { return paths.normalize(paths.relative(getFolderUri().fsPath, getFilePath())); } return getFilePath(); case 'relativeFileDirname': - let dirname = paths.dirname(getFilePath()); - if (folderUri) { + const dirname = paths.dirname(getFilePath()); + if (folderUri || argument) { return paths.normalize(paths.relative(getFolderUri().fsPath, dirname)); } return dirname; diff --git a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts index 10222aad2d..541e228c5b 100644 --- a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts +++ b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts @@ -11,7 +11,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { BaseConfigurationResolverService } from 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; -import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { Workspace, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { TestEditorService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestWindowConfiguration } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; @@ -23,6 +23,7 @@ import { EditorType } from 'vs/editor/common/editorCommon'; import { Selection } from 'vs/editor/common/core/selection'; import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; +import { testWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; const mockLineNumber = 10; class TestEditorServiceWithActiveEditor extends TestEditorService { @@ -36,6 +37,13 @@ class TestEditorServiceWithActiveEditor extends TestEditorService { } }; } + get activeEditor(): any { + return { + get resource(): any { + return uri.parse('file:///VSCode/workspaceLocation/file'); + } + }; + } } class TestConfigurationResolverService extends BaseConfigurationResolverService { @@ -48,6 +56,7 @@ suite('Configuration Resolver Service', () => { let environmentService: MockWorkbenchEnvironmentService; let mockCommandService: MockCommandService; let editorService: TestEditorServiceWithActiveEditor; + let containingWorkspace: Workspace; let workspace: IWorkspaceFolder; let quickInputService: MockQuickInputService; @@ -56,13 +65,9 @@ suite('Configuration Resolver Service', () => { editorService = new TestEditorServiceWithActiveEditor(); quickInputService = new MockQuickInputService(); environmentService = new MockWorkbenchEnvironmentService(envVariables); - workspace = { - uri: uri.parse('file:///VSCode/workspaceLocation'), - name: 'hey', - index: 0, - toResource: (path: string) => uri.file(path) - }; - configurationResolverService = new TestConfigurationResolverService({ getExecPath: () => undefined }, environmentService.userEnv, editorService, new MockInputsConfigurationService(), mockCommandService, new TestContextService(), quickInputService); + containingWorkspace = testWorkspace(uri.parse('file:///VSCode/workspaceLocation')); + workspace = containingWorkspace.folders[0]; + configurationResolverService = new TestConfigurationResolverService({ getExecPath: () => undefined }, environmentService.userEnv, editorService, new MockInputsConfigurationService(), mockCommandService, new TestContextService(containingWorkspace), quickInputService); }); teardown(() => { @@ -77,6 +82,34 @@ suite('Configuration Resolver Service', () => { } }); + test('workspace folder with argument', () => { + if (platform.isWindows) { + assert.strictEqual(configurationResolverService!.resolve(workspace, 'abc ${workspaceFolder:workspaceLocation} xyz'), 'abc \\VSCode\\workspaceLocation xyz'); + } else { + assert.strictEqual(configurationResolverService!.resolve(workspace, 'abc ${workspaceFolder:workspaceLocation} xyz'), 'abc /VSCode/workspaceLocation xyz'); + } + }); + + test('workspace folder with invalid argument', () => { + assert.throws(() => configurationResolverService!.resolve(workspace, 'abc ${workspaceFolder:invalidLocation} xyz')); + }); + + test('workspace folder with undefined workspace folder', () => { + assert.throws(() => configurationResolverService!.resolve(undefined, 'abc ${workspaceFolder} xyz')); + }); + + test('workspace folder with argument and undefined workspace folder', () => { + if (platform.isWindows) { + assert.strictEqual(configurationResolverService!.resolve(undefined, 'abc ${workspaceFolder:workspaceLocation} xyz'), 'abc \\VSCode\\workspaceLocation xyz'); + } else { + assert.strictEqual(configurationResolverService!.resolve(undefined, 'abc ${workspaceFolder:workspaceLocation} xyz'), 'abc /VSCode/workspaceLocation xyz'); + } + }); + + test('workspace folder with invalid argument and undefined workspace folder', () => { + assert.throws(() => configurationResolverService!.resolve(undefined, 'abc ${workspaceFolder:invalidLocation} xyz')); + }); + test('workspace root folder name', () => { assert.strictEqual(configurationResolverService!.resolve(workspace, 'abc ${workspaceRootFolderName} xyz'), 'abc workspaceLocation xyz'); }); @@ -85,6 +118,34 @@ suite('Configuration Resolver Service', () => { assert.strictEqual(configurationResolverService!.resolve(workspace, 'abc ${lineNumber} xyz'), `abc ${mockLineNumber} xyz`); }); + test('relative file', () => { + assert.strictEqual(configurationResolverService!.resolve(workspace, 'abc ${relativeFile} xyz'), 'abc file xyz'); + }); + + test('relative file with argument', () => { + assert.strictEqual(configurationResolverService!.resolve(workspace, 'abc ${relativeFile:workspaceLocation} xyz'), 'abc file xyz'); + }); + + test('relative file with invalid argument', () => { + assert.throws(() => configurationResolverService!.resolve(workspace, 'abc ${relativeFile:invalidLocation} xyz')); + }); + + test('relative file with undefined workspace folder', () => { + if (platform.isWindows) { + assert.strictEqual(configurationResolverService!.resolve(undefined, 'abc ${relativeFile} xyz'), 'abc \\VSCode\\workspaceLocation\\file xyz'); + } else { + assert.strictEqual(configurationResolverService!.resolve(undefined, 'abc ${relativeFile} xyz'), 'abc /VSCode/workspaceLocation/file xyz'); + } + }); + + test('relative file with argument and undefined workspace folder', () => { + assert.strictEqual(configurationResolverService!.resolve(undefined, 'abc ${relativeFile:workspaceLocation} xyz'), 'abc file xyz'); + }); + + test('relative file with invalid argument and undefined workspace folder', () => { + assert.throws(() => configurationResolverService!.resolve(undefined, 'abc ${relativeFile:invalidLocation} xyz')); + }); + test('substitute many', () => { if (platform.isWindows) { assert.strictEqual(configurationResolverService!.resolve(workspace, '${workspaceFolder} - ${workspaceFolder}'), '\\VSCode\\workspaceLocation - \\VSCode\\workspaceLocation'); diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index 35fb1b3790..c3dec1561e 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -34,7 +34,7 @@ import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/u import { timeout } from 'vs/base/common/async'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { indexOfPath } from 'vs/base/common/extpath'; -import { DEFAULT_CUSTOM_EDITOR, updateViewTypeSchema, editorAssociationsConfigurationNode } from 'vs/workbench/services/editor/browser/editorAssociationsSetting'; +import { DEFAULT_CUSTOM_EDITOR, updateViewTypeSchema, editorAssociationsConfigurationNode } from 'vs/workbench/services/editor/common/editorAssociationsSetting'; import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; type CachedEditorInput = ResourceEditorInput | IFileEditorInput | UntitledTextEditorInput; @@ -480,10 +480,10 @@ export class EditorService extends Disposable implements EditorServiceImpl { return toDisposable(() => remove()); } - getEditorOverrides(editorInput: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup | undefined): [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry][] { + getEditorOverrides(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined): [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry][] { const ret = []; for (const handler of this.openEditorHandlers) { - const handlers = handler.getEditorOverrides ? handler.getEditorOverrides(editorInput, options, group).map(val => { return [handler, val] as [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry]; }) : []; + const handlers = handler.getEditorOverrides ? handler.getEditorOverrides(resource, options, group).map(val => { return [handler, val] as [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry]; }) : []; ret.push(...handlers); } @@ -1165,8 +1165,8 @@ export class DelegatingEditorService implements IEditorService { @IEditorService private editorService: EditorService ) { } - getEditorOverrides(editorInput: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup | undefined) { - return []; + getEditorOverrides(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined) { + return this.editorService.getEditorOverrides(resource, options, group); } openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: OpenInEditorGroup): Promise; diff --git a/src/vs/workbench/services/editor/browser/editorAssociationsSetting.ts b/src/vs/workbench/services/editor/common/editorAssociationsSetting.ts similarity index 100% rename from src/vs/workbench/services/editor/browser/editorAssociationsSetting.ts rename to src/vs/workbench/services/editor/common/editorAssociationsSetting.ts diff --git a/src/vs/workbench/services/editor/common/editorService.ts b/src/vs/workbench/services/editor/common/editorService.ts index d6b9915b8c..2597345d94 100644 --- a/src/vs/workbench/services/editor/common/editorService.ts +++ b/src/vs/workbench/services/editor/common/editorService.ts @@ -10,6 +10,7 @@ import { Event } from 'vs/base/common/event'; import { IEditor, IDiffEditor } from 'vs/editor/common/editorCommon'; import { IEditorGroup, IEditorReplacement } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IDisposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; export const IEditorService = createDecorator('editorService'); @@ -35,7 +36,7 @@ export interface IOpenEditorOverrideEntry { export interface IOpenEditorOverrideHandler { open(editor: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup, id?: string): IOpenEditorOverride | undefined; - getEditorOverrides?(editor: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup | undefined): IOpenEditorOverrideEntry[]; + getEditorOverrides?(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined): IOpenEditorOverrideEntry[]; } export interface IOpenEditorOverride { @@ -224,7 +225,7 @@ export interface IEditorService { /** * Get all available editor overrides for the editor input. */ - getEditorOverrides(editorInput: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup | undefined): [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry][]; + getEditorOverrides(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined): [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry][]; /** * Allows to override the opening of editors by installing a handler that will diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index cd5a01ca18..8dc5f142ab 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -254,4 +254,6 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment return extensionHostDebugEnvironment; } + + get skipReleaseNotes(): boolean { return false; } } diff --git a/src/vs/workbench/services/environment/common/environmentService.ts b/src/vs/workbench/services/environment/common/environmentService.ts index bc34b5b4b1..9c7bdb0ddb 100644 --- a/src/vs/workbench/services/environment/common/environmentService.ts +++ b/src/vs/workbench/services/environment/common/environmentService.ts @@ -28,4 +28,6 @@ export interface IWorkbenchEnvironmentService extends IEnvironmentService { readonly webviewExternalEndpoint: string; readonly webviewResourceRoot: string; readonly webviewCspSource: string; + + readonly skipReleaseNotes: boolean; } diff --git a/src/vs/workbench/services/environment/electron-browser/environmentService.ts b/src/vs/workbench/services/environment/electron-browser/environmentService.ts index b8f560cd0f..5f5e0db688 100644 --- a/src/vs/workbench/services/environment/electron-browser/environmentService.ts +++ b/src/vs/workbench/services/environment/electron-browser/environmentService.ts @@ -55,6 +55,9 @@ export class NativeWorkbenchEnvironmentService extends EnvironmentService implem @memoize get extHostLogsPath(): URI { return URI.file(join(this.logsPath, `exthost${this.configuration.windowId}`)); } + @memoize + get skipReleaseNotes(): boolean { return !!this.args['skip-release-notes']; } + constructor( readonly configuration: INativeEnvironmentConfiguration, execPath: string diff --git a/src/vs/workbench/services/extensionManagement/electron-browser/extensionTipsService.ts b/src/vs/workbench/services/extensionManagement/electron-browser/extensionTipsService.ts index 94c2911f4a..7e173b9834 100644 --- a/src/vs/workbench/services/extensionManagement/electron-browser/extensionTipsService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-browser/extensionTipsService.ts @@ -8,21 +8,35 @@ import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedPr import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { IExtensionTipsService, IExecutableBasedExtensionTip, IWorkspaceTips, IConfigBasedExtensionTip } from 'vs/platform/extensionManagement/common/extensionManagement'; import { URI } from 'vs/base/common/uri'; +import { ExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionTipsService'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IRequestService } from 'vs/platform/request/common/request'; +import { ILogService } from 'vs/platform/log/common/log'; +import { Schemas } from 'vs/base/common/network'; -class NativeExtensionTipsService implements IExtensionTipsService { +class NativeExtensionTipsService extends ExtensionTipsService implements IExtensionTipsService { _serviceBrand: any; private readonly channel: IChannel; constructor( + @IFileService fileService: IFileService, + @IProductService productService: IProductService, + @IRequestService requestService: IRequestService, + @ILogService logService: ILogService, @ISharedProcessService sharedProcessService: ISharedProcessService ) { + super(fileService, productService, requestService, logService); this.channel = sharedProcessService.getChannel('extensionTipsService'); } getConfigBasedTips(folder: URI): Promise { - return this.channel.call('getConfigBasedTips', [folder]); + if (folder.scheme === Schemas.file) { + return this.channel.call('getConfigBasedTips', [folder]); + } + return super.getConfigBasedTips(folder); } getImportantExecutableBasedTips(): Promise { diff --git a/src/vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc.ts b/src/vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc.ts index 846d4a96c7..e831751888 100644 --- a/src/vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc.ts +++ b/src/vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc.ts @@ -19,6 +19,8 @@ import { localize } from 'vs/nls'; import { IProductService } from 'vs/platform/product/common/productService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; +import { generateUuid } from 'vs/base/common/uuid'; +import { joinPath } from 'vs/base/common/resources'; export class RemoteExtensionManagementChannelClient extends ExtensionManagementChannelClient { @@ -84,7 +86,8 @@ export class RemoteExtensionManagementChannelClient extends ExtensionManagementC } private async downloadAndInstall(extension: IGalleryExtension, installed: ILocalExtension[]): Promise { - const location = await this.galleryService.download(extension, URI.file(tmpdir()), installed.filter(i => areSameExtensions(i.identifier, extension.identifier))[0] ? InstallOperation.Update : InstallOperation.Install); + const location = joinPath(URI.file(tmpdir()), generateUuid()); + await this.galleryService.download(extension, location, installed.filter(i => areSameExtensions(i.identifier, extension.identifier))[0] ? InstallOperation.Update : InstallOperation.Install); return super.install(location); } diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index e9627cfb24..02f06bbd0f 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -117,14 +117,34 @@ export class BrowserHostService extends Disposable implements IHostService { const openable = toOpen[i]; openable.label = openable.label || this.getRecentLabel(openable); + // selectively copy payload: for now only extension debugging properties are considered + const originalPayload = this.workspaceProvider.payload; + let newPayload: Array | undefined = undefined; + if (originalPayload && Array.isArray(originalPayload)) { + for (let pair of originalPayload) { + if (Array.isArray(pair) && pair.length === 2) { + switch (pair[0]) { + case 'extensionDevelopmentPath': + case 'debugId': + case 'inspect-brk-extensions': + if (!newPayload) { + newPayload = new Array(); + } + newPayload.push(pair); + break; + } + } + } + } + // Folder if (isFolderToOpen(openable)) { - this.workspaceProvider.open({ folderUri: openable.folderUri }, { reuse: this.shouldReuse(options, false /* no file */) }); + this.workspaceProvider.open({ folderUri: openable.folderUri }, { reuse: this.shouldReuse(options, false /* no file */), payload: newPayload }); } // Workspace else if (isWorkspaceToOpen(openable)) { - this.workspaceProvider.open({ workspaceUri: openable.workspaceUri }, { reuse: this.shouldReuse(options, false /* no file */) }); + this.workspaceProvider.open({ workspaceUri: openable.workspaceUri }, { reuse: this.shouldReuse(options, false /* no file */), payload: newPayload }); } // File diff --git a/src/vs/workbench/services/keybinding/common/keybindingIO.ts b/src/vs/workbench/services/keybinding/common/keybindingIO.ts index 00022acb86..4b959b5c15 100644 --- a/src/vs/workbench/services/keybinding/common/keybindingIO.ts +++ b/src/vs/workbench/services/keybinding/common/keybindingIO.ts @@ -31,12 +31,16 @@ export class KeybindingIO { if (quotedSerializedWhen.length > 0) { out.write(`${quotedSerializeCommand},`); out.writeLine(); - out.write(` "when": ${quotedSerializedWhen} `); + out.write(` "when": ${quotedSerializedWhen}`); } else { - out.write(`${quotedSerializeCommand} `); + out.write(`${quotedSerializeCommand}`); } - // out.write(String(item.weight1 + '-' + item.weight2)); - out.write('}'); + if (item.commandArgs) { + out.write(','); + out.writeLine(); + out.write(` "args": ${JSON.stringify(item.commandArgs)}`); + } + out.write(' }'); } public static readUserKeybindingItem(input: IUserFriendlyKeybinding): IUserKeybindingItem { diff --git a/src/vs/workbench/services/preferences/common/preferences.ts b/src/vs/workbench/services/preferences/common/preferences.ts index 0282ecb737..294c52baa7 100644 --- a/src/vs/workbench/services/preferences/common/preferences.ts +++ b/src/vs/workbench/services/preferences/common/preferences.ts @@ -59,6 +59,7 @@ export interface ISetting { overrides?: ISetting[]; overrideOf?: ISetting; deprecationMessage?: string; + deprecationMessageIsMarkdown?: boolean; scope?: ConfigurationScope; type?: string | string[]; diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index 578f93179b..40273298a7 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -639,7 +639,8 @@ export class DefaultSettings extends Disposable { tags: prop.tags, disallowSyncIgnore: prop.disallowSyncIgnore, extensionInfo: extensionInfo, - deprecationMessage: prop.deprecationMessage, + deprecationMessage: prop.markdownDeprecationMessage || prop.deprecationMessage, + deprecationMessageIsMarkdown: !!prop.markdownDeprecationMessage, validator: createValidator(prop) }); } diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index 2078a32cc8..f9955912b7 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -75,7 +75,7 @@ export class ProgressService extends Disposable implements IProgressService { // Window progress without command can be shown as silent notification // which will first appear in the status bar and can then be brought to // the front when clicking. - return this.withNotificationProgress({ ...options, silent: true, location: ProgressLocation.Notification }, task, onDidCancel); + return this.withNotificationProgress({ delay: 150 /* default for ProgressLocation.Window */, ...options, silent: true, location: ProgressLocation.Notification }, task, onDidCancel); case ProgressLocation.Explorer: return this.withViewletProgress('workbench.view.explorer', task, { ...options, location }); case ProgressLocation.Scm: @@ -440,7 +440,7 @@ export class ProgressService extends Disposable implements IProgressService { let activityProgress: IDisposable; let delayHandle: any = setTimeout(() => { delayHandle = undefined; - const handle = this.activityService.showActivity(viewletId, new ProgressBadge(() => ''), 'progress-badge', 100); + const handle = this.activityService.showViewContainerActivity(viewletId, { badge: new ProgressBadge(() => ''), clazz: 'progress-badge', priority: 100 }); const startTimeVisible = Date.now(); const minTimeVisible = 300; activityProgress = { diff --git a/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts b/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts index 4a3b664131..a55d5bd71a 100644 --- a/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts +++ b/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts @@ -30,7 +30,7 @@ class TestViewlet implements IViewlet { getControl(): IEditorControl { return null!; } focus(): void { } getOptimalWidth(): number { return 10; } - openView(id: string, focus?: boolean): IView { return null!; } + openView(id: string, focus?: boolean): T | undefined { return undefined; } getViewPaneContainer(): IViewPaneContainer { return null!; } saveState(): void { } } diff --git a/src/vs/workbench/services/telemetry/browser/telemetryService.ts b/src/vs/workbench/services/telemetry/browser/telemetryService.ts index 1e793ddc4d..d5ad549cb1 100644 --- a/src/vs/workbench/services/telemetry/browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/browser/telemetryService.ts @@ -50,7 +50,8 @@ export class TelemetryService extends Disposable implements ITelemetryService { if (!!productService.enableTelemetry) { const config: ITelemetryServiceConfig = { appender: combinedAppender(new WebTelemetryAppender(logService, remoteAgentService), new LogAppender(logService)), - commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.configuration.remoteAuthority, environmentService.options && environmentService.options.resolveCommonTelemetryProperties) + commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.configuration.remoteAuthority, environmentService.options && environmentService.options.resolveCommonTelemetryProperties), + sendErrorTelemetry: false, }; this.impl = this._register(new BaseTelemetryService(config, configurationService)); @@ -75,6 +76,14 @@ export class TelemetryService extends Disposable implements ITelemetryService { return this.publicLog(eventName, data as ITelemetryData, anonymizeFilePaths); } + publicLogError(errorEventName: string, data?: ITelemetryData): Promise { + return this.impl.publicLog(errorEventName, data); + } + + publicLogError2 = never, T extends GDPRClassification = never>(eventName: string, data?: StrictPropertyCheck) { + return this.publicLogError(eventName, data as ITelemetryData); + } + getTelemetryInfo(): Promise { return this.impl.getTelemetryInfo(); } diff --git a/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts index 5e59819b6c..9d77fb0832 100644 --- a/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts @@ -40,7 +40,8 @@ export class TelemetryService extends Disposable implements ITelemetryService { const config: ITelemetryServiceConfig = { appender: combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(logService)), commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.configuration.machineId, productService.msftInternalDomains, environmentService.installSourcePath, environmentService.configuration.remoteAuthority), - piiPaths: environmentService.extensionsPath ? [environmentService.appRoot, environmentService.extensionsPath] : [environmentService.appRoot] + piiPaths: environmentService.extensionsPath ? [environmentService.appRoot, environmentService.extensionsPath] : [environmentService.appRoot], + sendErrorTelemetry: true }; this.impl = this._register(new BaseTelemetryService(config, configurationService)); @@ -65,6 +66,15 @@ export class TelemetryService extends Disposable implements ITelemetryService { return this.publicLog(eventName, data as ITelemetryData, anonymizeFilePaths); } + publicLogError(errorEventName: string, data?: ITelemetryData): Promise { + return this.impl.publicLogError(errorEventName, data); + } + + publicLogError2 = never, T extends GDPRClassification = never>(eventName: string, data?: StrictPropertyCheck) { + return this.publicLog(eventName, data as ITelemetryData); + } + + getTelemetryInfo(): Promise { return this.impl.getTelemetryInfo(); } diff --git a/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts index 2a5da6fc6e..3b7ebe7159 100644 --- a/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts +++ b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts @@ -30,6 +30,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IValidGrammarDefinition, IValidEmbeddedLanguagesMap, IValidTokenTypeMap } from 'vs/workbench/services/textMate/common/TMScopeRegistry'; import { TMGrammarFactory } from 'vs/workbench/services/textMate/common/TMGrammarFactory'; import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; export abstract class AbstractTextMateService extends Disposable implements ITextMateService { public _serviceBrand: undefined; @@ -41,6 +42,9 @@ export abstract class AbstractTextMateService extends Disposable implements ITex private readonly _createdModes: string[]; private readonly _encounteredLanguages: boolean[]; + private _debugMode: boolean; + private _debugModePrintFunc: (str: string) => void; + private _grammarDefinitions: IValidGrammarDefinition[] | null; private _grammarFactory: TMGrammarFactory | null; private _tokenizersRegistrations: IDisposable[]; @@ -54,7 +58,8 @@ export abstract class AbstractTextMateService extends Disposable implements ITex @INotificationService private readonly _notificationService: INotificationService, @ILogService private readonly _logService: ILogService, @IConfigurationService private readonly _configurationService: IConfigurationService, - @IStorageService private readonly _storageService: IStorageService + @IStorageService private readonly _storageService: IStorageService, + @IProgressService private readonly _progressService: IProgressService ) { super(); this._styleElement = dom.createStyleSheet(); @@ -62,6 +67,9 @@ export abstract class AbstractTextMateService extends Disposable implements ITex this._createdModes = []; this._encounteredLanguages = []; + this._debugMode = false; + this._debugModePrintFunc = () => { }; + this._grammarDefinitions = null; this._grammarFactory = null; this._tokenizersRegistrations = []; @@ -174,6 +182,46 @@ export abstract class AbstractTextMateService extends Disposable implements ITex }); } + public startDebugMode(printFn: (str: string) => void, onStop: () => void): void { + if (this._debugMode) { + this._notificationService.error(nls.localize('alreadyDebugging', "Already Logging.")); + return; + } + + this._debugModePrintFunc = printFn; + this._debugMode = true; + + if (this._debugMode) { + this._progressService.withProgress( + { + location: ProgressLocation.Notification, + buttons: [nls.localize('stop', "Stop")] + }, + (progress) => { + progress.report({ + message: nls.localize('progress1', "Preparing to log TM Grammar parsing. Press Stop when finished.") + }); + + return this._getVSCodeOniguruma().then((vscodeOniguruma) => { + vscodeOniguruma.setDefaultDebugCall(true); + progress.report({ + message: nls.localize('progress2', "Now logging TM Grammar parsing. Press Stop when finished.") + }); + return new Promise((resolve, reject) => { }); + }); + }, + (choice) => { + this._getVSCodeOniguruma().then((vscodeOniguruma) => { + this._debugModePrintFunc = () => { }; + this._debugMode = false; + vscodeOniguruma.setDefaultDebugCall(false); + onStop(); + }); + } + ); + } + } + private _canCreateGrammarFactory(): boolean { // Check if extension point is ready return (this._grammarDefinitions ? true : false); @@ -184,7 +232,11 @@ export abstract class AbstractTextMateService extends Disposable implements ITex return this._grammarFactory; } - const vscodeTextmate = await this._loadVSCodeTextmate(); + const [vscodeTextmate, vscodeOniguruma] = await Promise.all([import('vscode-textmate'), this._getVSCodeOniguruma()]); + const onigLib: Promise = Promise.resolve({ + createOnigScanner: (sources: string[]) => vscodeOniguruma.createOnigScanner(sources), + createOnigString: (str: string) => vscodeOniguruma.createOnigString(str) + }); // Avoid duplicate instantiations if (this._grammarFactory) { @@ -195,7 +247,7 @@ export abstract class AbstractTextMateService extends Disposable implements ITex logTrace: (msg: string) => this._logService.trace(msg), logError: (msg: string, err: any) => this._logService.error(msg, err), readFile: (resource: URI) => this._extensionResourceLoaderService.readExtensionResource(resource) - }, this._grammarDefinitions || [], vscodeTextmate, this._loadOnigLib()); + }, this._grammarDefinitions || [], vscodeTextmate, onigLib); this._onDidCreateGrammarFactory(this._grammarDefinitions || []); this._updateTheme(this._grammarFactory, this._themeService.getColorTheme(), true); @@ -340,8 +392,27 @@ export abstract class AbstractTextMateService extends Disposable implements ITex protected _onDidDisposeGrammarFactory(): void { } - protected abstract _loadVSCodeTextmate(): Promise; - protected abstract _loadOnigLib(): Promise | undefined; + private _vscodeOniguruma: Promise | null = null; + private _getVSCodeOniguruma(): Promise { + if (!this._vscodeOniguruma) { + this._vscodeOniguruma = this._doGetVSCodeOniguruma(); + } + return this._vscodeOniguruma; + } + + private async _doGetVSCodeOniguruma(): Promise { + const [vscodeOniguruma, wasm] = await Promise.all([import('vscode-oniguruma'), this._loadVSCodeOnigurumWASM()]); + const options = { + data: wasm, + print: (str: string) => { + this._debugModePrintFunc(str); + } + }; + await vscodeOniguruma.loadWASM(options); + return vscodeOniguruma; + } + + protected abstract _loadVSCodeOnigurumWASM(): Promise; } const donotAskUpdateKey = 'editor.maxTokenizationLineLength.donotask'; diff --git a/src/vs/workbench/services/textMate/browser/textMateService.ts b/src/vs/workbench/services/textMate/browser/textMateService.ts index 0d5a26f663..0aa08090f3 100644 --- a/src/vs/workbench/services/textMate/browser/textMateService.ts +++ b/src/vs/workbench/services/textMate/browser/textMateService.ts @@ -6,7 +6,6 @@ import { ITextMateService } from 'vs/workbench/services/textMate/common/textMateService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { AbstractTextMateService } from 'vs/workbench/services/textMate/browser/abstractTextMateService'; -import { IOnigLib } from 'vscode-textmate'; import { IModeService } from 'vs/editor/common/services/modeService'; import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -14,6 +13,7 @@ import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/work import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; +import { IProgressService } from 'vs/platform/progress/common/progress'; export class TextMateService extends AbstractTextMateService { @@ -24,46 +24,20 @@ export class TextMateService extends AbstractTextMateService { @INotificationService notificationService: INotificationService, @ILogService logService: ILogService, @IConfigurationService configurationService: IConfigurationService, - @IStorageService storageService: IStorageService + @IStorageService storageService: IStorageService, + @IProgressService progressService: IProgressService ) { - super(modeService, themeService, extensionResourceLoaderService, notificationService, logService, configurationService, storageService); + super(modeService, themeService, extensionResourceLoaderService, notificationService, logService, configurationService, storageService, progressService); } - protected _loadVSCodeTextmate(): Promise { - return import('vscode-textmate'); + protected async _loadVSCodeOnigurumWASM(): Promise { + const wasmPath = require.toUrl('vscode-oniguruma/../onig.wasm'); + const response = await fetch(wasmPath); + // Using the response directly only works if the server sets the MIME type 'application/wasm'. + // Otherwise, a TypeError is thrown when using the streaming compiler. + // We therefore use the non-streaming compiler :(. + return await response.arrayBuffer(); } - - protected _loadOnigLib(): Promise | undefined { - return loadOnigasm(); - } -} - -let onigasmPromise: Promise | null = null; -async function loadOnigasm(): Promise { - if (!onigasmPromise) { - onigasmPromise = doLoadOnigasm(); - } - return onigasmPromise; -} - -async function doLoadOnigasm(): Promise { - const [wasmBytes, onigasm] = await Promise.all([ - loadOnigasmWASM(), - import('onigasm-umd') - ]); - - await onigasm.loadWASM(wasmBytes); - return { - createOnigScanner(patterns: string[]) { return new onigasm.OnigScanner(patterns); }, - createOnigString(s: string) { return new onigasm.OnigString(s); } - }; -} - -async function loadOnigasmWASM(): Promise { - const wasmPath = require.toUrl('onigasm-umd/../onigasm.wasm'); - const response = await fetch(wasmPath); - const bytes = await response.arrayBuffer(); - return bytes; } registerSingleton(ITextMateService, TextMateService); diff --git a/src/vs/workbench/services/textMate/common/TMGrammarFactory.ts b/src/vs/workbench/services/textMate/common/TMGrammarFactory.ts index d4f2ced0b3..d05c97f7f0 100644 --- a/src/vs/workbench/services/textMate/common/TMGrammarFactory.ts +++ b/src/vs/workbench/services/textMate/common/TMGrammarFactory.ts @@ -33,7 +33,7 @@ export class TMGrammarFactory extends Disposable { private readonly _languageToScope2: string[]; private readonly _grammarRegistry: Registry; - constructor(host: ITMGrammarFactoryHost, grammarDefinitions: IValidGrammarDefinition[], vscodeTextmate: typeof import('vscode-textmate'), onigLib: Promise | undefined) { + constructor(host: ITMGrammarFactoryHost, grammarDefinitions: IValidGrammarDefinition[], vscodeTextmate: typeof import('vscode-textmate'), onigLib: Promise) { super(); this._host = host; this._initialState = vscodeTextmate.INITIAL; @@ -41,8 +41,8 @@ export class TMGrammarFactory extends Disposable { this._injections = {}; this._injectedEmbeddedLanguages = {}; this._languageToScope2 = []; - this._grammarRegistry = new vscodeTextmate.Registry({ - getOnigLib: (typeof onigLib === 'undefined' ? undefined : () => onigLib), + this._grammarRegistry = this._register(new vscodeTextmate.Registry({ + onigLib: onigLib, loadGrammar: async (scopeName: string) => { const grammarDefinition = this._scopeRegistry.getGrammarDefinition(scopeName); if (!grammarDefinition) { @@ -67,7 +67,7 @@ export class TMGrammarFactory extends Disposable { } return injections; } - }); + })); for (const validGrammar of grammarDefinitions) { this._scopeRegistry.register(validGrammar); diff --git a/src/vs/workbench/services/textMate/common/cgmanifest.json b/src/vs/workbench/services/textMate/common/cgmanifest.json index 4dead8495b..f458b3cc96 100644 --- a/src/vs/workbench/services/textMate/common/cgmanifest.json +++ b/src/vs/workbench/services/textMate/common/cgmanifest.json @@ -5,41 +5,43 @@ "type": "other", "other": { "name": "lib-oniguruma", - "downloadUrl": "http://dl.fedoraproject.org/pub/epel/7/SRPMS/Packages/o/oniguruma-5.9.5-3.el7.src.rpm", - "version": "5.9.3" + "downloadUrl": "https://github.com/kkos/oniguruma", + "version": "6.9.5" } }, "licenseDetail": [ - "Copyright (c) 2002-2007 K.Kosako. All rights reserved.", + "Oniguruma LICENSE", + "-----------------", + "", + "Copyright (c) 2002-2020 K.Kosako ", + "All rights reserved.", "", "The BSD License", "", "Redistribution and use in source and binary forms, with or without", "modification, are permitted provided that the following conditions", "are met:", - "", "1. Redistributions of source code must retain the above copyright", " notice, this list of conditions and the following disclaimer.", - "", "2. Redistributions in binary form must reproduce the above copyright", " notice, this list of conditions and the following disclaimer in the", " documentation and/or other materials provided with the distribution.", "", "THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND", "ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE", - "IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR", - "PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS", - "BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR", - "CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF", - "SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR", - "BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,", - "WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE", - "OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN", - "IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." + "IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE", + "ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE", + "FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL", + "DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS", + "OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)", + "HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT", + "LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY", + "OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF", + "SUCH DAMAGE." ], "isOnlyProductionDependency": true, "license": "BSD", - "version": "5.9.3" + "version": "6.9.5" } ], "version": 1 diff --git a/src/vs/workbench/services/textMate/common/textMateService.ts b/src/vs/workbench/services/textMate/common/textMateService.ts index 207b2d07b8..7f0020c030 100644 --- a/src/vs/workbench/services/textMate/common/textMateService.ts +++ b/src/vs/workbench/services/textMate/common/textMateService.ts @@ -15,6 +15,8 @@ export interface ITextMateService { onDidEncounterLanguage: Event; createGrammar(modeId: string): Promise; + + startDebugMode(printFn: (str: string) => void, onStop: () => void): void; } // -------------- Types "liberated" from vscode-textmate due to usage in /common/ diff --git a/src/vs/workbench/services/textMate/electron-browser/textMateService.ts b/src/vs/workbench/services/textMate/electron-browser/textMateService.ts index 7a15f4676a..0d27b8f650 100644 --- a/src/vs/workbench/services/textMate/electron-browser/textMateService.ts +++ b/src/vs/workbench/services/textMate/electron-browser/textMateService.ts @@ -13,7 +13,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { createWebWorker, MonacoWebWorker } from 'vs/editor/common/services/webWorker'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IOnigLib, IRawTheme } from 'vscode-textmate'; +import { IRawTheme } from 'vscode-textmate'; import { IValidGrammarDefinition } from 'vs/workbench/services/textMate/common/TMScopeRegistry'; import { TextMateWorker } from 'vs/workbench/services/textMate/electron-browser/textMateWorker'; import { ITextModel } from 'vs/editor/common/model'; @@ -24,6 +24,8 @@ import { TMGrammarFactory } from 'vs/workbench/services/textMate/common/TMGramma import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IProgressService } from 'vs/platform/progress/common/progress'; const RUN_TEXTMATE_IN_WORKER = false; @@ -117,7 +119,7 @@ export class TextMateWorkerHost { constructor( private readonly textMateService: TextMateService, - @IExtensionResourceLoaderService private readonly _extensionResourceLoaderService: IExtensionResourceLoaderService, + @IExtensionResourceLoaderService private readonly _extensionResourceLoaderService: IExtensionResourceLoaderService ) { } @@ -146,9 +148,11 @@ export class TextMateService extends AbstractTextMateService { @ILogService logService: ILogService, @IConfigurationService configurationService: IConfigurationService, @IStorageService storageService: IStorageService, + @IProgressService progressService: IProgressService, @IModelService private readonly _modelService: IModelService, + @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, ) { - super(modeService, themeService, extensionResourceLoaderService, notificationService, logService, configurationService, storageService); + super(modeService, themeService, extensionResourceLoaderService, notificationService, logService, configurationService, storageService, progressService); this._worker = null; this._workerProxy = null; this._tokenizers = Object.create(null); @@ -177,12 +181,14 @@ export class TextMateService extends AbstractTextMateService { } } - protected _loadVSCodeTextmate(): Promise { - return import('vscode-textmate'); - } - - protected _loadOnigLib(): Promise | undefined { - return undefined; + protected async _loadVSCodeOnigurumWASM(): Promise { + const wasmPath = ( + this._environmentService.isBuilt + ? require.toUrl('../../../../../../node_modules.asar.unpacked/vscode-oniguruma/release/onig.wasm') + : require.toUrl('../../../../../../node_modules/vscode-oniguruma/release/onig.wasm') + ); + const response = await fetch(wasmPath); + return response; } protected _onDidCreateGrammarFactory(grammarDefinitions: IValidGrammarDefinition[]): void { diff --git a/src/vs/workbench/services/textMate/electron-browser/textMateWorker.ts b/src/vs/workbench/services/textMate/electron-browser/textMateWorker.ts index 098c592968..5708546c33 100644 --- a/src/vs/workbench/services/textMate/electron-browser/textMateWorker.ts +++ b/src/vs/workbench/services/textMate/electron-browser/textMateWorker.ts @@ -11,7 +11,7 @@ import { TMGrammarFactory, ICreateGrammarResult } from 'vs/workbench/services/te import { IModelChangedEvent, MirrorTextModel } from 'vs/editor/common/model/mirrorTextModel'; import { TextMateWorkerHost } from 'vs/workbench/services/textMate/electron-browser/textMateService'; import { TokenizationStateStore } from 'vs/editor/common/model/textModelTokens'; -import { IGrammar, StackElement, IRawTheme } from 'vscode-textmate'; +import { IGrammar, StackElement, IRawTheme, IOnigLib } from 'vscode-textmate'; import { MultilineTokensBuilder, countEOL } from 'vs/editor/common/model/tokensStore'; import { LineTokens } from 'vs/editor/common/core/lineTokens'; @@ -118,7 +118,7 @@ export class TextMateWorker { private readonly _host: TextMateWorkerHost; private readonly _models: { [uri: string]: TextMateWorkerModel; }; private readonly _grammarCache: Promise[]; - private readonly _grammarFactory: TMGrammarFactory | null; + private readonly _grammarFactory: Promise; constructor(ctx: IWorkerContext, createData: ICreateData) { this._host = ctx.host; @@ -134,24 +134,36 @@ export class TextMateWorker { injectTo: def.injectTo, }; }); + this._grammarFactory = this._loadTMGrammarFactory(grammarDefinitions); + } - const globalDefine = (self).define; - try { - (self).define.amd = undefined; - const vscodeTextmate = require.__$__nodeRequire('vscode-textmate'); + private async _loadTMGrammarFactory(grammarDefinitions: IValidGrammarDefinition[]): Promise { + require.config({ + paths: { + 'vscode-textmate': '../node_modules/vscode-textmate/release/main', + 'vscode-oniguruma': '../node_modules/vscode-oniguruma/release/main', + } + }); + const vscodeTextmate = await import('vscode-textmate'); + const vscodeOniguruma = await import('vscode-oniguruma'); + const wasmPath = require.toUrl('vscode-oniguruma/../onig.wasm'); + const response = await fetch(wasmPath); + // Using the response directly only works if the server sets the MIME type 'application/wasm'. + // Otherwise, a TypeError is thrown when using the streaming compiler. + // We therefore use the non-streaming compiler :(. + const bytes = await response.arrayBuffer(); + await vscodeOniguruma.loadWASM(bytes); - this._grammarFactory = new TMGrammarFactory({ - logTrace: (msg: string) => {/* console.log(msg) */ }, - logError: (msg: string, err: any) => console.error(msg, err), - readFile: (resource: URI) => this._host.readFile(resource) - }, grammarDefinitions, vscodeTextmate, undefined); - } catch (err) { - console.error(err); - this._grammarFactory = null; - return; - } finally { - (self).define = globalDefine; - } + const onigLib: Promise = Promise.resolve({ + createOnigScanner: (sources) => vscodeOniguruma.createOnigScanner(sources), + createOnigString: (str) => vscodeOniguruma.createOnigString(str) + }); + + return new TMGrammarFactory({ + logTrace: (msg: string) => {/* console.log(msg) */ }, + logError: (msg: string, err: any) => console.error(msg, err), + readFile: (resource: URI) => this._host.readFile(resource) + }, grammarDefinitions, vscodeTextmate, onigLib); } public acceptNewModel(data: IRawModelData): void { @@ -175,19 +187,21 @@ export class TextMateWorker { } } - public getOrCreateGrammar(languageId: LanguageId): Promise { - if (!this._grammarFactory) { + public async getOrCreateGrammar(languageId: LanguageId): Promise { + const grammarFactory = await this._grammarFactory; + if (!grammarFactory) { return Promise.resolve(null); } if (!this._grammarCache[languageId]) { - this._grammarCache[languageId] = this._grammarFactory.createGrammar(languageId); + this._grammarCache[languageId] = grammarFactory.createGrammar(languageId); } return this._grammarCache[languageId]; } - public acceptTheme(theme: IRawTheme, colorMap: string[]): void { - if (this._grammarFactory) { - this._grammarFactory.setTheme(theme, colorMap); + public async acceptTheme(theme: IRawTheme, colorMap: string[]): Promise { + const grammarFactory = await this._grammarFactory; + if (grammarFactory) { + grammarFactory.setTheme(theme, colorMap); } } diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index bb7f99e38c..d5f2f72722 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -220,8 +220,8 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { this.currentColorTheme.setCustomTokenColors(this.settings.tokenColorCustomizations); hasColorChanges = true; } - if (e.affectsConfiguration(ThemeSettings.TOKEN_COLOR_CUSTOMIZATIONS_EXPERIMENTAL)) { - this.currentColorTheme.setCustomTokenStyleRules(this.settings.tokenStylesCustomizations); + if (e.affectsConfiguration(ThemeSettings.SEMANTIC_TOKEN_COLOR_CUSTOMIZATIONS) || e.affectsConfiguration(ThemeSettings.TOKEN_COLOR_CUSTOMIZATIONS_EXPERIMENTAL)) { + this.currentColorTheme.setCustomSemanticTokenColors(this.settings.semanticTokenColorCustomizations, this.settings.experimentalSemanticTokenColorCustomizations); hasColorChanges = true; } if (hasColorChanges) { diff --git a/src/vs/workbench/services/themes/common/colorThemeData.ts b/src/vs/workbench/services/themes/common/colorThemeData.ts index 0e11c793fa..f370455aae 100644 --- a/src/vs/workbench/services/themes/common/colorThemeData.ts +++ b/src/vs/workbench/services/themes/common/colorThemeData.ts @@ -6,7 +6,7 @@ import { basename } from 'vs/base/common/path'; import * as Json from 'vs/base/common/json'; import { Color } from 'vs/base/common/color'; -import { ExtensionData, ITokenColorCustomizations, ITextMateThemingRule, IWorkbenchColorTheme, IColorMap, IThemeExtensionPoint, VS_LIGHT_THEME, VS_HC_THEME, IColorCustomizations, IExperimentalTokenStyleCustomizations, ITokenColorizationSetting } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { ExtensionData, ITokenColorCustomizations, ITextMateThemingRule, IWorkbenchColorTheme, IColorMap, IThemeExtensionPoint, VS_LIGHT_THEME, VS_HC_THEME, IColorCustomizations, ISemanticTokenRules, ISemanticTokenColorizationSetting, ISemanticTokenColorCustomizations, IExperimentalSemanticTokenColorCustomizations } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { convertSettings } from 'vs/workbench/services/themes/common/themeCompatibility'; import * as nls from 'vs/nls'; import * as types from 'vs/base/common/types'; @@ -20,7 +20,7 @@ import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages'; import { URI } from 'vs/base/common/uri'; import { parse as parsePList } from 'vs/workbench/services/themes/common/plistParser'; import { startsWith } from 'vs/base/common/strings'; -import { TokenStyle, ProbeScope, TokenStylingRule, getTokenClassificationRegistry, TokenStyleValue, TokenStyleData, parseClassifierString } from 'vs/platform/theme/common/tokenClassificationRegistry'; +import { TokenStyle, SemanticTokenRule, ProbeScope, getTokenClassificationRegistry, TokenStyleValue, TokenStyleData, parseClassifierString } from 'vs/platform/theme/common/tokenClassificationRegistry'; import { MatcherWithPriority, Matcher, createMatchers } from 'vs/workbench/services/themes/common/textMateScopeMatcher'; import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; import { CharCode } from 'vs/base/common/charCode'; @@ -42,7 +42,7 @@ const tokenGroupToScopesMap = { }; -export type TokenStyleDefinition = TokenStylingRule | ProbeScope[] | TokenStyleValue; +export type TokenStyleDefinition = SemanticTokenRule | ProbeScope[] | TokenStyleValue; export type TokenStyleDefinitions = { [P in keyof TokenStyleData]?: TokenStyleDefinition | undefined }; export type TextMateThemingRuleDefinitions = { [P in keyof TokenStyleData]?: ITextMateThemingRule | undefined; } & { scope?: ProbeScope; }; @@ -62,14 +62,15 @@ export class ColorThemeData implements IWorkbenchColorTheme { private themeSemanticHighlighting: boolean | undefined; private customSemanticHighlighting: boolean | undefined; + private customSemanticHighlightingDeprecated: boolean | undefined; private themeTokenColors: ITextMateThemingRule[] = []; private customTokenColors: ITextMateThemingRule[] = []; private colorMap: IColorMap = {}; private customColorMap: IColorMap = {}; - private tokenStylingRules: TokenStylingRule[] = []; - private customTokenStylingRules: TokenStylingRule[] = []; + private semanticTokenRules: SemanticTokenRule[] = []; + private customSemanticTokenRules: SemanticTokenRule[] = []; private themeTokenScopeMatchers: Matcher[] | undefined; private customTokenScopeMatchers: Matcher[] | undefined; @@ -85,7 +86,13 @@ export class ColorThemeData implements IWorkbenchColorTheme { } get semanticHighlighting(): boolean { - return this.customSemanticHighlighting !== undefined ? this.customSemanticHighlighting : !!this.themeSemanticHighlighting; + if (this.customSemanticHighlighting !== undefined) { + return this.customSemanticHighlighting; + } + if (this.customSemanticHighlightingDeprecated !== undefined) { + return this.customSemanticHighlightingDeprecated; + } + return !!this.themeSemanticHighlighting; } get tokenColors(): ITextMateThemingRule[] { @@ -170,18 +177,16 @@ export class ColorThemeData implements IWorkbenchColorTheme { } } } - for (const rule of this.tokenStylingRules) { - const matchScore = rule.selector.match(type, modifiers, language); - if (matchScore >= 0) { - _processStyle(matchScore, rule.style, rule); - } - } - for (const rule of this.customTokenStylingRules) { + function _processSemanticTokenRule(rule: SemanticTokenRule) { const matchScore = rule.selector.match(type, modifiers, language); if (matchScore >= 0) { _processStyle(matchScore, rule.style, rule); } } + + this.semanticTokenRules.forEach(_processSemanticTokenRule); + this.customSemanticTokenRules.forEach(_processSemanticTokenRule); + let hasUndefinedStyleProperty = false; for (let k in score) { const key = k as keyof TokenStyle; @@ -240,14 +245,14 @@ export class ColorThemeData implements IWorkbenchColorTheme { index.add(rule.settings.background); }); - this.tokenStylingRules.forEach(r => index.add(r.style.foreground)); + this.semanticTokenRules.forEach(r => index.add(r.style.foreground)); tokenClassificationRegistry.getTokenStylingDefaultRules().forEach(r => { const defaultColor = r.defaults[this.type]; if (defaultColor && typeof defaultColor === 'object') { index.add(defaultColor.foreground); } }); - this.customTokenStylingRules.forEach(r => index.add(r.style.foreground)); + this.customSemanticTokenRules.forEach(r => index.add(r.style.foreground)); this.tokenColorIndex = index; } @@ -273,11 +278,11 @@ export class ColorThemeData implements IWorkbenchColorTheme { }; } - public getTokenStylingRuleScope(rule: TokenStylingRule): 'setting' | 'theme' | undefined { - if (this.customTokenStylingRules.indexOf(rule) !== -1) { + public getTokenStylingRuleScope(rule: SemanticTokenRule): 'setting' | 'theme' | undefined { + if (this.customSemanticTokenRules.indexOf(rule) !== -1) { return 'setting'; } - if (this.tokenStylingRules.indexOf(rule) !== -1) { + if (this.semanticTokenRules.indexOf(rule) !== -1) { return 'theme'; } return undefined; @@ -346,7 +351,7 @@ export class ColorThemeData implements IWorkbenchColorTheme { public setCustomizations(settings: ThemeConfiguration) { this.setCustomColors(settings.colorCustomizations); this.setCustomTokenColors(settings.tokenColorCustomizations); - this.setCustomTokenStyleRules(settings.tokenStylesCustomizations); + this.setCustomSemanticTokenColors(settings.semanticTokenColorCustomizations, settings.experimentalSemanticTokenColorCustomizations); } public setCustomColors(colors: IColorCustomizations) { @@ -374,7 +379,7 @@ export class ColorThemeData implements IWorkbenchColorTheme { public setCustomTokenColors(customTokenColors: ITokenColorCustomizations) { this.customTokenColors = []; - this.customSemanticHighlighting = undefined; + this.customSemanticHighlightingDeprecated = undefined; // first add the non-theme specific settings this.addCustomTokenColors(customTokenColors); @@ -390,19 +395,53 @@ export class ColorThemeData implements IWorkbenchColorTheme { this.customTokenScopeMatchers = undefined; } - public setCustomTokenStyleRules(tokenStylingRules: IExperimentalTokenStyleCustomizations) { - this.customTokenStylingRules = []; - readCustomTokenStyleRules(tokenStylingRules, this.customTokenStylingRules); + public setCustomSemanticTokenColors(semanticTokenColors: ISemanticTokenColorCustomizations | undefined, experimental?: IExperimentalSemanticTokenColorCustomizations) { + this.customSemanticTokenRules = []; + this.customSemanticHighlighting = undefined; - const themeSpecificColors = tokenStylingRules[`[${this.settingsId}]`] as IExperimentalTokenStyleCustomizations; - if (types.isObject(themeSpecificColors)) { - readCustomTokenStyleRules(themeSpecificColors, this.customTokenStylingRules); + if (experimental) { // apply deprecated settings first + this.readSemanticTokenRules(experimental); + const themeSpecificColors = experimental[`[${this.settingsId}]`] as IExperimentalSemanticTokenColorCustomizations; + if (types.isObject(themeSpecificColors)) { + this.readSemanticTokenRules(themeSpecificColors); + } + } + if (semanticTokenColors) { + this.customSemanticHighlighting = semanticTokenColors.enabled; + if (semanticTokenColors.rules) { + this.readSemanticTokenRules(semanticTokenColors.rules); + } + const themeSpecificColors = semanticTokenColors[`[${this.settingsId}]`] as ISemanticTokenColorCustomizations; + if (types.isObject(themeSpecificColors)) { + if (themeSpecificColors.enabled !== undefined) { + this.customSemanticHighlighting = themeSpecificColors.enabled; + } + if (themeSpecificColors.rules) { + this.readSemanticTokenRules(themeSpecificColors.rules); + } + } } this.tokenColorIndex = undefined; this.textMateThemingRules = undefined; } + + private readSemanticTokenRules(tokenStylingRuleSection: ISemanticTokenRules) { + for (let key in tokenStylingRuleSection) { + if (key[0] !== '[') { // still do this test until experimental settings are gone + try { + const rule = readSemanticTokenRule(key, tokenStylingRuleSection[key]); + if (rule) { + this.customSemanticTokenRules.push(rule); + } + } catch (e) { + // invalid selector, ignore + } + } + } + } + private addCustomTokenColors(customTokenColors: ITokenColorCustomizations) { // Put the general customizations such as comments, strings, etc. first so that // they can be overridden by specific customizations like "string.interpolated" @@ -427,7 +466,7 @@ export class ColorThemeData implements IWorkbenchColorTheme { } } if (customTokenColors.semanticHighlighting !== undefined) { - this.customSemanticHighlighting = customTokenColors.semanticHighlighting; + this.customSemanticHighlightingDeprecated = customTokenColors.semanticHighlighting; } } @@ -449,12 +488,12 @@ export class ColorThemeData implements IWorkbenchColorTheme { const result = { colors: {}, textMateRules: [], - stylingRules: [], + semanticTokenRules: [], semanticHighlighting: false }; return _loadColorTheme(extensionResourceLoaderService, this.location, result).then(_ => { this.isLoaded = true; - this.tokenStylingRules = result.stylingRules; + this.semanticTokenRules = result.semanticTokenRules; this.colorMap = result.colors; this.themeTokenColors = result.textMateRules; this.themeSemanticHighlighting = result.semanticHighlighting; @@ -480,7 +519,7 @@ export class ColorThemeData implements IWorkbenchColorTheme { settingsId: this.settingsId, selector: this.id.split(' ').join('.'), // to not break old clients themeTokenColors: this.themeTokenColors, - tokenStylingRules: this.tokenStylingRules.map(TokenStylingRule.toJSONObject), + semanticTokenRules: this.semanticTokenRules.map(SemanticTokenRule.toJSONObject), extensionData: ExtensionData.toJSONObject(this.extensionData), location: this.location?.toJSON(), themeSemanticHighlighting: this.themeSemanticHighlighting, @@ -493,7 +532,7 @@ export class ColorThemeData implements IWorkbenchColorTheme { hasEqualData(other: ColorThemeData) { return objects.equals(this.colorMap, other.colorMap) && objects.equals(this.themeTokenColors, other.themeTokenColors) - && arrays.equals(this.tokenStylingRules, other.tokenStylingRules, TokenStylingRule.equals) + && arrays.equals(this.semanticTokenRules, other.semanticTokenRules, SemanticTokenRule.equals) && this.themeSemanticHighlighting === other.themeSemanticHighlighting; } @@ -547,13 +586,13 @@ export class ColorThemeData implements IWorkbenchColorTheme { case 'id': case 'label': case 'settingsId': case 'watch': case 'themeSemanticHighlighting': (theme as any)[key] = data[key]; break; - case 'tokenStylingRules': + case 'semanticTokenRules': const rulesData = data[key]; if (Array.isArray(rulesData)) { for (let d of rulesData) { - const rule = TokenStylingRule.fromJSONObject(tokenClassificationRegistry, d); + const rule = SemanticTokenRule.fromJSONObject(tokenClassificationRegistry, d); if (rule) { - theme.tokenStylingRules.push(rule); + theme.semanticTokenRules.push(rule); } } } @@ -605,7 +644,7 @@ function toCSSSelector(extensionId: string, path: string) { return str; } -async function _loadColorTheme(extensionResourceLoaderService: IExtensionResourceLoaderService, themeLocation: URI, result: { textMateRules: ITextMateThemingRule[], colors: IColorMap, stylingRules: TokenStylingRule[], semanticHighlighting: boolean }): Promise { +async function _loadColorTheme(extensionResourceLoaderService: IExtensionResourceLoaderService, themeLocation: URI, result: { textMateRules: ITextMateThemingRule[], colors: IColorMap, semanticTokenRules: SemanticTokenRule[], semanticHighlighting: boolean }): Promise { if (resources.extname(themeLocation) === '.json') { const content = await extensionResourceLoaderService.readExtensionResource(themeLocation); let errors: Json.ParseError[] = []; @@ -650,9 +689,9 @@ async function _loadColorTheme(extensionResourceLoaderService: IExtensionResourc if (semanticTokenColors && typeof semanticTokenColors === 'object') { for (let key in semanticTokenColors) { try { - const rule = readCustomTokenStyleRule(key, semanticTokenColors[key]); + const rule = readSemanticTokenRule(key, semanticTokenColors[key]); if (rule) { - result.stylingRules.push(rule); + result.semanticTokenRules.push(rule); } } catch (e) { return Promise.reject(new Error(nls.localize({ key: 'error.invalidformat.semanticTokenColors', comment: ['{0} will be replaced by a path. Values in quotes should not be translated.'] }, "Problem parsing color theme file: {0}. Property 'semanticTokenColors' conatains a invalid selector", themeLocation.toString()))); @@ -770,13 +809,13 @@ function getScopeMatcher(rule: ITextMateThemingRule): Matcher { }; } -function readCustomTokenStyleRule(selectorString: string, settings: ITokenColorizationSetting | string | undefined): TokenStylingRule | undefined { +function readSemanticTokenRule(selectorString: string, settings: ISemanticTokenColorizationSetting | string | boolean | undefined): SemanticTokenRule | undefined { const selector = tokenClassificationRegistry.parseTokenSelector(selectorString); let style: TokenStyle | undefined; if (typeof settings === 'string') { style = TokenStyle.fromSettings(settings, undefined); - } else if (isTokenColorizationSetting(settings)) { - style = TokenStyle.fromSettings(settings.foreground, settings.fontStyle); + } else if (isSemanticTokenColorizationSetting(settings)) { + style = TokenStyle.fromSettings(settings.foreground, settings.fontStyle, settings.bold, settings.underline, settings.italic); } if (style) { return { selector, style }; @@ -784,24 +823,9 @@ function readCustomTokenStyleRule(selectorString: string, settings: ITokenColori return undefined; } -function readCustomTokenStyleRules(tokenStylingRuleSection: IExperimentalTokenStyleCustomizations, result: TokenStylingRule[] = []) { - for (let key in tokenStylingRuleSection) { - if (key[0] !== '[') { - try { - const rule = readCustomTokenStyleRule(key, tokenStylingRuleSection[key]); - if (rule) { - result.push(rule); - } - } catch (e) { - // invalid selector, ignore - } - } - } - return result; -} - -function isTokenColorizationSetting(style: any): style is ITokenColorizationSetting { - return style && (style.foreground || style.fontStyle); +function isSemanticTokenColorizationSetting(style: any): style is ISemanticTokenColorizationSetting { + return style && (types.isString(style.foreground) || types.isString(style.fontStyle) || types.isBoolean(style.italic) + || types.isBoolean(style.underline) || types.isBoolean(style.bold)); } diff --git a/src/vs/workbench/services/themes/common/themeConfiguration.ts b/src/vs/workbench/services/themes/common/themeConfiguration.ts index 2758dbc6ec..9f8604ba30 100644 --- a/src/vs/workbench/services/themes/common/themeConfiguration.ts +++ b/src/vs/workbench/services/themes/common/themeConfiguration.ts @@ -12,7 +12,7 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { textmateColorsSchemaId, textmateColorGroupSchemaId } from 'vs/workbench/services/themes/common/colorThemeSchema'; import { workbenchColorsSchemaId } from 'vs/platform/theme/common/colorRegistry'; import { tokenStylingSchemaId } from 'vs/platform/theme/common/tokenClassificationRegistry'; -import { ThemeSettings, IWorkbenchColorTheme, IWorkbenchFileIconTheme, IColorCustomizations, ITokenColorCustomizations, IExperimentalTokenStyleCustomizations, IWorkbenchProductIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { ThemeSettings, IWorkbenchColorTheme, IWorkbenchFileIconTheme, IColorCustomizations, ITokenColorCustomizations, IWorkbenchProductIconTheme, ISemanticTokenColorCustomizations, IExperimentalSemanticTokenColorCustomizations } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; const DEFAULT_THEME_SETTING_VALUE = 'Default Light Azure Data Studio'; // {{SQL CARBON EDIT}} replace default theme @@ -134,19 +134,43 @@ const tokenColorSchema: IJSONSchema = { }, semanticHighlighting: { description: nls.localize('editorColors.semanticHighlighting', 'Whether semantic highlighting should be enabled for this theme.'), + deprecationMessage: nls.localize('editorColors.semanticHighlighting.deprecationMessage', 'Use `enabled` in `editor.semanticTokenColorCustomizations` setting instead.'), type: 'boolean' } } }; + const tokenColorCustomizationSchema: IConfigurationPropertySchema = { - description: nls.localize('editorColors', "Overrides editor colors and font style from the currently selected color theme."), + description: nls.localize('editorColors', "Overrides editor syntax colors and font style from the currently selected color theme."), default: {}, allOf: [tokenColorSchema] }; -const experimentalTokenStylingCustomizationSchema: IConfigurationPropertySchema = { - description: nls.localize('editorColorsTokenStyles', "Overrides token color and styles from the currently selected color theme."), + +const semanticTokenColorSchema: IJSONSchema = { + type: 'object', + properties: { + enabled: { + type: 'boolean', + description: nls.localize('editorColors.semanticHighlighting.enabled', 'Whether semantic highlighting is enabled or disabled for this theme') + }, + rules: { + $ref: tokenStylingSchemaId, + description: nls.localize('editorColors.semanticHighlighting.rules', 'Semantic token styling rules for this theme.') + } + }, + additionalProperties: false +}; + +const semanticTokenColorCustomizationSchema: IConfigurationPropertySchema = { + description: nls.localize('semanticTokenColors', "Overrides editor semantic token color and styles from the currently selected color theme."), default: {}, - allOf: [{ $ref: tokenStylingSchemaId }] + allOf: [{ ...semanticTokenColorSchema, patternProperties: { '^\\[': {} } }] +}; + +const experimentalTokenStylingCustomizationSchema: IConfigurationPropertySchema = { + deprecationMessage: nls.localize('editorColors.experimentalTokenStyling.deprecationMessage', 'Use `editor.semanticTokenColorCustomizations` instead.'), + default: {}, + allOf: [{ $ref: tokenStylingSchemaId }], }; const tokenColorCustomizationConfiguration: IConfigurationNode = { id: 'editor', @@ -154,6 +178,7 @@ const tokenColorCustomizationConfiguration: IConfigurationNode = { type: 'object', properties: { [ThemeSettings.TOKEN_COLOR_CUSTOMIZATIONS]: tokenColorCustomizationSchema, + [ThemeSettings.SEMANTIC_TOKEN_COLOR_CUSTOMIZATIONS]: semanticTokenColorCustomizationSchema, [ThemeSettings.TOKEN_COLOR_CUSTOMIZATIONS_EXPERIMENTAL]: experimentalTokenStylingCustomizationSchema } }; @@ -167,22 +192,24 @@ export function updateColorThemeConfigurationSchemas(themes: IWorkbenchColorThem const themeSpecificWorkbenchColors: IJSONSchema = { properties: {} }; const themeSpecificTokenColors: IJSONSchema = { properties: {} }; - const themeSpecificTokenStyling: IJSONSchema = { properties: {} }; + const themeSpecificSemanticTokenColors: IJSONSchema = { properties: {} }; + const experimentalThemeSpecificSemanticTokenColors: IJSONSchema = { properties: {} }; const workbenchColors = { $ref: workbenchColorsSchemaId, additionalProperties: false }; const tokenColors = { properties: tokenColorSchema.properties, additionalProperties: false }; - const tokenStyling = { $ref: tokenStylingSchemaId, additionalProperties: false }; for (let t of themes) { // add theme specific color customization ("[Abyss]":{ ... }) const themeId = `[${t.settingsId}]`; themeSpecificWorkbenchColors.properties![themeId] = workbenchColors; themeSpecificTokenColors.properties![themeId] = tokenColors; - themeSpecificTokenStyling.properties![themeId] = tokenStyling; + themeSpecificSemanticTokenColors.properties![themeId] = semanticTokenColorSchema; + experimentalThemeSpecificSemanticTokenColors.properties![themeId] = { $ref: tokenStylingSchemaId, additionalProperties: false }; } colorCustomizationsSchema.allOf![1] = themeSpecificWorkbenchColors; tokenColorCustomizationSchema.allOf![1] = themeSpecificTokenColors; - experimentalTokenStylingCustomizationSchema.allOf![1] = themeSpecificTokenStyling; + semanticTokenColorCustomizationSchema.allOf![1] = themeSpecificSemanticTokenColors; + experimentalTokenStylingCustomizationSchema.allOf![1] = experimentalThemeSpecificSemanticTokenColors; configurationRegistry.notifyConfigurationSchemaUpdated(themeSettingsConfiguration, tokenColorCustomizationConfiguration); } @@ -226,8 +253,12 @@ export class ThemeConfiguration { return this.configurationService.getValue(ThemeSettings.TOKEN_COLOR_CUSTOMIZATIONS) || {}; } - public get tokenStylesCustomizations(): IExperimentalTokenStyleCustomizations { - return this.configurationService.getValue(ThemeSettings.TOKEN_COLOR_CUSTOMIZATIONS_EXPERIMENTAL) || {}; + public get semanticTokenColorCustomizations(): ISemanticTokenColorCustomizations | undefined { + return this.configurationService.getValue(ThemeSettings.SEMANTIC_TOKEN_COLOR_CUSTOMIZATIONS); + } + + public get experimentalSemanticTokenColorCustomizations(): IExperimentalSemanticTokenColorCustomizations | undefined { + return this.configurationService.getValue(ThemeSettings.TOKEN_COLOR_CUSTOMIZATIONS_EXPERIMENTAL); } public async setColorTheme(theme: IWorkbenchColorTheme, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { diff --git a/src/vs/workbench/services/themes/common/workbenchThemeService.ts b/src/vs/workbench/services/themes/common/workbenchThemeService.ts index 6a59779527..15a0ac03e0 100644 --- a/src/vs/workbench/services/themes/common/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/common/workbenchThemeService.ts @@ -25,6 +25,7 @@ export enum ThemeSettings { PRODUCT_ICON_THEME = 'workbench.productIconTheme', COLOR_CUSTOMIZATIONS = 'workbench.colorCustomizations', TOKEN_COLOR_CUSTOMIZATIONS = 'editor.tokenColorCustomizations', + SEMANTIC_TOKEN_COLOR_CUSTOMIZATIONS = 'editor.semanticTokenColorCustomizations', TOKEN_COLOR_CUSTOMIZATIONS_EXPERIMENTAL = 'editor.tokenColorCustomizationsExperimental', PREFERRED_DARK_THEME = 'workbench.preferredDarkColorTheme', @@ -93,11 +94,21 @@ export interface ITokenColorCustomizations { functions?: string | ITokenColorizationSetting; variables?: string | ITokenColorizationSetting; textMateRules?: ITextMateThemingRule[]; - semanticHighlighting?: boolean; + semanticHighlighting?: boolean; // deprecated, use ISemanticTokenColorCustomizations.enabled instead } -export interface IExperimentalTokenStyleCustomizations { - [styleRuleOrThemeSettingsId: string]: string | ITokenColorizationSetting | IExperimentalTokenStyleCustomizations | undefined; +export interface ISemanticTokenColorCustomizations { + enabled?: boolean; + rules?: ISemanticTokenRules; + [styleRuleOrThemeSettingsId: string]: ISemanticTokenRules | ISemanticTokenColorCustomizations | boolean | undefined; +} + +export interface IExperimentalSemanticTokenColorCustomizations { + [styleRuleOrThemeSettingsId: string]: ISemanticTokenRules | IExperimentalSemanticTokenColorCustomizations | undefined; +} + +export interface ISemanticTokenRules { + [selector: string]: string | ISemanticTokenColorizationSetting | undefined; } export interface ITextMateThemingRule { @@ -112,6 +123,14 @@ export interface ITokenColorizationSetting { fontStyle?: string; /* [italic|underline|bold] */ } +export interface ISemanticTokenColorizationSetting { + foreground?: string; + fontStyle?: string; /* [italic|underline|bold] */ + bold?: boolean; + underline?: boolean; + italic?: boolean; +} + export interface ExtensionData { extensionId: string; extensionPublisher: string; diff --git a/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts b/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts index 9f80711119..2e1196ec15 100644 --- a/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts +++ b/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts @@ -99,7 +99,7 @@ suite('Themes - TokenStyleResolving', () => { assertTokenStyles(themeData, { 'comment': ts('#88846f', undefinedStyle), 'variable': ts('#F8F8F2', unsetStyle), - 'type': ts('#A6E22E', { underline: true }), + 'type': ts('#A6E22E', { bold: false, underline: true, italic: false }), 'function': ts('#A6E22E', unsetStyle), 'string': ts('#E6DB74', undefinedStyle), 'number': ts('#AE81FF', undefinedStyle), @@ -199,7 +199,7 @@ suite('Themes - TokenStyleResolving', () => { assertTokenStyles(themeData, { 'comment': ts('#384887', undefinedStyle), 'variable': ts(undefined, unsetStyle), - 'type': ts('#ffeebb', { underline: true }), + 'type': ts('#ffeebb', { underline: true, bold: false, italic: false }), 'function': ts('#ddbb88', unsetStyle), 'string': ts('#22aa44', undefinedStyle), 'number': ts('#f280d0', undefinedStyle), @@ -271,19 +271,19 @@ suite('Themes - TokenStyleResolving', () => { assertTokenStyle(tokenStyle, defaultTokenStyle, 'keyword.operators'); tokenStyle = themeData.resolveScopes([['storage']]); - assertTokenStyle(tokenStyle, ts('#F92672', { italic: true }), 'storage'); + assertTokenStyle(tokenStyle, ts('#F92672', { italic: true, bold: false, underline: false }), 'storage'); tokenStyle = themeData.resolveScopes([['storage.type']]); - assertTokenStyle(tokenStyle, ts('#66D9EF', { italic: true }), 'storage.type'); + assertTokenStyle(tokenStyle, ts('#66D9EF', { italic: true, bold: false, underline: false }), 'storage.type'); tokenStyle = themeData.resolveScopes([['entity.name.class']]); - assertTokenStyle(tokenStyle, ts('#A6E22E', { underline: true }), 'entity.name.class'); + assertTokenStyle(tokenStyle, ts('#A6E22E', { italic: false, bold: false, underline: true }), 'entity.name.class'); tokenStyle = themeData.resolveScopes([['meta.structure.dictionary.json', 'string.quoted.double.json']]); assertTokenStyle(tokenStyle, ts('#66D9EF', undefined), 'json property'); tokenStyle = themeData.resolveScopes([['keyword'], ['storage.type'], ['entity.name.class']]); - assertTokenStyle(tokenStyle, ts('#66D9EF', { italic: true }), 'storage.type'); + assertTokenStyle(tokenStyle, ts('#66D9EF', { italic: true, bold: false, underline: false }), 'storage.type'); }); @@ -318,7 +318,7 @@ suite('Themes - TokenStyleResolving', () => { themeData.setCustomTokenColors(customTokenColors); const tokenStyle = themeData.resolveScopes([['entity.name.type.class']]); - assertTokenStyle(tokenStyle, ts('#FF00FF', { underline: true }), 'entity.name.type.class'); + assertTokenStyle(tokenStyle, ts('#FF00FF', { italic: false, bold: false, underline: true }), 'entity.name.type.class'); }); @@ -326,13 +326,16 @@ suite('Themes - TokenStyleResolving', () => { test('rule matching', async () => { const themeData = ColorThemeData.createLoadedEmptyTheme('test', 'test'); themeData.setCustomColors({ 'editor.foreground': '#000000' }); - themeData.setCustomTokenStyleRules({ - 'type': '#ff0000', - 'class': { foreground: '#0000ff', fontStyle: 'italic' }, - '*.static': { fontStyle: 'bold' }, - '*.declaration': { fontStyle: 'italic' }, - '*.async.static': { fontStyle: 'italic underline' }, - '*.async': { foreground: '#000fff', fontStyle: 'underline' } + themeData.setCustomSemanticTokenColors({ + enabled: true, + rules: { + 'type': '#ff0000', + 'class': { foreground: '#0000ff', italic: true }, + '*.static': { bold: true }, + '*.declaration': { italic: true }, + '*.async.static': { italic: true, underline: true }, + '*.async': { foreground: '#000fff', underline: true } + } }); assertTokenStyles(themeData, { @@ -340,7 +343,7 @@ suite('Themes - TokenStyleResolving', () => { 'type.static': ts('#ff0000', { bold: true }), 'type.static.declaration': ts('#ff0000', { bold: true, italic: true }), 'class': ts('#0000ff', { italic: true }), - 'class.static.declaration': ts('#0000ff', { bold: true, italic: true }), + 'class.static.declaration': ts('#0000ff', { bold: true, italic: true, }), 'class.declaration': ts('#0000ff', { italic: true }), 'class.declaration.async': ts('#000fff', { underline: true, italic: true }), 'class.declaration.async.static': ts('#000fff', { italic: true, underline: true, bold: true }), @@ -357,18 +360,24 @@ suite('Themes - TokenStyleResolving', () => { try { const themeData = ColorThemeData.createLoadedEmptyTheme('test', 'test'); themeData.setCustomColors({ 'editor.foreground': '#000000' }); - themeData.setCustomTokenStyleRules({ - 'interface': '#ff0000', - 'myTestInterface': { fontStyle: 'italic' }, - 'interface.static': { fontStyle: 'bold' } + themeData.setCustomSemanticTokenColors({ + enabled: true, + rules: { + 'interface': '#ff0000', + 'myTestInterface': { italic: true }, + 'interface.static': { bold: true } + } }); assertTokenStyles(themeData, { 'myTestSubInterface': ts('#ff0000', { italic: true }) }); assertTokenStyles(themeData, { 'myTestSubInterface.static': ts('#ff0000', { italic: true, bold: true }) }); - themeData.setCustomTokenStyleRules({ - 'interface': '#ff0000', - 'myTestInterface': { foreground: '#ff00ff', fontStyle: 'italic' } + themeData.setCustomSemanticTokenColors({ + enabled: true, + rules: { + 'interface': '#ff0000', + 'myTestInterface': { foreground: '#ff00ff', italic: true } + } }); assertTokenStyles(themeData, { 'myTestSubInterface': ts('#ff00ff', { italic: true }) }); } finally { @@ -381,11 +390,14 @@ suite('Themes - TokenStyleResolving', () => { try { const themeData = ColorThemeData.createLoadedEmptyTheme('test', 'test'); themeData.setCustomColors({ 'editor.foreground': '#000000' }); - themeData.setCustomTokenStyleRules({ - 'interface': '#fff000', - 'interface:java': '#ff0000', - 'interface.static': { fontStyle: 'bold' }, - 'interface.static:typescript': { fontStyle: 'italic' } + themeData.setCustomSemanticTokenColors({ + enabled: true, + rules: { + 'interface': '#fff000', + 'interface:java': '#ff0000', + 'interface.static': { bold: true }, + 'interface.static:typescript': { italic: true } + } }); assertTokenStyles(themeData, { 'interface': ts('#ff0000', undefined) }, 'java'); diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index 9d20296974..89f52a8940 100644 --- a/src/vs/workbench/services/views/browser/viewDescriptorService.ts +++ b/src/vs/workbench/services/views/browser/viewDescriptorService.ts @@ -29,6 +29,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor _serviceBrand: undefined; private static readonly CACHED_VIEW_POSITIONS = 'views.cachedViewPositions'; + private static readonly CACHED_VIEW_CONTAINER_LOCATIONS = 'views.cachedViewContainerLocations'; private static readonly COMMON_CONTAINER_ID_PREFIX = 'workbench.views.service'; private readonly _onDidChangeContainer: Emitter<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }> = this._register(new Emitter<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }>()); @@ -37,6 +38,9 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor private readonly _onDidChangeLocation: Emitter<{ views: IViewDescriptor[], from: ViewContainerLocation, to: ViewContainerLocation }> = this._register(new Emitter<{ views: IViewDescriptor[], from: ViewContainerLocation, to: ViewContainerLocation }>()); readonly onDidChangeLocation: Event<{ views: IViewDescriptor[], from: ViewContainerLocation, to: ViewContainerLocation }> = this._onDidChangeLocation.event; + private readonly _onDidChangeContainerLocation: Emitter<{ viewContainer: ViewContainer, from: ViewContainerLocation, to: ViewContainerLocation }> = this._register(new Emitter<{ viewContainer: ViewContainer, from: ViewContainerLocation, to: ViewContainerLocation }>()); + readonly onDidChangeContainerLocation: Event<{ viewContainer: ViewContainer, from: ViewContainerLocation, to: ViewContainerLocation }> = this._onDidChangeContainerLocation.event; + private readonly viewContainerModels: Map; private readonly activeViewContextKeys: Map>; private readonly movableViewContextKeys: Map>; @@ -46,6 +50,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor private readonly viewContainersRegistry: IViewContainersRegistry; private cachedViewInfo: Map; + private cachedViewContainerInfo: Map; private _cachedViewPositionsValue: string | undefined; private get cachedViewPositionsValue(): string { @@ -63,6 +68,22 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } } + private _cachedViewContainerLocationsValue: string | undefined; + private get cachedViewContainerLocationsValue(): string { + if (!this._cachedViewContainerLocationsValue) { + this._cachedViewContainerLocationsValue = this.getStoredCachedViewContainerLocationsValue(); + } + + return this._cachedViewContainerLocationsValue; + } + + private set cachedViewContainerLocationsValue(value: string) { + if (this._cachedViewContainerLocationsValue !== value) { + this._cachedViewContainerLocationsValue = value; + this.setStoredCachedViewContainerLocationsValue(value); + } + } + constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @@ -83,9 +104,10 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor this.viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); this.cachedViewInfo = this.getCachedViewPositions(); + this.cachedViewContainerInfo = this.getCachedViewContainerLocations(); // Register all containers that were registered before this ctor - this.viewContainersRegistry.all.forEach(viewContainer => this.onDidRegisterViewContainer(viewContainer)); + this.getViewContainers().forEach(viewContainer => this.onDidRegisterViewContainer(viewContainer)); // Try generating all generated containers that don't need extensions this.tryGenerateContainers(); @@ -252,6 +274,11 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } getViewContainerLocation(viewContainer: ViewContainer): ViewContainerLocation { + const location = this.cachedViewContainerInfo.get(viewContainer.id); + return location !== undefined ? location : this.getDefaultViewContainerLocation(viewContainer); + } + + getDefaultViewContainerLocation(viewContainer: ViewContainer): ViewContainerLocation { return this.viewContainersRegistry.getViewContainerLocation(viewContainer); } @@ -267,14 +294,29 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor return this.viewContainersRegistry.get(id) || null; } getViewContainersByLocation(location: ViewContainerLocation): ViewContainer[] { - return this.viewContainersRegistry.getViewContainers(location); + return this.getViewContainers().filter(v => this.getViewContainerLocation(v) === location); } getViewContainers(): ViewContainer[] { return this.viewContainersRegistry.all; } moveViewContainerToLocation(viewContainer: ViewContainer, location: ViewContainerLocation): void { - // to be implemented + const from = this.getViewContainerLocation(viewContainer); + const to = location; + if (from !== to) { + if (this.getDefaultViewContainerLocation(viewContainer) === to) { + this.cachedViewContainerInfo.delete(viewContainer.id); + } else { + this.cachedViewContainerInfo.set(viewContainer.id, to); + } + + this._onDidChangeContainerLocation.fire({ viewContainer, from, to }); + + const views = this.getViewsByContainer(viewContainer); + this._onDidChangeLocation.fire({ views, from, to }); + + this.saveViewContainerLocationsToCache(); + } } moveViewToLocation(view: IViewDescriptor, location: ViewContainerLocation): void { @@ -376,6 +418,10 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor return result; } + private getCachedViewContainerLocations(): Map { + return new Map(JSON.parse(this.cachedViewContainerLocationsValue)); + } + private onDidStorageChange(e: IWorkspaceStorageChangeEvent): void { if (e.key === ViewDescriptorService.CACHED_VIEW_POSITIONS && e.scope === StorageScope.GLOBAL && this.cachedViewPositionsValue !== this.getStoredCachedViewPositionsValue() /* This checks if current window changed the value or not */) { @@ -407,7 +453,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } // If a value is not present in the cache, it must be reset to default - this.viewContainersRegistry.all.forEach(viewContainer => { + this.getViewContainers().forEach(viewContainer => { const viewContainerModel = this.getViewContainerModel(viewContainer); viewContainerModel.allViewDescriptors.forEach(viewDescriptor => { if (!newCachedPositions.has(viewDescriptor.id)) { @@ -424,6 +470,35 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor this.cachedViewInfo = this.getCachedViewPositions(); } + + + if (e.key === ViewDescriptorService.CACHED_VIEW_CONTAINER_LOCATIONS && e.scope === StorageScope.GLOBAL + && this.cachedViewContainerLocationsValue !== this.getStoredCachedViewContainerLocationsValue() /* This checks if current window changed the value or not */) { + this._cachedViewContainerLocationsValue = this.getStoredCachedViewContainerLocationsValue(); + const newCachedLocations = this.getCachedViewContainerLocations(); + + for (const [containerId, location] of newCachedLocations.entries()) { + const container = this.getViewContainerById(containerId); + if (container) { + if (location !== this.getViewContainerLocation(container)) { + this.moveViewContainerToLocation(container, location); + } + } + } + + this.getViewContainers().forEach(viewContainer => { + if (!newCachedLocations.has(viewContainer.id)) { + const currentLocation = this.getViewContainerLocation(viewContainer); + const defaultLocation = this.getDefaultViewContainerLocation(viewContainer); + + if (currentLocation !== defaultLocation) { + this.moveViewContainerToLocation(viewContainer, defaultLocation); + } + } + }); + + this.cachedViewContainerInfo = this.getCachedViewContainerLocations(); + } } // Generated Container Id Format @@ -442,8 +517,16 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor this.storageService.store(ViewDescriptorService.CACHED_VIEW_POSITIONS, value, StorageScope.GLOBAL); } + private getStoredCachedViewContainerLocationsValue(): string { + return this.storageService.get(ViewDescriptorService.CACHED_VIEW_CONTAINER_LOCATIONS, StorageScope.GLOBAL, '[]'); + } + + private setStoredCachedViewContainerLocationsValue(value: string): void { + this.storageService.store(ViewDescriptorService.CACHED_VIEW_CONTAINER_LOCATIONS, value, StorageScope.GLOBAL); + } + private saveViewPositionsToCache(): void { - this.viewContainersRegistry.all.forEach(viewContainer => { + this.getViewContainers().forEach(viewContainer => { const viewContainerModel = this.getViewContainerModel(viewContainer); viewContainerModel.allViewDescriptors.forEach(viewDescriptor => { const containerLocation = this.getViewContainerLocation(viewContainer); @@ -467,6 +550,17 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor this.cachedViewPositionsValue = JSON.stringify([...this.cachedViewInfo]); } + private saveViewContainerLocationsToCache(): void { + for (const [containerId, location] of this.cachedViewContainerInfo) { + const container = this.getViewContainerById(containerId); + if (container && location === this.getDefaultViewContainerLocation(container)) { + this.cachedViewContainerInfo.delete(containerId); + } + } + + this.cachedViewContainerLocationsValue = JSON.stringify([...this.cachedViewContainerInfo]); + } + private getViewsByContainer(viewContainer: ViewContainer): IViewDescriptor[] { const result = this.viewsRegistry.getViews(viewContainer).filter(viewDescriptor => { const cachedContainer = this.cachedViewInfo.get(viewDescriptor.id)?.containerId || viewContainer.id; diff --git a/src/vs/workbench/services/views/common/viewContainerModel.ts b/src/vs/workbench/services/views/common/viewContainerModel.ts index a05efd1af6..d495471981 100644 --- a/src/vs/workbench/services/views/common/viewContainerModel.ts +++ b/src/vs/workbench/services/views/common/viewContainerModel.ts @@ -381,11 +381,11 @@ export class ViewContainerModel extends Disposable implements IViewContainerMode const viewDescriptor = viewDescriptorItem.viewDescriptor; if (!viewDescriptor.canToggleVisibility) { - throw new Error(`Can't toggle this view's visibility`); + continue; } - if (this.isViewDescriptorVisible(viewDescriptorItem) === visible) { - return; + if (this.isViewDescriptorVisibleWhenActive(viewDescriptorItem) === visible) { + continue; } if (viewDescriptor.workspace) { @@ -398,6 +398,11 @@ export class ViewContainerModel extends Disposable implements IViewContainerMode viewDescriptorItem.state.size = size; } + if (this.isViewDescriptorVisible(viewDescriptorItem) !== visible) { + // do not add events if visibility is not changed + continue; + } + if (visible) { added.push({ index: visibleIndex, viewDescriptor, size: viewDescriptorItem.state.size, collapsed: !!viewDescriptorItem.state.collapsed }); } else { diff --git a/src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts b/src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts index f42e07e669..f28e9ed87a 100644 --- a/src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts +++ b/src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts @@ -382,4 +382,86 @@ suite('ViewContainerModel', () => { assert.ok(!targetEvent.called, 'remove event should not be called since it is already hidden'); }); + test('add event is not triggered if view was set visible (when visible) and not active', async function () { + container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + const testObject = viewDescriptorService.getViewContainerModel(container); + const target = disposableStore.add(new ViewDescriptorSequence(testObject)); + const viewDescriptor: IViewDescriptor = { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + when: ContextKeyExpr.equals('showview1', true), + canToggleVisibility: true + }; + + const key = contextKeyService.createKey('showview1', true); + key.set(false); + ViewsRegistry.registerViews([viewDescriptor], container); + + assert.equal(testObject.visibleViewDescriptors.length, 0); + assert.equal(target.elements.length, 0); + + const targetEvent = sinon.spy(testObject.onDidAddVisibleViewDescriptors); + testObject.setVisible('view1', true); + assert.ok(!targetEvent.called, 'add event should not be called since it is already visible'); + assert.equal(testObject.visibleViewDescriptors.length, 0); + assert.equal(target.elements.length, 0); + }); + + test('remove event is not triggered if view was hidden and not active', async function () { + container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + const testObject = viewDescriptorService.getViewContainerModel(container); + const target = disposableStore.add(new ViewDescriptorSequence(testObject)); + const viewDescriptor: IViewDescriptor = { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + when: ContextKeyExpr.equals('showview1', true), + canToggleVisibility: true + }; + + const key = contextKeyService.createKey('showview1', true); + key.set(false); + ViewsRegistry.registerViews([viewDescriptor], container); + + assert.equal(testObject.visibleViewDescriptors.length, 0); + assert.equal(target.elements.length, 0); + + const targetEvent = sinon.spy(testObject.onDidAddVisibleViewDescriptors); + testObject.setVisible('view1', false); + assert.ok(!targetEvent.called, 'add event should not be called since it is disabled'); + assert.equal(testObject.visibleViewDescriptors.length, 0); + assert.equal(target.elements.length, 0); + }); + + test('add event is not triggered if view was set visible (when not visible) and not active', async function () { + container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + const testObject = viewDescriptorService.getViewContainerModel(container); + const target = disposableStore.add(new ViewDescriptorSequence(testObject)); + const viewDescriptor: IViewDescriptor = { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + when: ContextKeyExpr.equals('showview1', true), + canToggleVisibility: true + }; + + const key = contextKeyService.createKey('showview1', true); + key.set(false); + ViewsRegistry.registerViews([viewDescriptor], container); + + assert.equal(testObject.visibleViewDescriptors.length, 0); + assert.equal(target.elements.length, 0); + + testObject.setVisible('view1', false); + assert.equal(testObject.visibleViewDescriptors.length, 0); + assert.equal(target.elements.length, 0); + + const targetEvent = sinon.spy(testObject.onDidAddVisibleViewDescriptors); + testObject.setVisible('view1', true); + assert.ok(!targetEvent.called, 'add event should not be called since it is disabled'); + assert.equal(testObject.visibleViewDescriptors.length, 0); + assert.equal(target.elements.length, 0); + }); + }); diff --git a/src/vs/workbench/test/browser/api/extHostDocumentData.test.ts b/src/vs/workbench/test/browser/api/extHostDocumentData.test.ts index 466bb88284..4199e29f42 100644 --- a/src/vs/workbench/test/browser/api/extHostDocumentData.test.ts +++ b/src/vs/workbench/test/browser/api/extHostDocumentData.test.ts @@ -315,6 +315,22 @@ suite('ExtHostDocumentData', () => { assert.ok(range.contains(pos)); assert.equal(data.document.getText(range), 'TaskDefinition'); }); + + test('Rename popup sometimes populates with text on the left side omitted #96013', function () { + + const regex = /(-?\d*\.\d\w*)|([^\`\~\!\@\#\$\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g; + const line = 'int abcdefhijklmnopqwvrstxyz;'; + + data = new ExtHostDocumentData(undefined!, URI.file(''), [ + line + ], '\n', 'text', 1, false); + + let range = data.document.getWordRangeAtPosition(new Position(0, 27), regex)!; + assert.equal(range.start.line, 0); + assert.equal(range.end.line, 0); + assert.equal(range.start.character, 4); + assert.equal(range.end.character, 28); + }); }); enum AssertDocumentLineMappingDirection { diff --git a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts index ab8769f48c..d3ed32cc48 100644 --- a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts +++ b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts @@ -11,7 +11,7 @@ import { NullLogService } from 'vs/platform/log/common/log'; import { ExtHostNotebookConcatDocument } from 'vs/workbench/api/common/extHostNotebookConcatDocument'; import { ExtHostNotebookDocument, ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; import { URI } from 'vs/base/common/uri'; -import { CellKind, CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, CellUri, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Position, Location, Range } from 'vs/workbench/api/common/extHostTypes'; import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; @@ -51,6 +51,7 @@ suite('NotebookConcatDocument', function () { }); await extHostNotebooks.$resolveNotebook('test', notebookUri); extHostNotebooks.$acceptModelChanged(notebookUri, { + kind: NotebookCellsChangeType.ModelChange, versionId: 0, changes: [[0, 0, [{ handle: 0, @@ -104,6 +105,7 @@ suite('NotebookConcatDocument', function () { test('location, position mapping', function () { extHostNotebooks.$acceptModelChanged(notebookUri, { + kind: NotebookCellsChangeType.ModelChange, versionId: notebook.versionId + 1, changes: [[0, 0, [{ handle: 1, @@ -142,6 +144,7 @@ suite('NotebookConcatDocument', function () { // UPDATE 1 extHostNotebooks.$acceptModelChanged(notebookUri, { + kind: NotebookCellsChangeType.ModelChange, versionId: notebook.versionId + 1, changes: [[0, 0, [{ handle: 1, @@ -163,6 +166,7 @@ suite('NotebookConcatDocument', function () { // UPDATE 2 extHostNotebooks.$acceptModelChanged(notebookUri, { + kind: NotebookCellsChangeType.ModelChange, versionId: notebook.versionId + 1, changes: [[1, 0, [{ handle: 2, @@ -185,6 +189,7 @@ suite('NotebookConcatDocument', function () { // UPDATE 3 (remove cell #2 again) extHostNotebooks.$acceptModelChanged(notebookUri, { + kind: NotebookCellsChangeType.ModelChange, versionId: notebook.versionId + 1, changes: [[1, 1, []]] }); @@ -202,6 +207,7 @@ suite('NotebookConcatDocument', function () { // UPDATE 1 extHostNotebooks.$acceptModelChanged(notebookUri, { + kind: NotebookCellsChangeType.ModelChange, versionId: notebook.versionId + 1, changes: [[0, 0, [{ handle: 1, @@ -264,6 +270,7 @@ suite('NotebookConcatDocument', function () { test('selector', function () { extHostNotebooks.$acceptModelChanged(notebookUri, { + kind: NotebookCellsChangeType.ModelChange, versionId: notebook.versionId + 1, changes: [[0, 0, [{ handle: 1, @@ -291,6 +298,7 @@ suite('NotebookConcatDocument', function () { assertLines(barLangDoc, 'barLang-document'); extHostNotebooks.$acceptModelChanged(notebookUri, { + kind: NotebookCellsChangeType.ModelChange, versionId: notebook.versionId + 1, changes: [[2, 0, [{ handle: 3, @@ -323,6 +331,7 @@ suite('NotebookConcatDocument', function () { test('offsetAt(position) <-> positionAt(offset)', function () { extHostNotebooks.$acceptModelChanged(notebookUri, { + kind: NotebookCellsChangeType.ModelChange, versionId: notebook.versionId + 1, changes: [[0, 0, [{ handle: 1, @@ -373,6 +382,7 @@ suite('NotebookConcatDocument', function () { test('locationAt(position) <-> positionAt(location)', function () { extHostNotebooks.$acceptModelChanged(notebookUri, { + kind: NotebookCellsChangeType.ModelChange, versionId: notebook.versionId + 1, changes: [[0, 0, [{ handle: 1, @@ -407,6 +417,7 @@ suite('NotebookConcatDocument', function () { test('getText(range)', function () { extHostNotebooks.$acceptModelChanged(notebookUri, { + kind: NotebookCellsChangeType.ModelChange, versionId: notebook.versionId + 1, changes: [[0, 0, [{ handle: 1, diff --git a/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts b/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts index a96eeea336..16967ee12e 100644 --- a/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts +++ b/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts @@ -51,7 +51,7 @@ suite('MainThreadDocumentsAndEditors', () => { const dialogService = new TestDialogService(); const notificationService = new TestNotificationService(); const undoRedoService = new UndoRedoService(dialogService, notificationService); - modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService(), undoRedoService, dialogService); + modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService(), undoRedoService); codeEditorService = new TestCodeEditorService(); textFileService = new class extends mock() { isDirty() { return false; } diff --git a/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts b/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts index 1fbfff9eb7..adec2a43b3 100644 --- a/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts +++ b/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts @@ -73,7 +73,7 @@ suite('MainThreadEditors', () => { const dialogService = new TestDialogService(); const notificationService = new TestNotificationService(); const undoRedoService = new UndoRedoService(dialogService, notificationService); - modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService(), undoRedoService, dialogService); + modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService(), undoRedoService); const services = new ServiceCollection(); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 15a8362314..b7d51b8b32 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -632,7 +632,7 @@ export class TestEditorService implements EditorServiceImpl { constructor(private editorGroupService?: IEditorGroupsService) { } getEditors() { return []; } - getEditorOverrides(editorInput: IEditorInput, options: IEditorOptions | undefined, group: IEditorGroup | undefined): [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry][] { return []; } + getEditorOverrides(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined): [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry][] { return []; } overrideOpenEditor(_handler: IOpenEditorOverrideHandler): IDisposable { return toDisposable(() => undefined); } registerCustomEditorViewTypesHandler(source: string, handler: ICustomEditorViewTypesHandler): IDisposable { throw new Error('Method not implemented.'); diff --git a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts index 5f717f0b11..2c5cab773a 100644 --- a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts @@ -79,7 +79,7 @@ suite.skip('TextSearch performance (integration)', () => { [IDialogService, dialogService], [INotificationService, notificationService], [IUndoRedoService, undoRedoService], - [IModelService, new ModelServiceImpl(configurationService, textResourcePropertiesService, new TestThemeService(), logService, undoRedoService, dialogService)], + [IModelService, new ModelServiceImpl(configurationService, textResourcePropertiesService, new TestThemeService(), logService, undoRedoService)], [IWorkspaceContextService, new TestContextService(testWorkspace(URI.file(testWorkspacePath)))], [IEditorService, new TestEditorService()], [IEditorGroupsService, new TestEditorGroupsService()], @@ -186,6 +186,14 @@ class TestTelemetryService implements ITelemetryService { return this.publicLog(eventName, data as any); } + public publicLogError(eventName: string, data?: any): Promise { + return this.publicLog(eventName, data); + } + + public publicLogError2 = never, T extends GDPRClassification = never>(eventName: string, data?: StrictPropertyCheck) { + return this.publicLogError(eventName, data as any); + } + public getTelemetryInfo(): Promise { return Promise.resolve({ instanceId: 'someValue.instanceId', diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index 953c4ba85a..36c403536c 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -119,7 +119,7 @@ interface IDefaultSideBarLayout { }[]; } | { id: 'explorer' | 'run' | 'scm' | 'search' | 'extensions' | 'remote' | string; - active?: false | undefined; + active?: false; order?: number; visible?: boolean; views?: { @@ -140,15 +140,24 @@ interface IDefaultPanelLayout { } | { id: 'terminal' | 'debug' | 'problems' | 'output' | 'comments' | string; order?: number; - active?: false | undefined; + active?: false; visible?: boolean; })[]; } +interface IDefaultEditor { + path: string; + scheme: string; + active?: boolean; +} + interface IDefaultLayout { sidebar?: IDefaultSideBarLayout; panel?: IDefaultPanelLayout; - // editors?: IDefaultWorkspaceEditorsLayout + editors?: IDefaultEditor[]; + + // Internal only + firstRun?: boolean; } interface IWorkbenchConstructionOptions { diff --git a/test/automation/src/code.ts b/test/automation/src/code.ts index b994e280b3..021a815ff2 100644 --- a/test/automation/src/code.ts +++ b/test/automation/src/code.ts @@ -132,7 +132,7 @@ export async function spawn(options: SpawnOptions): Promise { const args = [ options.workspacePath, - '--skip-getting-started', + '--skip-release-notes', '--disable-telemetry', '--disable-updates', '--disable-crash-reporter', diff --git a/test/automation/src/playwrightDriver.ts b/test/automation/src/playwrightDriver.ts index 69af0616d3..c0ae908c20 100644 --- a/test/automation/src/playwrightDriver.ts +++ b/test/automation/src/playwrightDriver.ts @@ -141,7 +141,7 @@ function waitForEndpoint(): Promise { export function connect(browserType: 'chromium' | 'webkit' | 'firefox' = 'chromium'): Promise<{ client: IDisposable, driver: IDriver }> { return new Promise(async (c) => { - const browser = await playwright[browserType].launch({ headless: false, dumpio: true }); + const browser = await playwright[browserType].launch({ headless: false }); const context = await browser.newContext(); const page = await context.newPage(); await page.setViewportSize({ width, height }); diff --git a/test/integration/browser/src/index.ts b/test/integration/browser/src/index.ts index aa5f3cb306..b835c8b901 100644 --- a/test/integration/browser/src/index.ts +++ b/test/integration/browser/src/index.ts @@ -31,7 +31,7 @@ const height = 800; async function runTestsInBrowser(browserType: 'chromium' | 'firefox' | 'webkit', endpoint: url.UrlWithStringQuery, server: cp.ChildProcess): Promise { const args = process.platform === 'linux' && browserType === 'chromium' ? ['--no-sandbox'] : undefined; // disable sandbox to run chrome on certain Linux distros - const browser = await playwright[browserType].launch({ headless: !Boolean(optimist.argv.debug), dumpio: true, args }); + const browser = await playwright[browserType].launch({ headless: !Boolean(optimist.argv.debug), args }); const context = await browser.newContext(); const page = await context.newPage(); await page.setViewportSize({ width, height }); diff --git a/test/unit/browser/index.js b/test/unit/browser/index.js index 4ab8e87b58..8cb6fe0896 100644 --- a/test/unit/browser/index.js +++ b/test/unit/browser/index.js @@ -119,7 +119,7 @@ const testModules = (async function () { async function runTestsInBrowser(testModules, browserType) { const args = process.platform === 'linux' && browserType === 'chromium' ? ['--no-sandbox'] : undefined; // disable sandbox to run chrome on certain Linux distros - const browser = await playwright[browserType].launch({ headless: !Boolean(argv.debug), dumpio: true, args }); + const browser = await playwright[browserType].launch({ headless: !Boolean(argv.debug), args }); const context = await browser.newContext(); const page = await context.newPage(); const target = url.pathToFileURL(path.join(__dirname, 'renderer.html')); diff --git a/yarn.lock b/yarn.lock index 19ae14dd69..f18955be1c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3316,7 +3316,7 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" -extract-zip@^1.0.3, extract-zip@^1.6.6: +extract-zip@^1.0.3: version "1.6.7" resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.7.tgz#a840b4b8af6403264c8db57f4f1a74333ef81fe9" integrity sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k= @@ -3326,6 +3326,17 @@ extract-zip@^1.0.3, extract-zip@^1.6.6: mkdirp "0.5.1" yauzl "2.4.1" +extract-zip@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.0.tgz#f53b71d44f4ff5a4527a2259ade000fb8b303492" + integrity sha512-i42GQ498yibjdvIhivUsRslx608whtGoFIhF26Z7O4MYncBxp8CwalOs1lnHy21A9sIohWO2+uiE4SRtC9JXDg== + dependencies: + debug "^4.1.1" + get-stream "^5.1.0" + yauzl "^2.10.0" + optionalDependencies: + "@types/yauzl" "^2.9.1" + extsprintf@1.3.0, extsprintf@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" @@ -5318,10 +5329,10 @@ jade@0.26.3: commander "0.6.1" mkdirp "0.3.0" -jpeg-js@^0.3.6: - version "0.3.6" - resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.3.6.tgz#c40382aac9506e7d1f2d856eb02f6c7b2a98b37c" - integrity sha512-MUj2XlMB8kpe+8DJUGH/3UJm4XpI8XEgZQ+CiHDeyrGoKPdW/8FJv6ku+3UiYm5Fz3CWaL+iXmD8Q4Ap6aC1Jw== +jpeg-js@^0.3.7: + version "0.3.7" + resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.3.7.tgz#471a89d06011640592d314158608690172b1028d" + integrity sha512-9IXdWudL61npZjvLuVe/ktHiA41iE8qFyLB+4VDTblEsWBzeg8WQTlktdUK4CdncUqtUgUg0bbOmTE2bKBKaBQ== jquery@3.4.0: version "3.4.0" @@ -6093,6 +6104,11 @@ mime@^1.4.1: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +mime@^2.4.4: + version "2.4.4" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5" + integrity sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA== + mimic-fn@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" @@ -6354,10 +6370,10 @@ native-is-elevated@0.4.1: resolved "https://registry.yarnpkg.com/native-is-elevated/-/native-is-elevated-0.4.1.tgz#f6391aafb13441f5b949b39ae0b466b06e7f3986" integrity sha512-2vBXCXCXYKLDjP0WzrXs/AFjDb2njPR31EbGiZ1mR2fMJg211xClK1Xm19RXve35kvAL4dBKOFGCMIyc2+pPsw== -native-keymap@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/native-keymap/-/native-keymap-2.1.1.tgz#8b6ba2d4e81b5fe44948ebe3ff88e3777b70d4d6" - integrity sha512-8ACRLXS9xMmRQVWK8YYp0zUSW4PjXuF2VGYu1kq0WYcND9yyr3BASyhopn+VXP3nDehS3Ct5FjDtsOiLEhBvmQ== +native-keymap@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/native-keymap/-/native-keymap-2.1.2.tgz#9773313f619d4c2b66b452cf036310a145523b59" + integrity sha512-n+oe+sxaauCFxomkl9Xrw1iUp88jTamMaGJSHNSGZ8rkIN9N+Wi6KIvBO8x3nmFxLI27KWu1d8IrLBxFKPNQag== native-watchdog@1.3.0: version "1.3.0" @@ -6731,18 +6747,6 @@ onetime@^5.1.0: dependencies: mimic-fn "^2.1.0" -onigasm-umd@2.2.5: - version "2.2.5" - resolved "https://registry.yarnpkg.com/onigasm-umd/-/onigasm-umd-2.2.5.tgz#f104247334a543accd3f8d641a4d99b3d908d6a1" - integrity sha512-R3qD7hq6i2bBklF+QyjqZl/G4fe7GwtukI28YLH2vuiatqx52tb9vpg2sxwemKc3nF76SgkeyOKJLchBmTm0Aw== - -oniguruma@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/oniguruma/-/oniguruma-7.2.0.tgz#c9a59c1ea7b9fe67e237a02e02139b638856f3af" - integrity sha512-bh+ZLdykY1sdIx8jBp2zpLbVFDBc3XmKH4Ceo2lijNaN1WhEqtnpqFlmtCbRuDB17nJ58RAUStVwfW8e8uEbnA== - dependencies: - nan "^2.14.0" - opn@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/opn/-/opn-6.0.0.tgz#3c5b0db676d5f97da1233d1ed42d182bc5a27d2d" @@ -7164,27 +7168,28 @@ pkg-dir@^3.0.0: dependencies: find-up "^3.0.0" -playwright-core@=0.12.1: - version "0.12.1" - resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-0.12.1.tgz#07581d1cbe84bb1e438ecdb188de3ed6d5e81ee0" - integrity sha512-NZ8Qe/kqsgAmFBxWZnUeE+MoZ04UzNI0DHOKA+I1p/5rbpaWhe1Vx5zVNa05A1iEvOtnKV1PdIEe4IPumG2y2w== +playwright-core@=0.15.0: + version "0.15.0" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-0.15.0.tgz#c605c98a13c81d5a2e2691f15d04758cf302c20a" + integrity sha512-uTm4PoF2U3iXkLMMG9vlTxlGfO8atQGAHDxqi8xV7hEjNSYeLTU7c6HN5zwadeHRVuBbNsZ4yqu9u4hoqC7uxQ== dependencies: - debug "^4.1.0" - extract-zip "^1.6.6" + debug "^4.1.1" + extract-zip "^2.0.0" https-proxy-agent "^3.0.0" - jpeg-js "^0.3.6" - pngjs "^3.4.0" + jpeg-js "^0.3.7" + mime "^2.4.4" + pngjs "^5.0.0" progress "^2.0.3" proxy-from-env "^1.1.0" rimraf "^3.0.2" ws "^6.1.0" -playwright@0.12.1: - version "0.12.1" - resolved "https://registry.yarnpkg.com/playwright/-/playwright-0.12.1.tgz#59445ede1aecec120091db7bc95b4e626451e0b0" - integrity sha512-icF4+I8y7A5HjhbTsa4Eqtl2fuGe3ECvW0Wrn6aRM5eL5/AqUIgIf2U/0e1S1bEsDfz1JVvClGl5Gqw4aI5H4w== +playwright@0.15.0: + version "0.15.0" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-0.15.0.tgz#bf5c3bb8404975aba78459310742388c08438431" + integrity sha512-UGHkQz8DT43uJ0KgMh2rmj8BI4FE5ReQJ9nm5mG68tt1Cj2sXPdM2b05qptfYYBPtQRetQqtJTauZ6rlCDemaQ== dependencies: - playwright-core "=0.12.1" + playwright-core "=0.15.0" plist@^3.0.1: version "3.0.1" @@ -7221,10 +7226,10 @@ plugin-error@^1.0.1: arr-union "^3.1.0" extend-shallow "^3.0.2" -pngjs@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f" - integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w== +pngjs@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" + integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== posix-character-classes@^0.1.0: version "0.1.1" @@ -9512,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.20200420: - version "3.9.0-dev.20200420" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.0-dev.20200420.tgz#99c2bc0936dbf4479b0b5260d80475ed494b1532" - integrity sha512-36MW6V+oXNnsSgliSjUWvtOkO21g9+iFGHPFv+O06HsCl3dcuqzBac17m8xuOuWo1LUlEgS6yAnD9fiVgvmCfg== +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== uc.micro@^1.0.1, uc.micro@^1.0.3: version "1.0.3" @@ -9958,6 +9963,11 @@ vscode-nsfw@1.2.8: lodash.isundefined "^3.0.1" nan "^2.10.0" +vscode-oniguruma@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.3.0.tgz#6788a9db2f8b0781243b4eb8c7a1dd25f6c0e2c8" + integrity sha512-m4Br19v6XD4MRbVrgsLNSZgQrBzk1BCMCleL8+GrcoGxKEJJd62zOFcTaoQR3hCrSlLqoxWmJ7Cc0VieVV3iTQ== + vscode-proxy-agent@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.5.2.tgz#0c90d24d353957b841d741da7b2701e3f0a044c4" @@ -9980,12 +9990,10 @@ vscode-sqlite3@4.0.10: dependencies: nan "^2.14.0" -vscode-textmate@4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-4.4.0.tgz#14032afeb50152e8f53258c95643e555f2948305" - integrity sha512-dFpm2eK0HwEjeFSD1DDh3j0q47bDSVuZt20RiJWxGqjtm73Wu2jip3C2KaZI3dQx/fSeeXCr/uEN4LNaNj7Ytw== - dependencies: - oniguruma "^7.2.0" +vscode-textmate@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-5.1.1.tgz#d88dbf271bee7cede455a21bd4894ba5724a4a7e" + integrity sha512-5VHjF+Fglf9d2JI5OyQ7FHutK6/29G0qYyD920K0SWO7uY8JTWbqyKAHEtfB/ZDk2fOe/E23n3wz9fHXKi63yg== vscode-windows-ca-certs@0.2.0: version "0.2.0" @@ -10301,15 +10309,15 @@ xterm-addon-web-links@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-webgl@0.7.0-beta.6: - version "0.7.0-beta.6" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.7.0-beta.6.tgz#340d728bae7456a1d67a7cb8996dfd51d96721b0" - integrity sha512-IoR0hPtG5qBrcLG1B7GSzo4W2hYocP8UgG5LlyXkEkT/0BqVcGnICgR8Ck7EfMmU8ci4jNFiHYjK/Bgc4m2S4g== +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@4.6.0-beta.15: - version "4.6.0-beta.15" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.6.0-beta.15.tgz#93a0cb1ff047a452b03070499e2ccd0ba53e0678" - integrity sha512-5G+3QSM/GKN/Tdq/IIU7FDivzh0eXsv3sNmZFDdtvNggnu2K56l4N5P0KZo0HdFztDfTyW/FLfqsPAwhrK4Khg== +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== y18n@^3.2.1: version "3.2.1" @@ -10394,6 +10402,14 @@ yauzl@2.4.1: dependencies: fd-slicer "~1.0.1" +yauzl@^2.10.0, yauzl@^2.9.2: + version "2.10.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0" + yauzl@^2.2.1, yauzl@^2.3.1: version "2.9.1" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.9.1.tgz#a81981ea70a57946133883f029c5821a89359a7f" @@ -10402,14 +10418,6 @@ yauzl@^2.2.1, yauzl@^2.3.1: buffer-crc32 "~0.2.3" fd-slicer "~1.0.1" -yauzl@^2.9.2: - version "2.10.0" - resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" - integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= - dependencies: - buffer-crc32 "~0.2.3" - fd-slicer "~1.1.0" - yazl@^2.2.1, yazl@^2.2.2, yazl@^2.4.3: version "2.4.3" resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.4.3.tgz#ec26e5cc87d5601b9df8432dbdd3cd2e5173a071" From 0cae38f8aea03e72aef3a91bf19a15c9e63241a8 Mon Sep 17 00:00:00 2001 From: AzureDataStudio Date: Wed, 29 Apr 2020 19:32:06 -0700 Subject: [PATCH 2/4] ignore errors --- src/sql/workbench/browser/parts/views/treeView.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/sql/workbench/browser/parts/views/treeView.ts b/src/sql/workbench/browser/parts/views/treeView.ts index aa73bbdc78..1a3694a258 100644 --- a/src/sql/workbench/browser/parts/views/treeView.ts +++ b/src/sql/workbench/browser/parts/views/treeView.ts @@ -192,6 +192,7 @@ export class TreeView extends Disposable implements ITreeView { this._register(this.themeService.onDidColorThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/)); this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('explorer.decorations')) { + // eslint-ignore-next-line @typescript-eslint/no-floating-promises this.doRefresh([this.root]); /** soft refresh **/ } })); @@ -263,6 +264,7 @@ export class TreeView extends Disposable implements ITreeView { this._register(this._dataProvider.onDidChangeEmpty(() => this._onDidChangeWelcomeState.fire())); } this.updateMessage(); + // eslint-ignore-next-line @typescript-eslint/no-floating-promises this.refresh(); } else { this._dataProvider = undefined; @@ -386,6 +388,7 @@ export class TreeView extends Disposable implements ITreeView { } if (this.isVisible && this.elementsToRefresh.length) { + // eslint-ignore-next-line @typescript-eslint/no-floating-promises this.doRefresh(this.elementsToRefresh); this.elementsToRefresh = []; } From 2a71032284a4e1d1d87bec78e7cfd19e6b62f6e1 Mon Sep 17 00:00:00 2001 From: AzureDataStudio Date: Wed, 29 Apr 2020 19:45:35 -0700 Subject: [PATCH 3/4] wrong eslint command --- src/sql/workbench/browser/parts/views/treeView.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sql/workbench/browser/parts/views/treeView.ts b/src/sql/workbench/browser/parts/views/treeView.ts index 1a3694a258..b2f8b25a27 100644 --- a/src/sql/workbench/browser/parts/views/treeView.ts +++ b/src/sql/workbench/browser/parts/views/treeView.ts @@ -192,7 +192,7 @@ export class TreeView extends Disposable implements ITreeView { this._register(this.themeService.onDidColorThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/)); this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('explorer.decorations')) { - // eslint-ignore-next-line @typescript-eslint/no-floating-promises + // eslint-disable-next-line @typescript-eslint/no-floating-promises this.doRefresh([this.root]); /** soft refresh **/ } })); @@ -264,7 +264,7 @@ export class TreeView extends Disposable implements ITreeView { this._register(this._dataProvider.onDidChangeEmpty(() => this._onDidChangeWelcomeState.fire())); } this.updateMessage(); - // eslint-ignore-next-line @typescript-eslint/no-floating-promises + // eslint-disable-next-line @typescript-eslint/no-floating-promises this.refresh(); } else { this._dataProvider = undefined; @@ -388,7 +388,7 @@ export class TreeView extends Disposable implements ITreeView { } if (this.isVisible && this.elementsToRefresh.length) { - // eslint-ignore-next-line @typescript-eslint/no-floating-promises + // eslint-disable-next-line @typescript-eslint/no-floating-promises this.doRefresh(this.elementsToRefresh); this.elementsToRefresh = []; } From f8232be553e29a2593c599edacf33677912e2404 Mon Sep 17 00:00:00 2001 From: AzureDataStudio Date: Wed, 29 Apr 2020 21:40:29 -0700 Subject: [PATCH 4/4] fix compile --- src/sql/workbench/browser/parts/views/treeView.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sql/workbench/browser/parts/views/treeView.ts b/src/sql/workbench/browser/parts/views/treeView.ts index b2f8b25a27..a5a755222c 100644 --- a/src/sql/workbench/browser/parts/views/treeView.ts +++ b/src/sql/workbench/browser/parts/views/treeView.ts @@ -3,7 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/views'; import { Event, Emitter } from 'vs/base/common/event'; import { IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';