From 6ff1e3866bcad2a8a639fdaf6e6fe4aeab49b2b2 Mon Sep 17 00:00:00 2001 From: Christopher Suh Date: Sat, 3 Oct 2020 14:42:05 -0400 Subject: [PATCH] Merge from vscode fcf3346a8e9f5ee1e00674461d9e2c2292a14ee3 (#12295) * Merge from vscode fcf3346a8e9f5ee1e00674461d9e2c2292a14ee3 * Fix test build break * Update distro * Fix build errors * Update distro * Update REH build file * Update build task names for REL * Fix product build yaml * Fix product REH task name * Fix type in task name * Update linux build step * Update windows build tasks * Turn off server publish * Disable REH * Fix typo * Bump distro * Update vscode tests * Bump distro * Fix type in disto * Bump distro * Turn off docker build * Remove docker step from release Co-authored-by: ADS Merger Co-authored-by: Karl Burtram --- .devcontainer/Dockerfile | 1 + .github/subscribers.json | 7 + .github/workflows/deep-classifier-runner.yml | 50 + .github/workflows/deep-classifier-scraper.yml | 27 + .github/workflows/latest-release-monitor.yml | 27 + .vscode/notebooks/api.github-issues | 2 +- .vscode/notebooks/my-work.github-issues | 2 +- .vscode/notebooks/verification.github-issues | 2 +- .vscode/searches/TrustedTypes.code-search | 194 +++ .vscode/searches/es6.code-search | 48 +- .vscode/searches/ts36031.code-search | 58 +- .yarnrc | 2 +- build/azure-pipelines/common/createAsset.ts | 2 +- .../azure-pipelines/common/publish-webview.ts | 4 +- .../darwin/product-build-darwin.yml | 4 - .../darwin/sql-product-build-darwin.yml | 2 - build/azure-pipelines/exploration-build.yml | 4 +- .../linux/product-build-linux.yml | 65 +- build/azure-pipelines/linux/publish.sh | 37 +- .../linux/sql-product-build-linux.yml | 3 +- build/azure-pipelines/product-build.yml | 19 +- build/azure-pipelines/product-compile.yml | 12 +- build/azure-pipelines/sql-product-build.yml | 20 +- build/azure-pipelines/sql-product-compile.yml | 4 +- build/azure-pipelines/win32/createDrop.ps1 | 6 +- .../win32/sql-product-build-win32.yml | 6 +- build/gulpfile.vscode.js | 4 +- build/gulpfile.vscode.linux.js | 56 +- build/lib/electron.js | 2 +- build/lib/electron.ts | 2 +- build/lib/i18n.resources.json | 4 + build/lib/i18n.ts | 2 +- build/lib/layersChecker.js | 52 +- build/lib/layersChecker.ts | 57 +- build/lib/preLaunch.ts | 2 +- cgmanifest.json | 4 +- extensions/bat/language-configuration.json | 6 +- .../schemas/attachContainer.schema.json | 1 + .../schemas/devContainer.schema.json | 1 + extensions/git/package.json | 123 +- extensions/git/package.nls.json | 16 +- extensions/git/src/api/git.d.ts | 1 + extensions/git/src/commands.ts | 82 +- extensions/git/src/decorationProvider.ts | 2 +- extensions/git/src/git.ts | 37 +- extensions/git/src/main.ts | 2 +- extensions/git/src/model.ts | 4 +- extensions/git/src/util.ts | 10 - extensions/github/package.nls.json | 4 +- .../json-language-features/server/README.md | 26 +- .../server/src/jsonServer.ts | 13 +- .../microsoft-authentication/src/AADHelper.ts | 3 +- extensions/python/package.json | 2 +- extensions/sql/cgmanifest.json | 2 +- extensions/sql/syntaxes/sql.tmLanguage.json | 2 +- .../theme-defaults/themes/dark_defaults.json | 3 +- .../theme-defaults/themes/light_defaults.json | 1 + .../src/notebook.test.ts | 1376 +++++++++++++++++ package.json | 16 +- remote/package.json | 10 +- remote/web/package.json | 5 +- remote/web/yarn.lock | 21 +- remote/yarn.lock | 67 +- resources/linux/code-workspace.xml | 7 + resources/linux/code.desktop | 2 +- resources/linux/debian/postinst.template | 5 + resources/linux/debian/postrm.template | 7 +- resources/linux/rpm/code.spec.template | 9 + resources/linux/rpm/dependencies.json | 132 +- resources/web/code-web.js | 5 +- src/main.js | 10 +- .../base/browser/ui/buttonMenu/buttonMenu.ts | 12 +- .../base/browser/ui/panel/panel.component.ts | 5 +- src/sql/base/browser/ui/panel/panel.ts | 7 +- .../browser/ui/table/highPerf/tableView.ts | 5 +- .../browser/ui/table/highPerf/tableWidget.ts | 9 +- .../platform/accounts/common/accountStore.ts | 9 +- .../connection/common/connectionConfig.ts | 15 +- .../common/connectionProfileGroup.ts | 3 +- .../common/connectionStatusManager.ts | 7 +- .../connection/common/connectionStore.ts | 7 +- .../common/providerConnectionInfo.ts | 7 +- .../test/common/connectionConfig.test.ts | 23 +- .../test/common/connectionStore.test.ts | 3 +- .../test/node/connectionStatusManager.test.ts | 2 +- .../common/serializationService.ts | 3 +- .../browser/mainThreadAccountManagement.ts | 3 +- .../api/browser/mainThreadDashboardWebview.ts | 3 +- .../api/browser/mainThreadModelView.ts | 3 +- .../mainThreadNotebookDocumentsAndEditors.ts | 9 +- .../api/common/extHostAccountManagement.ts | 7 +- .../api/common/extHostDataProtocol.ts | 3 +- .../workbench/api/common/extHostModelView.ts | 5 +- .../api/common/extHostNotebookEditor.ts | 3 +- .../api/common/extHostResourceProvider.ts | 3 +- .../browser/editor/profiler/profilerInput.ts | 5 +- src/sql/workbench/browser/modal/modal.ts | 5 +- .../browser/modelComponents/componentBase.ts | 3 +- .../declarativeTable.component.ts | 6 +- .../modelComponents/dropdown.component.ts | 5 +- .../formContainer.component.ts | 3 +- .../browser/modelComponents/modelStore.ts | 3 +- .../modelComponents/treeComponentRenderer.ts | 5 +- .../browser/asmtResultsView.component.ts | 7 +- .../electron-browser/commandLine.ts | 12 +- .../test/electron-browser/commandLine.test.ts | 4 +- .../dashboardContainer.contribution.ts | 3 +- .../dashboardGridContainer.contribution.ts | 3 +- .../dashboardNavSection.component.ts | 3 +- .../dashboardWidgetContainer.contribution.ts | 3 +- .../contents/widgetContent.component.ts | 9 +- .../contrib/dashboard/browser/core/actions.ts | 5 +- .../dashboard/browser/core/dashboardHelper.ts | 5 +- .../browser/core/dashboardPage.component.ts | 11 +- .../dashboard/browser/dashboardRegistry.ts | 3 +- .../editData/browser/editDataActions.ts | 5 +- .../browser/jobHistory.component.ts | 5 +- .../browser/jobsView.component.ts | 11 +- .../browser/notebooksView.component.ts | 11 +- .../notebook/browser/cellToolbarActions.ts | 3 +- .../browser/models/cellMagicMapper.ts | 3 +- .../notebook/browser/notebook.component.ts | 17 +- .../notebook/browser/notebookActions.ts | 11 +- .../profiler/browser/profilerEditor.ts | 15 +- .../contrib/query/browser/gridPanel.ts | 12 +- .../query/browser/keyboardQueryActions.ts | 3 +- .../contrib/views/browser/treeView.ts | 10 +- .../welcome/page/browser/welcomePage.ts | 2 +- .../browser/accountManagementService.ts | 7 +- .../browser/accountPickerImpl.ts | 3 +- .../connection/browser/connectionActions.ts | 3 +- .../browser/connectionController.ts | 3 +- .../browser/connectionDialogService.ts | 3 +- .../browser/connectionManagementService.ts | 15 +- .../connection/browser/connectionWidget.ts | 23 +- .../connection/test/browser/testTreeView.ts | 7 +- .../browser/newDashboardTabViewModel.ts | 3 +- .../electron-browser/insightsUtils.test.ts | 4 +- .../browser/jobManagementUtilities.ts | 13 +- .../services/notebook/browser/models/cell.ts | 5 +- .../notebook/browser/models/notebookModel.ts | 31 +- .../notebook/browser/sql/sqlSessionManager.ts | 5 +- .../browser/serverTreeActionProvider.ts | 5 +- .../browser/objectExplorerService.test.ts | 9 +- .../profiler/browser/profilerFilterDialog.ts | 5 +- .../services/query/common/queryRunner.ts | 3 +- .../common/queryHistoryServiceImpl.ts | 3 +- .../services/tasks/common/tasksService.ts | 3 +- .../api/extHostObjectExplorer.test.ts | 3 +- src/tsconfig.vscode.json | 2 +- src/typings/trustedTypes.d.ts | 36 + src/vs/base/browser/codicons.ts | 7 +- src/vs/base/browser/dom.ts | 124 +- src/vs/base/browser/markdownRenderer.ts | 9 +- .../browser/ui/actionbar/actionViewItems.ts | 68 +- src/vs/base/browser/ui/actionbar/actionbar.ts | 28 +- .../ui/breadcrumbs/breadcrumbsWidget.ts | 10 +- src/vs/base/browser/ui/button/button.ts | 50 +- .../browser/ui/codicons/codicon/codicon.ttf | Bin 61024 -> 61532 bytes .../base/browser/ui/codicons/codiconLabel.ts | 4 +- .../base/browser/ui/codicons/codiconStyles.ts | 2 +- .../browser/ui/contextview/contextview.ts | 6 +- src/vs/base/browser/ui/dropdown/dropdown.ts | 14 +- .../ui/dropdown/dropdownActionViewItem.ts | 25 +- .../ui/highlightedlabel/highlightedLabel.ts | 37 +- src/vs/base/browser/ui/list/listView.ts | 50 +- src/vs/base/browser/ui/list/listWidget.ts | 31 +- src/vs/base/browser/ui/list/rowCache.ts | 4 +- src/vs/base/browser/ui/menu/menu.ts | 4 +- src/vs/base/browser/ui/menu/menubar.ts | 22 +- src/vs/base/browser/ui/sash/sash.ts | 30 +- .../browser/ui/selectBox/selectBoxCustom.ts | 73 +- .../browser/ui/selectBox/selectBoxNative.ts | 2 +- src/vs/base/browser/ui/splitview/paneview.ts | 39 +- src/vs/base/browser/ui/splitview/splitview.ts | 28 +- src/vs/base/browser/ui/toolbar/toolbar.ts | 2 +- src/vs/base/browser/ui/tree/abstractTree.ts | 46 +- src/vs/base/browser/ui/tree/asyncDataTree.ts | 9 +- src/vs/base/common/arrays.ts | 33 +- src/vs/base/common/async.ts | 26 +- src/vs/base/common/codicons.ts | 17 +- src/vs/base/common/objects.ts | 10 +- src/vs/base/common/resources.ts | 6 +- src/vs/base/common/skipList.ts | 14 +- src/vs/base/node/processes.ts | 4 +- src/vs/base/node/ps.ts | 11 +- src/vs/base/node/terminalEncoding.ts | 4 +- src/vs/base/node/zip.ts | 9 +- src/vs/base/parts/ipc/node/ipc.cp.ts | 13 +- .../parts/quickinput/browser/quickInput.ts | 6 +- src/vs/base/parts/request/browser/request.ts | 6 +- .../parts/sandbox/electron-browser/preload.js | 1 + .../parts/sandbox/electron-sandbox/globals.ts | 5 + src/vs/base/test/browser/actionbar.test.ts | 35 +- src/vs/base/test/browser/codicons.test.ts | 14 +- .../browser/ui/tree/asyncDataTree.test.ts | 45 +- src/vs/base/test/common/async.test.ts | 2 +- src/vs/base/test/common/utils.ts | 6 +- src/vs/base/worker/workerMain.ts | 3 +- .../code/browser/workbench/workbench-dev.html | 4 +- src/vs/code/browser/workbench/workbench.html | 4 +- src/vs/code/browser/workbench/workbench.ts | 15 +- .../contrib/languagePackCachedDataCleaner.ts | 3 +- .../contrib/nodeCachedDataCleaner.ts | 3 +- .../contrib/storageDataCleaner.ts | 3 +- .../sharedProcess/sharedProcessMain.ts | 6 +- .../electron-browser/workbench/workbench.js | 39 +- src/vs/code/electron-main/app.ts | 3 +- src/vs/code/electron-main/auth.ts | 7 +- src/vs/code/electron-main/main.ts | 124 +- src/vs/code/electron-main/sharedProcess.ts | 4 +- src/vs/code/electron-main/window.ts | 15 +- .../issue/issueReporterMain.ts | 81 +- .../issue/issueReporterModel.ts | 42 - .../issue/test/testReporterModel.test.ts | 11 - .../processExplorer/processExplorerMain.ts | 17 +- src/vs/code/node/cli.ts | 11 +- src/vs/code/node/cliProcessMain.ts | 12 +- src/vs/code/node/paths.ts | 102 -- src/vs/code/node/shellEnv.ts | 2 +- .../browser/controller/pointerHandler.ts | 6 +- .../editor/browser/services/openerService.ts | 3 +- .../browser/viewParts/lines/viewLine.ts | 12 +- .../common/modes/languageFeatureRegistry.ts | 48 + .../common/modes/supports/tokenization.ts | 10 +- .../services/markerDecorationsServiceImpl.ts | 9 +- src/vs/editor/common/view/viewContext.ts | 5 +- src/vs/editor/contrib/codelens/codelens.ts | 61 +- .../contrib/codelens/codelensController.ts | 152 +- .../editor/contrib/codelens/codelensWidget.ts | 21 +- .../contrib/colorPicker/colorPickerWidget.ts | 12 +- .../contrib/documentSymbols/outlineModel.ts | 21 +- src/vs/editor/contrib/format/format.ts | 2 +- .../contrib/gotoError/gotoErrorWidget.ts | 12 +- .../gotoSymbol/peek/referencesController.ts | 10 +- .../contrib/gotoSymbol/symbolNavigation.ts | 8 +- .../editor/contrib/hover/modesContentHover.ts | 4 +- src/vs/editor/contrib/links/getLinks.ts | 16 +- .../contrib/message/messageController.ts | 5 +- .../parameterHints/parameterHintsWidget.ts | 5 +- .../test/parameterHintsModel.test.ts | 2 +- .../contrib/rename/test/onTypeRename.test.ts | 2 +- .../contrib/smartSelect/bracketSelections.ts | 4 +- .../editor/contrib/smartSelect/smartSelect.ts | 8 +- .../contrib/snippet/snippetController2.ts | 6 +- .../editor/contrib/suggest/media/suggest.css | 1 + .../contrib/suggest/suggestAlternatives.ts | 4 +- .../contrib/suggest/suggestController.ts | 5 +- src/vs/editor/contrib/suggest/suggestModel.ts | 2 +- .../editor/contrib/suggest/wordContextKey.ts | 4 +- .../wordHighlighter/wordHighlighter.ts | 4 +- .../browser/inspectTokens/inspectTokens.ts | 5 +- .../browser/standaloneThemeServiceImpl.ts | 9 +- .../test/browser/standaloneLanguages.test.ts | 7 +- .../modes/supports/characterPair.test.ts | 3 +- src/vs/loader.js | 45 + .../backup/electron-main/backupMainService.ts | 5 +- .../electron-main/backupMainService.test.ts | 2 +- .../test/common/configurationService.test.ts | 4 +- .../electron-main/extensionHostDebugIpc.ts | 2 +- .../dialogs/electron-browser/dialogIpc.ts | 26 - src/vs/platform/driver/browser/baseDriver.ts | 2 +- .../platform/driver/electron-main/driver.ts | 2 +- src/vs/platform/electron/common/electron.ts | 11 + .../electron-main/electronMainService.ts | 43 +- src/vs/platform/environment/common/argv.ts | 105 ++ .../environment/common/environment.ts | 77 +- src/vs/platform/environment/node/argv.ts | 102 +- .../platform/environment/node/argvHelper.ts | 12 +- .../environment/node/environmentService.ts | 97 +- .../common/configRemotes.ts | 3 +- .../common/extensionGalleryService.ts | 393 +++-- .../node/extensionDownloader.ts | 3 +- .../node/extensionLifecycle.ts | 3 +- .../node/extensionManagementService.ts | 669 ++++---- .../node/extensionTipsService.ts | 3 +- .../node/extensionsManifestCache.ts | 2 +- .../node/extensionsScanner.ts | 100 +- .../test/node/extensionGalleryService.test.ts | 2 +- src/vs/platform/files/common/files.ts | 1 + .../node/watcher/nodejs/watcherService.ts | 4 +- .../node/watcher/nsfw/nsfwWatcherService.ts | 19 +- .../nsfw/test/nsfwWatcherService.test.ts | 5 +- .../watcher/unix/chokidarWatcherService.ts | 26 +- .../watcher/win32/csharpWatcherService.ts | 2 +- .../test/common/nullFileSystemProvider.ts | 24 +- src/vs/platform/issue/common/issue.ts | 10 +- .../issue/electron-main/issueMainService.ts | 5 +- .../launch/electron-main/launchMainService.ts | 16 +- .../electron-main/lifecycleMainService.ts | 6 +- .../localizations/node/localizations.ts | 3 +- .../platform/menubar/electron-main/menubar.ts | 3 +- src/vs/platform/remote/common/remoteHosts.ts | 4 +- .../electron-main/requestMainService.ts | 3 +- .../platform/request/node/requestService.ts | 11 +- src/vs/platform/state/node/stateService.ts | 3 +- .../storage/node/storageMainService.ts | 3 +- .../platform/storage/node/storageService.ts | 3 +- .../storage/test/node/storageService.test.ts | 2 +- src/vs/platform/theme/common/theme.ts | 13 + src/vs/platform/theme/common/themeService.ts | 15 +- .../theme/test/common/testThemeService.ts | 5 +- .../electron-main/abstractUpdateService.ts | 3 +- .../electron-main/updateService.darwin.ts | 3 +- .../electron-main/updateService.linux.ts | 3 +- .../electron-main/updateService.snap.ts | 3 +- .../electron-main/updateService.win32.ts | 3 +- src/vs/platform/url/common/urlIpc.ts | 3 +- .../url/electron-main/electronUrlListener.ts | 2 +- .../userDataSync/common/keybindingsMerge.ts | 4 +- .../userDataSync/common/settingsMerge.ts | 4 +- .../common/userDataSyncStoreService.ts | 7 +- .../webview/common/webviewPortMapping.ts | 4 +- .../electron-main/webviewProtocolProvider.ts | 3 +- src/vs/platform/windows/common/windows.ts | 36 +- .../platform/windows/electron-main/windows.ts | 10 +- .../electron-main/windowsMainService.ts | 28 +- src/vs/platform/windows/node/window.ts | 33 - .../workspacesHistoryMainService.ts | 3 +- .../electron-main/workspacesMainService.ts | 3 +- .../workspacesMainService.test.ts | 2 +- src/vs/vscode.d.ts | 94 +- src/vs/vscode.proposed.d.ts | 159 +- .../api/browser/extensionHost.contribution.ts | 1 + .../api/browser/mainThreadBulkEdits.ts | 44 + .../api/browser/mainThreadCustomEditors.ts | 10 +- .../api/browser/mainThreadDocuments.ts | 6 +- .../browser/mainThreadDocumentsAndEditors.ts | 4 +- .../api/browser/mainThreadEditors.ts | 2 +- .../api/browser/mainThreadFileSystem.ts | 11 +- .../api/browser/mainThreadMessageService.ts | 4 +- .../api/browser/mainThreadNotebook.ts | 259 ++-- .../api/browser/mainThreadProgress.ts | 2 +- src/vs/workbench/api/browser/mainThreadSCM.ts | 5 +- .../api/browser/mainThreadWebviewPanels.ts | 6 +- .../api/browser/mainThreadWebviewViews.ts | 25 +- .../api/browser/mainThreadWebviews.ts | 4 +- .../api/browser/viewsExtensionPoint.ts | 16 +- .../workbench/api/common/extHost.api.impl.ts | 20 +- .../api/common/extHost.common.services.ts | 4 +- .../workbench/api/common/extHost.protocol.ts | 32 +- .../api/common/extHostApiCommands.ts | 20 +- .../workbench/api/common/extHostBulkEdits.ts | 29 + .../common/extHostDocumentSaveParticipant.ts | 6 +- .../api/common/extHostExtensionService.ts | 2 +- .../workbench/api/common/extHostFileSystem.ts | 19 +- .../api/common/extHostFileSystemConsumer.ts | 14 +- .../common/extHostFileSystemEventService.ts | 6 +- .../api/common/extHostFileSystemInfo.ts | 35 + .../api/common/extHostLanguageFeatures.ts | 8 +- .../workbench/api/common/extHostNotebook.ts | 906 +---------- .../api/common/extHostNotebookDocument.ts | 527 +++++++ .../api/common/extHostNotebookEditor.ts | 230 +++ src/vs/workbench/api/common/extHostTask.ts | 6 +- .../api/common/extHostTerminalService.ts | 6 +- .../api/common/extHostTextEditors.ts | 7 - .../api/common/extHostTypeConverters.ts | 5 +- src/vs/workbench/api/common/extHostTypes.ts | 17 +- .../api/common/extHostWebviewView.ts | 24 +- .../api/common/menusExtensionPoint.ts | 21 +- .../browser/actions/layoutActions.ts | 7 +- src/vs/workbench/browser/contextkeys.ts | 12 +- .../parts/activitybar/activitybarPart.ts | 19 +- .../workbench/browser/parts/compositeBar.ts | 6 +- .../browser/parts/compositeBarActions.ts | 22 +- .../workbench/browser/parts/compositePart.ts | 9 +- .../parts/editor/editor.contribution.ts | 48 +- .../workbench/browser/parts/editor/editor.ts | 1 + .../browser/parts/editor/editorActions.ts | 20 +- .../browser/parts/editor/editorCommands.ts | 249 +-- .../browser/parts/editor/editorGroupView.ts | 24 +- .../parts/editor/media/tabstitlecontrol.css | 132 +- .../browser/parts/editor/sideBySideEditor.ts | 2 +- .../browser/parts/editor/tabsTitleControl.ts | 182 ++- .../browser/parts/editor/titleControl.ts | 8 +- .../browser/parts/panel/panelPart.ts | 8 +- .../browser/parts/statusbar/statusbarPart.ts | 7 +- .../browser/parts/titlebar/titlebarPart.ts | 4 +- .../browser/parts/views/viewPaneContainer.ts | 37 +- src/vs/workbench/browser/style.ts | 5 +- src/vs/workbench/browser/web.main.ts | 61 +- .../browser/workbench.contribution.ts | 21 +- src/vs/workbench/common/editor.ts | 28 +- src/vs/workbench/common/editor/editorGroup.ts | 6 - .../common/editor/textResourceEditorInput.ts | 13 +- src/vs/workbench/common/theme.ts | 6 + .../contrib/backup/common/backupRestorer.ts | 4 +- .../contrib/bulkEdit/browser/bulkCellEdits.ts | 6 +- .../browser/preview/bulkEdit.contribution.ts | 2 +- .../bulkEdit/browser/preview/bulkEditPane.ts | 6 +- .../callHierarchy/common/callHierarchy.ts | 4 +- .../inspectEditorTokens.ts | 5 +- .../comments/browser/commentThreadWidget.ts | 6 +- .../contrib/comments/browser/commentsView.ts | 2 +- .../contrib/comments/common/commentModel.ts | 10 +- .../configurationExportHelper.contribution.ts | 2 +- .../configurationExportHelper.ts | 2 +- .../browser/customEditor.contribution.ts | 2 +- .../customEditor/browser/customEditorInput.ts | 2 +- .../browser/customEditorInputFactory.ts | 4 +- .../contrib/debug/browser/baseDebugView.ts | 16 +- .../browser/breakpointEditorContribution.ts | 2 +- .../contrib/debug/browser/breakpointsView.ts | 14 +- .../contrib/debug/browser/callStackView.ts | 18 +- .../debug/browser/debugActionViewItems.ts | 18 +- .../contrib/debug/browser/debugActions.ts | 4 +- .../contrib/debug/browser/debugCommands.ts | 4 +- .../browser/debugConfigurationManager.ts | 44 +- .../debug/browser/debugEditorActions.ts | 4 + .../debug/browser/debugEditorContribution.ts | 3 +- .../contrib/debug/browser/debugQuickAccess.ts | 2 +- .../contrib/debug/browser/debugSession.ts | 30 +- .../contrib/debug/browser/debugToolBar.ts | 4 +- .../contrib/debug/browser/debugViewlet.ts | 5 +- .../browser/extensionHostDebugService.ts | 5 +- .../debug/browser/loadedScriptsView.ts | 7 +- .../contrib/debug/browser/rawDebugSession.ts | 86 +- .../workbench/contrib/debug/browser/repl.ts | 21 +- .../contrib/debug/browser/replFilter.ts | 5 +- .../contrib/debug/browser/replViewer.ts | 6 +- .../debug/browser/statusbarColorProvider.ts | 12 +- .../contrib/debug/browser/variablesView.ts | 4 +- .../debug/browser/watchExpressionsView.ts | 5 +- .../workbench/contrib/debug/common/debug.ts | 28 +- .../contrib/debug/common/debugModel.ts | 4 +- .../contrib/debug/common/debugger.ts | 3 +- .../contrib/debug/common/replModel.ts | 3 +- .../debugANSIHandling.test.ts | 97 +- .../experimentService.test.ts | 184 --- .../extensions/browser/extensionEditor.ts | 25 +- .../extensionRecommendationsService.ts | 3 +- .../extensions/browser/extensionsList.ts | 8 +- .../extensions/browser/extensionsViewer.ts | 2 +- .../extensions/browser/extensionsViewlet.ts | 14 +- .../extensions/browser/extensionsViews.ts | 40 +- .../extensions/browser/extensionsWidgets.ts | 10 +- .../browser/extensionsWorkbenchService.ts | 2 +- .../extensions/common/extensionsUtils.ts | 3 +- .../extensions.contribution.ts | 4 +- .../runtimeExtensionsEditor.ts | 25 +- .../extensionsActions.ts | 2 +- .../extensionRecommendationsService.test.ts | 8 +- .../extensionsActions.test.ts | 30 +- .../electron-browser/extensionsViews.test.ts | 19 +- .../extensionsWorkbenchService.test.ts | 20 +- .../contrib/files/browser/explorerViewlet.ts | 3 +- .../files/browser/fileActions.contribution.ts | 27 +- .../contrib/files/browser/fileCommands.ts | 13 +- .../files/browser/views/explorerView.ts | 23 +- .../files/browser/views/explorerViewer.ts | 20 +- .../files/browser/views/media/openeditors.css | 8 +- .../files/browser/views/openEditorsView.ts | 34 +- .../contrib/files/common/explorerModel.ts | 4 +- .../contrib/files/common/explorerService.ts | 1 + .../workbench/contrib/files/common/files.ts | 5 + .../format/browser/formatActionsMultiple.ts | 6 +- .../issue/electron-browser/issueService.ts | 2 +- .../browser/localizationsActions.ts | 3 +- .../logs.contribution.ts | 2 +- .../logsActions.ts | 5 +- .../markers/browser/markersTreeViewer.ts | 6 +- .../contrib/markers/browser/markersView.ts | 10 +- .../markers/browser/markersViewActions.ts | 10 +- .../notebook/browser/contrib/coreActions.ts | 185 ++- .../notebook/browser/contrib/scm/scm.ts | 6 +- .../notebook/browser/diff/cellComponents.ts | 57 +- .../browser/diff/notebookTextDiffEditor.ts | 75 +- .../browser/diff/notebookTextDiffList.ts | 4 +- .../notebook/browser/notebook.contribution.ts | 8 +- .../notebook/browser/notebookBrowser.ts | 5 +- .../browser/notebookDiffEditorInput.ts | 6 +- .../notebook/browser/notebookEditor.ts | 16 +- .../notebook/browser/notebookEditorInput.ts | 4 +- .../notebook/browser/notebookEditorWidget.ts | 19 +- .../notebook/browser/notebookServiceImpl.ts | 137 +- .../notebook/browser/view/notebookCellList.ts | 8 +- .../view/renderers/backLayerWebView.ts | 12 +- .../browser/view/renderers/cellRenderer.ts | 34 +- .../browser/view/renderers/cellWidgets.ts | 4 +- .../view/renderers/commonViewComponents.ts | 4 +- .../notebook/browser/view/renderers/dnd.ts | 4 +- .../browser/view/renderers/markdownCell.ts | 6 +- .../browser/view/renderers/webviewPreloads.ts | 2 +- .../notebook/browser/viewModel/cellEdit.ts | 19 +- .../browser/viewModel/notebookViewModel.ts | 152 +- .../contrib/notebook/common/model/cellEdit.ts | 47 +- .../common/model/notebookCellTextModel.ts | 4 +- .../common/model/notebookTextModel.ts | 810 +++++----- .../contrib/notebook/common/notebookCommon.ts | 124 +- .../notebook/common/notebookEditorModel.ts | 97 +- .../notebookEditorModelResolverService.ts | 45 +- .../notebook/common/notebookService.ts | 16 +- .../common/services/notebookSimpleWorker.ts | 64 +- .../services/notebookWorkerServiceImpl.ts | 50 +- .../notebook/test/notebookTextModel.test.ts | 123 +- .../notebook/test/notebookViewModel.test.ts | 31 +- .../notebook/test/testNotebookEditor.ts | 13 +- .../contrib/outline/browser/outlinePane.ts | 12 +- .../contrib/output/browser/outputView.ts | 5 +- .../output/common/outputLinkComputer.ts | 3 +- .../electron-browser/startupProfiler.ts | 4 +- .../electron-browser/startupTimings.ts | 2 +- .../preferences/browser/keybindingsEditor.ts | 24 +- .../browser/preferences.contribution.ts | 4 +- .../preferences/browser/preferencesEditor.ts | 9 +- .../browser/preferencesRenderers.ts | 3 +- .../preferences/browser/preferencesWidgets.ts | 26 +- .../preferences/browser/settingsEditor2.ts | 12 +- .../remote/browser/explorerViewItems.ts | 7 +- .../contrib/remote/browser/remote.ts | 7 +- .../contrib/remote/browser/remoteIndicator.ts | 9 +- .../electron-browser/remote.contribution.ts | 13 +- src/vs/workbench/contrib/sash/browser/sash.ts | 2 +- .../contrib/scm/browser/dirtydiffDecorator.ts | 6 +- .../contrib/scm/browser/media/scm.css | 11 + .../scm/browser/scmRepositoryRenderer.ts | 6 +- .../contrib/scm/browser/scmViewPane.ts | 81 +- .../scm/browser/scmViewPaneContainer.ts | 3 +- src/vs/workbench/contrib/scm/browser/util.ts | 4 +- .../search/browser/anythingQuickAccess.ts | 6 +- .../contrib/search/browser/searchView.ts | 4 +- .../contrib/search/common/searchModel.ts | 2 +- .../search/test/common/cacheState.test.ts | 2 +- .../browser/searchEditorActions.ts | 4 +- .../searchEditor/browser/searchEditorInput.ts | 8 +- .../contrib/snippets/browser/insertSnippet.ts | 2 +- .../contrib/snippets/browser/tabCompletion.ts | 8 +- .../partsSplash.contribution.ts | 2 +- .../electron-browser/workspaceTagsService.ts | 17 +- .../tasks/browser/abstractTaskService.ts | 2 +- .../tasks/browser/runAutomaticTasks.ts | 8 +- .../tasks/browser/terminalTaskSystem.ts | 4 +- .../contrib/tasks/common/problemCollectors.ts | 2 +- .../terminalExternalLinkProviderAdapter.ts | 2 +- .../terminal/browser/links/terminalLink.ts | 26 +- .../browser/links/terminalLinkManager.ts | 6 +- .../links/terminalProtocolLinkProvider.ts | 2 +- .../terminalValidatedLocalLinkProvider.ts | 6 +- .../browser/links/terminalWordLinkProvider.ts | 22 +- .../terminal/browser/terminalActions.ts | 2 +- .../terminal/browser/terminalConfigHelper.ts | 21 +- .../terminal/browser/terminalInstance.ts | 24 +- .../browser/terminalProcessExtHostProxy.ts | 6 +- .../terminal/browser/terminalService.ts | 10 +- .../browser/widgets/terminalHoverWidget.ts | 5 +- .../contrib/terminal/common/terminal.ts | 8 +- .../terminal/common/terminalConfiguration.ts | 40 +- .../terminalNativeContribution.ts | 6 +- .../contrib/terminal/node/terminalProcess.ts | 49 + ...terminalValidatedLocalLinkProvider.test.ts | 8 +- .../links/terminalWordLinkProvider.test.ts | 9 + .../test/common/terminalColorRegistry.test.ts | 11 +- .../themes/browser/themes.contribution.ts | 15 +- .../contrib/timeline/browser/timelinePane.ts | 5 +- .../update/browser/releaseNotesEditor.ts | 4 +- .../userDataSync/browser/userDataSync.ts | 2 +- .../contrib/views/browser/treeView.ts | 11 +- .../contrib/watermark/browser/watermark.ts | 1 - .../webview/browser/baseWebviewElement.ts | 15 +- .../browser/dynamicWebviewEditorOverlay.ts | 6 +- .../contrib/webview/browser/pre/host.js | 1 + .../contrib/webview/browser/pre/main.js | 9 +- .../contrib/webview/browser/themeing.ts | 7 +- .../webview/browser/webview.contribution.ts | 59 +- .../contrib/webview/browser/webview.ts | 6 +- .../browser/webview.web.contribution.ts | 10 + .../contrib/webview/browser/webviewElement.ts | 19 +- .../webview/browser/webviewIconManager.ts | 2 +- .../contrib/webview/browser/webviewService.ts | 38 +- .../electron-browser/iframeWebviewElement.ts | 6 +- .../electron-browser/webviewElement.ts | 6 +- .../electron-browser/webviewService.ts | 27 +- .../browser/webviewCommands.ts | 4 +- .../browser/webviewEditor.ts | 2 +- .../browser/webviewEditorInput.ts | 0 .../browser/webviewEditorInputFactory.ts | 0 .../browser/webviewPanel.contribution.ts | 36 + .../browser/webviewWorkbenchService.ts | 3 - .../webviewView/browser/webviewViewPane.ts | 49 +- .../webviewView/browser/webviewViewService.ts | 3 + .../welcome/page/browser/welcomePage.ts | 4 +- .../electron-browser/desktop.main.ts | 18 +- .../electron-sandbox/desktop.contribution.ts | 13 +- .../electron-sandbox/desktop.main.ts | 55 +- .../sandbox.simpleservices.ts | 193 ++- .../window.ts | 13 +- .../accessibilityService.ts | 7 +- .../browser/authenticationService.ts | 9 +- .../backup/common/backupFileService.ts | 4 +- .../backup/electron-browser/backup.ts | 12 - .../backupFileService.test.ts | 16 +- .../browser/configurationService.ts | 29 +- .../common/configurationEditingService.ts | 3 +- .../common/jsonEditingService.ts | 3 +- .../configurationCache.ts | 6 +- .../configurationEditingService.test.ts | 12 +- .../configurationService.test.ts | 50 +- .../configurationResolverService.ts | 3 +- .../configurationResolverService.test.ts | 4 +- .../credentials/browser/credentialsService.ts | 3 +- .../test/browser/decorationsService.test.ts | 4 +- .../browser/abstractFileDialogService.ts | 8 +- .../dialogs/browser/simpleFileDialog.ts | 11 +- .../dialogService.ts | 12 +- .../electron-sandbox/fileDialogService.ts | 7 +- .../services/editor/browser/editorService.ts | 41 +- .../services/editor/common/editorOpenWith.ts | 12 +- .../services/editor/common/editorService.ts | 4 +- .../editor/test/browser/editorService.test.ts | 5 + .../environment/browser/environmentService.ts | 40 +- .../environment/common/environmentService.ts | 28 +- .../electron-browser/environmentService.ts | 84 +- .../electron-sandbox/environmentService.ts | 29 + .../extensionManagementServerService.ts | 9 +- .../common/webExtensionsScannerService.ts | 2 +- .../extensionManagementServerService.ts | 5 +- .../extensionEnablementService.test.ts | 13 +- .../browser/webWorkerExtensionHost.ts | 56 +- .../extensions/common/webWorkerIframe.ts | 16 +- .../cachedExtensionScanner.ts | 2 +- .../electron-browser/extensionService.ts | 3 +- .../localProcessExtensionHost.ts | 4 +- .../extensions/worker/extensionHostWorker.ts | 13 +- .../worker/extensionHostWorkerMain.ts | 3 +- .../services/history/browser/history.ts | 6 +- .../host/browser/browserHostService.ts | 13 + .../workbench/services/host/browser/host.ts | 2 +- .../electron-sandbox/nativeHostService.ts | 14 + .../workbench/services/hover/browser/hover.ts | 7 +- .../services/hover/browser/hoverService.ts | 14 +- .../keybindingEditing.test.ts | 10 +- .../outputChannelModelService.ts | 3 +- .../services/path/browser/pathService.ts | 21 +- .../services/path/common/pathService.ts | 10 + .../pathService.ts | 7 +- .../preferences/browser/preferencesService.ts | 5 +- .../progress/browser/progressService.ts | 4 +- .../searchService.ts | 10 +- .../rawSearchService.test.ts | 4 +- .../electron-browser/sharedProcessService.ts | 2 +- .../electron-browser/telemetryService.ts | 2 +- .../browser/abstractTextMateService.ts | 2 +- .../textfile/browser/textFileService.ts | 7 +- .../electron-browser/nativeTextFileService.ts | 2 +- .../test/browser/textFileEditorModel.test.ts | 733 --------- .../textFileEditorModelManager.test.ts | 278 ---- .../test/browser/textFileService.test.ts | 168 -- .../browser/textModelResolverService.test.ts | 212 --- .../browser/browserHostColorSchemeService.ts | 54 + .../themes/browser/fileIconThemeData.ts | 2 +- .../themes/browser/workbenchThemeService.ts | 56 +- .../services/themes/common/colorThemeData.ts | 13 +- .../themes/common/hostColorSchemeService.ts | 19 + .../themes/common/themeConfiguration.ts | 21 +- .../themes/common/workbenchThemeService.ts | 2 - .../nativeHostColorSchemeService.ts | 45 + .../timer/electron-browser/timerService.ts | 2 +- .../services/userData/browser/userDataInit.ts | 4 + .../userData/common/fileUserDataProvider.ts | 18 +- .../fileUserDataProvider.test.ts | 232 ++- .../views/common/viewContainerModel.ts | 8 +- .../test/browser/viewContainerModel.test.ts | 467 ------ .../abstractWorkspaceEditingService.ts | 2 +- .../workspaceEditingService.ts | 4 +- .../browser/api/extHostApiCommands.test.ts | 23 + ...itors.test.ts => extHostBulkEdits.test.ts} | 24 +- .../browser/api/extHostConfiguration.test.ts | 11 +- .../extHostDocumentSaveParticipant.test.ts | 32 +- .../api/extHostLanguageFeatures.test.ts | 8 +- .../test/browser/api/extHostNotebook.test.ts | 101 +- .../api/extHostNotebookConcatDocument.test.ts | 404 ++--- .../browser/api/extHostTextEditor.test.ts | 1 - .../test/browser/api/extHostTreeViews.test.ts | 4 +- ...mainThreadDocumentContentProviders.test.ts | 2 +- .../api/mainThreadDocumentsAndEditors.test.ts | 5 +- .../test/browser/quickAccess.test.ts | 314 ---- .../test/browser/workbenchTestServices.ts | 9 +- .../textsearch.perf.integrationTest.ts | 2 +- .../electron-browser/workbenchTestServices.ts | 13 +- src/vs/workbench/workbench.common.main.ts | 1 + src/vs/workbench/workbench.desktop.main.ts | 10 +- src/vs/workbench/workbench.sandbox.main.ts | 9 + src/vs/workbench/workbench.web.api.ts | 9 +- src/vs/workbench/workbench.web.main.ts | 4 +- .../smoke/src/areas/notebook/notebook.test.ts | 2 +- test/unit/electron/index.js | 1 + yarn.lock | 107 +- 687 files changed, 10507 insertions(+), 9104 deletions(-) create mode 100644 .github/subscribers.json create mode 100644 .github/workflows/deep-classifier-runner.yml create mode 100644 .github/workflows/deep-classifier-scraper.yml create mode 100644 .github/workflows/latest-release-monitor.yml create mode 100644 .vscode/searches/TrustedTypes.code-search create mode 100644 extensions/vscode-notebook-tests/src/notebook.test.ts create mode 100644 resources/linux/code-workspace.xml create mode 100644 src/typings/trustedTypes.d.ts delete mode 100644 src/vs/code/node/paths.ts delete mode 100644 src/vs/platform/dialogs/electron-browser/dialogIpc.ts create mode 100644 src/vs/platform/environment/common/argv.ts create mode 100644 src/vs/platform/theme/common/theme.ts create mode 100644 src/vs/workbench/api/browser/mainThreadBulkEdits.ts create mode 100644 src/vs/workbench/api/common/extHostBulkEdits.ts create mode 100644 src/vs/workbench/api/common/extHostFileSystemInfo.ts create mode 100644 src/vs/workbench/api/common/extHostNotebookDocument.ts create mode 100644 src/vs/workbench/api/common/extHostNotebookEditor.ts rename src/vs/workbench/contrib/extensions/{electron-browser => electron-sandbox}/extensionsActions.ts (96%) rename src/vs/workbench/contrib/logs/{electron-browser => electron-sandbox}/logs.contribution.ts (95%) rename src/vs/workbench/contrib/logs/{electron-browser => electron-sandbox}/logsActions.ts (90%) create mode 100644 src/vs/workbench/contrib/webview/browser/webview.web.contribution.ts rename src/vs/workbench/contrib/{webview => webviewPanel}/browser/webviewCommands.ts (96%) rename src/vs/workbench/contrib/{webview => webviewPanel}/browser/webviewEditor.ts (98%) rename src/vs/workbench/contrib/{webview => webviewPanel}/browser/webviewEditorInput.ts (100%) rename src/vs/workbench/contrib/{webview => webviewPanel}/browser/webviewEditorInputFactory.ts (100%) create mode 100644 src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts rename src/vs/workbench/contrib/{webview => webviewPanel}/browser/webviewWorkbenchService.ts (98%) rename src/vs/workbench/{electron-browser => electron-sandbox}/window.ts (98%) rename src/vs/workbench/services/accessibility/{electron-browser => electron-sandbox}/accessibilityService.ts (93%) delete mode 100644 src/vs/workbench/services/backup/electron-browser/backup.ts rename src/vs/workbench/services/configuration/{node => electron-browser}/configurationCache.ts (90%) rename src/vs/workbench/services/configurationResolver/{electron-browser => electron-sandbox}/configurationResolverService.ts (94%) rename src/vs/workbench/services/dialogs/{electron-browser => electron-sandbox}/dialogService.ts (93%) create mode 100644 src/vs/workbench/services/environment/electron-sandbox/environmentService.ts rename src/vs/workbench/services/path/{electron-browser => electron-sandbox}/pathService.ts (75%) rename src/vs/workbench/services/search/{node => electron-browser}/searchService.ts (92%) rename src/vs/workbench/services/search/test/{node => electron-browser}/rawSearchService.test.ts (98%) delete mode 100644 src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts delete mode 100644 src/vs/workbench/services/textfile/test/browser/textFileEditorModelManager.test.ts delete mode 100644 src/vs/workbench/services/textfile/test/browser/textFileService.test.ts delete mode 100644 src/vs/workbench/services/textmodelResolver/test/browser/textModelResolverService.test.ts create mode 100644 src/vs/workbench/services/themes/browser/browserHostColorSchemeService.ts create mode 100644 src/vs/workbench/services/themes/common/hostColorSchemeService.ts create mode 100644 src/vs/workbench/services/themes/electron-sandbox/nativeHostColorSchemeService.ts delete mode 100644 src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts rename src/vs/workbench/services/workspaces/{electron-browser => electron-sandbox}/workspaceEditingService.ts (96%) rename src/vs/workbench/test/browser/api/{extHostTextEditors.test.ts => extHostBulkEdits.test.ts} (67%) delete mode 100644 src/vs/workbench/test/browser/quickAccess.test.ts diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 23c94155f3..504e4cebfe 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -73,6 +73,7 @@ RUN apt-get update \ libnss3 \ libxss1 \ libasound2 \ + libgbm1 \ xfonts-base \ xfonts-terminus \ fonts-noto \ diff --git a/.github/subscribers.json b/.github/subscribers.json new file mode 100644 index 0000000000..89dee80d4a --- /dev/null +++ b/.github/subscribers.json @@ -0,0 +1,7 @@ +{ + "label-to-subscribe-to": [ + "list of usernames to subscribe", + "such as:", + "JacksonKearl" + ] +} diff --git a/.github/workflows/deep-classifier-runner.yml b/.github/workflows/deep-classifier-runner.yml new file mode 100644 index 0000000000..82ae49039f --- /dev/null +++ b/.github/workflows/deep-classifier-runner.yml @@ -0,0 +1,50 @@ +name: "Deep Classifier: Runner" +on: + schedule: + - cron: 0 * * * * + repository_dispatch: + types: [trigger-deep-classifier-runner] + +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Checkout Actions + uses: actions/checkout@v2 + with: + repository: 'microsoft/vscode-github-triage-actions' + ref: v35 + path: ./actions + - name: Install Actions + run: npm install --production --prefix ./actions + - name: Install Additional Dependencies + # Pulls in a bunch of other packages that arent needed for the rest of the actions + run: npm install @azure/storage-blob@12.1.1 + - name: "Run Classifier: Scraper" + uses: ./actions/classifier-deep/apply/fetch-sources + with: + # slightly overlapping to protect against issues slipping through the cracks if a run is delayed + from: 80 + until: 5 + configPath: classifier + blobContainerName: vscode-issue-classifier + blobStorageKey: ${{secrets.AZURE_BLOB_STORAGE_CONNECTION_STRING}} + token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} + appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}} + - name: Set up Python 3.7 + uses: actions/setup-python@v1 + with: + python-version: 3.7 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install --upgrade numpy scipy scikit-learn joblib nltk simpletransformers torch torchvision + - name: "Run Classifier: Generator" + run: python ./actions/classifier-deep/apply/generate-labels/main.py + - name: "Run Classifier: Labeler" + uses: ./actions/classifier-deep/apply/apply-labels + with: + configPath: classifier + allowLabels: "needs more info|new release" + appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}} + token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} diff --git a/.github/workflows/deep-classifier-scraper.yml b/.github/workflows/deep-classifier-scraper.yml new file mode 100644 index 0000000000..9fe52cd347 --- /dev/null +++ b/.github/workflows/deep-classifier-scraper.yml @@ -0,0 +1,27 @@ +name: "Deep Classifier: Scraper" +on: + repository_dispatch: + types: [trigger-deep-classifier-scraper] + +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Checkout Actions + uses: actions/checkout@v2 + with: + repository: 'microsoft/vscode-github-triage-actions' + ref: v35 + path: ./actions + - name: Install Actions + run: npm install --production --prefix ./actions + - name: Install Additional Dependencies + # Pulls in a bunch of other packages that arent needed for the rest of the actions + run: npm install @azure/storage-blob@12.1.1 + - name: "Run Classifier: Scraper" + uses: ./actions/classifier-deep/train/fetch-issues + with: + blobContainerName: vscode-issue-classifier + blobStorageKey: ${{secrets.AZURE_BLOB_STORAGE_CONNECTION_STRING}} + token: ${{secrets.ISSUE_SCRAPER_TOKEN}} + appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}} diff --git a/.github/workflows/latest-release-monitor.yml b/.github/workflows/latest-release-monitor.yml new file mode 100644 index 0000000000..5585505438 --- /dev/null +++ b/.github/workflows/latest-release-monitor.yml @@ -0,0 +1,27 @@ +name: Latest Release Monitor +on: + schedule: + - cron: 0/5 * * * * + repository_dispatch: + types: [trigger-latest-release-monitor] + +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Checkout Actions + uses: actions/checkout@v2 + with: + repository: 'microsoft/vscode-github-triage-actions' + path: ./actions + ref: v35 + - name: Install Actions + run: npm install --production --prefix ./actions + - name: Install Storage Module + run: npm install @azure/storage-blob@12.1.1 + - name: Run Latest Release Monitor + uses: ./actions/latest-release-monitor + with: + storageKey: ${{secrets.AZURE_BLOB_STORAGE_CONNECTION_STRING}} + appInsightsKey: ${{secrets.TRIAGE_ACTIONS_APP_INSIGHTS}} + token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} diff --git a/.vscode/notebooks/api.github-issues b/.vscode/notebooks/api.github-issues index ad2d028465..2eb4b6432b 100644 --- a/.vscode/notebooks/api.github-issues +++ b/.vscode/notebooks/api.github-issues @@ -8,7 +8,7 @@ { "kind": 2, "language": "github-issues", - "value": "$repo=repo:microsoft/vscode\n$milestone=milestone:\"August 2020\"", + "value": "$repo=repo:microsoft/vscode\n$milestone=milestone:\"September 2020\"", "editable": true }, { diff --git a/.vscode/notebooks/my-work.github-issues b/.vscode/notebooks/my-work.github-issues index d1452276f6..a5de65e0bd 100644 --- a/.vscode/notebooks/my-work.github-issues +++ b/.vscode/notebooks/my-work.github-issues @@ -8,7 +8,7 @@ { "kind": 2, "language": "github-issues", - "value": "// list of repos we work in\n$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks\n\n// current milestone name\n$milestone=milestone:\"August 2020\"", + "value": "// list of repos we work in\n$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks\n\n// current milestone name\n$milestone=milestone:\"September 2020\"", "editable": true }, { diff --git a/.vscode/notebooks/verification.github-issues b/.vscode/notebooks/verification.github-issues index 79f7f8a5c0..4e65452ea5 100644 --- a/.vscode/notebooks/verification.github-issues +++ b/.vscode/notebooks/verification.github-issues @@ -14,7 +14,7 @@ { "kind": 2, "language": "github-issues", - "value": "$repos=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks \n$milestone=milestone:\"July 2020\"", + "value": "$repos=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks \n$milestone=milestone:\"September 2020\"", "editable": true }, { diff --git a/.vscode/searches/TrustedTypes.code-search b/.vscode/searches/TrustedTypes.code-search new file mode 100644 index 0000000000..4b61b3e058 --- /dev/null +++ b/.vscode/searches/TrustedTypes.code-search @@ -0,0 +1,194 @@ +# Query: .innerHTML = +# Flags: CaseSensitive WordMatch +# Including: src/vs/**/*.{t,j}s +# Excluding: *.test.ts +# ContextLines: 3 + +22 results - 14 files + +src/vs/base/browser/markdownRenderer.ts: + 161 const strValue = values[0]; + 162 const span = element.querySelector(`div[data-code="${id}"]`); + 163 if (span) { + 164: span.innerHTML = strValue; + 165 } + 166 }).catch(err => { + 167 // ignore + + 243 return true; + 244 } + 245 + 246: element.innerHTML = insane(renderedMarkdown, { + 247 allowedSchemes, + 248 // allowedTags should included everything that markdown renders to. + 249 // Since we have our own sanitize function for marked, it's possible we missed some tag so let insane make sure. + +src/vs/base/browser/ui/contextview/contextview.ts: + 157 this.shadowRootHostElement = DOM.$('.shadow-root-host'); + 158 this.container.appendChild(this.shadowRootHostElement); + 159 this.shadowRoot = this.shadowRootHostElement.attachShadow({ mode: 'open' }); + 160: this.shadowRoot.innerHTML = ` + 161 + +src/vs/code/electron-sandbox/issue/issueReporterMain.ts: + 57 const platformClass = platform.isWindows ? 'windows' : platform.isLinux ? 'linux' : 'mac'; + 58 addClass(document.body, platformClass); // used by our fonts + 59 + 60: document.body.innerHTML = BaseHtml(); + 61 const issueReporter = new IssueReporter(configuration); + 62 issueReporter.render(); + 63 document.body.style.display = 'block'; + +src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts: + 320 content.push(`.highest { color: ${styles.highlightForeground}; }`); + 321 } + 322 + 323: styleTag.innerHTML = content.join('\n'); + 324 if (document.head) { + 325 document.head.appendChild(styleTag); + 326 } + +src/vs/editor/browser/view/domLineBreaksComputer.ts: + 107 allCharOffsets[i] = tmp[0]; + 108 allVisibleColumns[i] = tmp[1]; + 109 } + 110: containerDomNode.innerHTML = sb.build(); + 111 + 112 containerDomNode.style.position = 'absolute'; + 113 containerDomNode.style.top = '10000'; + +src/vs/editor/browser/view/viewLayer.ts: + 507 private _finishRenderingNewLines(ctx: IRendererContext, domNodeIsEmpty: boolean, newLinesHTML: string, wasNew: boolean[]): void { + 508 const lastChild = this.domNode.lastChild; + 509 if (domNodeIsEmpty || !lastChild) { + 510: this.domNode.innerHTML = newLinesHTML; + 511 } else { + 512 lastChild.insertAdjacentHTML('afterend', newLinesHTML); + 513 } + + 525 private _finishRenderingInvalidLines(ctx: IRendererContext, invalidLinesHTML: string, wasInvalid: boolean[]): void { + 526 const hugeDomNode = document.createElement('div'); + 527 + 528: hugeDomNode.innerHTML = invalidLinesHTML; + 529 + 530 for (let i = 0; i < ctx.linesLength; i++) { + 531 const line = ctx.lines[i]; + +src/vs/editor/browser/widget/diffEditorWidget.ts: + 2157 + 2158 let domNode = document.createElement('div'); + 2159 domNode.className = `view-lines line-delete ${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`; + 2160: domNode.innerHTML = sb.build(); + 2161 Configuration.applyFontInfoSlow(domNode, fontInfo); + 2162 + 2163 let marginDomNode = document.createElement('div'); + 2164 marginDomNode.className = 'inline-deleted-margin-view-zone'; + 2165: marginDomNode.innerHTML = marginHTML.join(''); + 2166 Configuration.applyFontInfoSlow(marginDomNode, fontInfo); + 2167 + 2168 return { + +src/vs/editor/standalone/browser/colorizer.ts: + 40 let text = domNode.firstChild ? domNode.firstChild.nodeValue : ''; + 41 domNode.className += ' ' + theme; + 42 let render = (str: string) => { + 43: domNode.innerHTML = str; + 44 }; + 45 return this.colorize(modeService, text || '', mimeType, options).then(render, (err) => console.error(err)); + 46 } + +src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts: + 212 if (!this._globalStyleElement) { + 213 this._globalStyleElement = dom.createStyleSheet(); + 214 this._globalStyleElement.className = 'monaco-colors'; + 215: this._globalStyleElement.innerHTML = this._css; + 216 this._styleElements.push(this._globalStyleElement); + 217 } + 218 return Disposable.None; + + 221 private _registerShadowDomContainer(domNode: HTMLElement): IDisposable { + 222 const styleElement = dom.createStyleSheet(domNode); + 223 styleElement.className = 'monaco-colors'; + 224: styleElement.innerHTML = this._css; + 225 this._styleElements.push(styleElement); + 226 return { + 227 dispose: () => { + + 291 ruleCollector.addRule(generateTokensCSSForColorMap(colorMap)); + 292 + 293 this._css = cssRules.join('\n'); + 294: this._styleElements.forEach(styleElement => styleElement.innerHTML = this._css); + 295 + 296 TokenizationRegistry.setColorMap(colorMap); + 297 this._onColorThemeChange.fire(theme); + +src/vs/editor/test/browser/controller/imeTester.ts: + 55 let content = this._model.getModelLineContent(i); + 56 r += content + '
'; + 57 } + 58: output.innerHTML = r; + 59 } + 60 } + 61 + + 69 let title = document.createElement('div'); + 70 title.className = 'title'; + 71 + 72: title.innerHTML = description + '. Type ' + inputStr + ''; + 73 container.appendChild(title); + 74 + 75 let startBtn = document.createElement('button'); + +src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts: + 454 + 455 private getMarkdownDragImage(templateData: MarkdownCellRenderTemplate): HTMLElement { + 456 const dragImageContainer = DOM.$('.cell-drag-image.monaco-list-row.focused.markdown-cell-row'); + 457: dragImageContainer.innerHTML = templateData.container.outerHTML; + 458 + 459 // Remove all rendered content nodes after the + 460 const markdownContent = dragImageContainer.querySelector('.cell.markdown')!; + + 611 return null; + 612 } + 613 + 614: editorContainer.innerHTML = richEditorText; + 615 + 616 return dragImageContainer; + 617 } + +src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts: + 375 addMouseoverListeners(outputNode, outputId); + 376 const content = data.content; + 377 if (content.type === RenderOutputType.Html) { + 378: outputNode.innerHTML = content.htmlContent; + 379 cellOutputContainer.appendChild(outputNode); + 380 domEval(outputNode); + 381 } else { + +src/vs/workbench/contrib/webview/browser/pre/main.js: + 386 // apply default styles + 387 const defaultStyles = newDocument.createElement('style'); + 388 defaultStyles.id = '_defaultStyles'; + 389: defaultStyles.innerHTML = defaultCssRules; + 390 newDocument.head.prepend(defaultStyles); + 391 + 392 applyStyles(newDocument, newDocument.body); + +src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts: + 281 + 282 const content = model.main.textEditorModel.getValue(EndOfLinePreference.LF); + 283 if (!strings.endsWith(input.resource.path, '.md')) { + 284: this.content.innerHTML = content; + 285 this.updateSizeClasses(); + 286 this.decorateContent(); + 287 this.contentDisposables.push(this.keybindingService.onDidUpdateKeybindings(() => this.decorateContent())); + + 303 const innerContent = document.createElement('div'); + 304 innerContent.classList.add('walkThroughContent'); // only for markdown files + 305 const markdown = this.expandMacros(content); + 306: innerContent.innerHTML = marked(markdown, { renderer }); + 307 this.content.appendChild(innerContent); + 308 + 309 model.snippets.forEach((snippet, i) => { diff --git a/.vscode/searches/es6.code-search b/.vscode/searches/es6.code-search index 6ab0d14c5a..1e1bdd9418 100644 --- a/.vscode/searches/es6.code-search +++ b/.vscode/searches/es6.code-search @@ -2,43 +2,31 @@ # Flags: CaseSensitive WordMatch # ContextLines: 2 -14 results - 4 files +12 results - 4 files src/vs/base/browser/dom.ts: - 81 }; - 82 - 83: /** @deprecated ES6 - use classList*/ - 84 export const hasClass: (node: HTMLElement | SVGElement, className: string) => boolean = _classList.hasClass.bind(_classList); + 83 }; + 84 85: /** @deprecated ES6 - use classList*/ - 86 export const addClass: (node: HTMLElement | SVGElement, className: string) => void = _classList.addClass.bind(_classList); + 86 export const hasClass: (node: HTMLElement | SVGElement, className: string) => boolean = _classList.hasClass.bind(_classList); 87: /** @deprecated ES6 - use classList*/ - 88 export const addClasses: (node: HTMLElement | SVGElement, ...classNames: string[]) => void = _classList.addClasses.bind(_classList); + 88 export const addClass: (node: HTMLElement | SVGElement, className: string) => void = _classList.addClass.bind(_classList); 89: /** @deprecated ES6 - use classList*/ - 90 export const removeClass: (node: HTMLElement | SVGElement, className: string) => void = _classList.removeClass.bind(_classList); + 90 export const addClasses: (node: HTMLElement | SVGElement, ...classNames: string[]) => void = _classList.addClasses.bind(_classList); 91: /** @deprecated ES6 - use classList*/ - 92 export const removeClasses: (node: HTMLElement | SVGElement, ...classNames: string[]) => void = _classList.removeClasses.bind(_classList); + 92 export const removeClass: (node: HTMLElement | SVGElement, className: string) => void = _classList.removeClass.bind(_classList); 93: /** @deprecated ES6 - use classList*/ - 94 export const toggleClass: (node: HTMLElement | SVGElement, className: string, shouldHaveIt?: boolean) => void = _classList.toggleClass.bind(_classList); - 95 + 94 export const removeClasses: (node: HTMLElement | SVGElement, ...classNames: string[]) => void = _classList.removeClasses.bind(_classList); + 95: /** @deprecated ES6 - use classList*/ + 96 export const toggleClass: (node: HTMLElement | SVGElement, className: string, shouldHaveIt?: boolean) => void = _classList.toggleClass.bind(_classList); + 97 src/vs/base/common/arrays.ts: 401 402 /** - 403: * @deprecated ES6: use `Array.findIndex` + 403: * @deprecated ES6: use `Array.find` 404 */ - 405 export function firstIndex(array: ReadonlyArray, fn: (item: T) => boolean): number { - - 417 - 418 /** - 419: * @deprecated ES6: use `Array.find` - 420 */ - 421 export function first(array: ReadonlyArray, fn: (item: T) => boolean, notFoundValue: T): T; - - 568 - 569 /** - 570: * @deprecated ES6: use `Array.find` - 571 */ - 572 export function find(arr: ArrayLike, predicate: (value: T, index: number, arr: ArrayLike) => any): T | undefined { + 405 export function first(array: ReadonlyArray, fn: (item: T) => boolean, notFoundValue: T): T; src/vs/base/common/objects.ts: 115 @@ -66,8 +54,8 @@ src/vs/base/common/strings.ts: 170 */ 171 export function endsWith(haystack: string, needle: string): boolean { - 861 - 862 /** - 863: * @deprecated ES6 - 864 */ - 865 export function repeat(s: string, count: number): string { + 857 + 858 /** + 859: * @deprecated ES6 + 860 */ + 861 export function repeat(s: string, count: number): string { diff --git a/.vscode/searches/ts36031.code-search b/.vscode/searches/ts36031.code-search index 8a74db2a9a..51071b8840 100644 --- a/.vscode/searches/ts36031.code-search +++ b/.vscode/searches/ts36031.code-search @@ -2,18 +2,52 @@ # Flags: RegExp # ContextLines: 2 -2 results - 2 files +8 results - 4 files src/vs/base/browser/ui/tree/asyncDataTree.ts: - 243 } : () => 'treeitem', - 244 isChecked: options.accessibilityProvider!.isChecked ? (e) => { - 245: return !!(options.accessibilityProvider?.isChecked!(e.element as T)); - 246 } : undefined, - 247 getAriaLabel(e) { + 241 } : () => 'treeitem', + 242 isChecked: options.accessibilityProvider!.isChecked ? (e) => { + 243: return !!(options.accessibilityProvider?.isChecked!(e.element as T)); + 244 } : undefined, + 245 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)!, +src/vs/platform/list/browser/listService.ts: + 463 + 464 if (typeof options?.openOnSingleClick !== 'boolean' && options?.configurationService) { + 465: this.openOnSingleClick = options?.configurationService!.getValue(openModeSettingKey) !== 'doubleClick'; + 466 this._register(options?.configurationService.onDidChangeConfiguration(() => { + 467: this.openOnSingleClick = options?.configurationService!.getValue(openModeSettingKey) !== 'doubleClick'; + 468 })); + 469 } else { + +src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts: + 1526 + 1527 await this._ensureActiveKernel(); + 1528: await this._activeKernel?.cancelNotebookCell!(this._notebookViewModel!.uri, undefined); + 1529 } + 1530 + + 1535 + 1536 await this._ensureActiveKernel(); + 1537: await this._activeKernel?.executeNotebookCell!(this._notebookViewModel!.uri, undefined); + 1538 } + 1539 + + 1553 + 1554 await this._ensureActiveKernel(); + 1555: await this._activeKernel?.cancelNotebookCell!(this._notebookViewModel!.uri, cell.handle); + 1556 } + 1557 + + 1567 + 1568 await this._ensureActiveKernel(); + 1569: await this._activeKernel?.executeNotebookCell!(this._notebookViewModel!.uri, cell.handle); + 1570 } + 1571 + +src/vs/workbench/contrib/webview/electron-browser/iframeWebviewElement.ts: + 89 .then(() => this._resourceRequestManager.ensureReady()) + 90 .then(() => { + 91: this.element?.contentWindow!.postMessage({ channel, args: data }, '*'); + 92 }); + 93 } diff --git a/.yarnrc b/.yarnrc index 3c6eccfb10..fbf612d098 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,3 +1,3 @@ disturl "https://atom.io/download/electron" -target "9.2.1" +target "9.3.0" runtime "electron" diff --git a/build/azure-pipelines/common/createAsset.ts b/build/azure-pipelines/common/createAsset.ts index 12eadf7de0..f06c6bbcbf 100644 --- a/build/azure-pipelines/common/createAsset.ts +++ b/build/azure-pipelines/common/createAsset.ts @@ -53,7 +53,7 @@ async function uploadBlob(blobService: azure.BlobService, quality: string, blobN } }; - await new Promise((c, e) => blobService.createBlockBlobFromLocalFile(quality, blobName, filePath, blobOptions, err => err ? e(err) : c())); + await new Promise((c, e) => blobService.createBlockBlobFromLocalFile(quality, blobName, filePath, blobOptions, err => err ? e(err) : c())); } function getEnv(name: string): string { diff --git a/build/azure-pipelines/common/publish-webview.ts b/build/azure-pipelines/common/publish-webview.ts index 852c12f98a..b90909996a 100644 --- a/build/azure-pipelines/common/publish-webview.ts +++ b/build/azure-pipelines/common/publish-webview.ts @@ -17,7 +17,7 @@ const fileNames = [ ]; async function assertContainer(blobService: azure.BlobService, container: string): Promise { - await new Promise((c, e) => blobService.createContainerIfNotExists(container, { publicAccessLevel: 'blob' }, err => err ? e(err) : c())); + await new Promise((c, e) => blobService.createContainerIfNotExists(container, { publicAccessLevel: 'blob' }, err => err ? e(err) : c())); } async function doesBlobExist(blobService: azure.BlobService, container: string, blobName: string): Promise { @@ -33,7 +33,7 @@ async function uploadBlob(blobService: azure.BlobService, container: string, blo } }; - await new Promise((c, e) => blobService.createBlockBlobFromLocalFile(container, blobName, file, blobOptions, err => err ? e(err) : c())); + await new Promise((c, e) => blobService.createBlockBlobFromLocalFile(container, blobName, file, blobOptions, err => err ? e(err) : c())); } async function publish(commit: string, files: readonly string[]): Promise { diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index 3b186bb113..d5e684890e 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -87,10 +87,6 @@ steps: set -e VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ yarn gulp vscode-darwin-min-ci - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn gulp vscode-reh-darwin-min-ci - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn gulp vscode-reh-web-darwin-min-ci displayName: Build - script: | diff --git a/build/azure-pipelines/darwin/sql-product-build-darwin.yml b/build/azure-pipelines/darwin/sql-product-build-darwin.yml index baa35049bc..9011050df8 100644 --- a/build/azure-pipelines/darwin/sql-product-build-darwin.yml +++ b/build/azure-pipelines/darwin/sql-product-build-darwin.yml @@ -96,8 +96,6 @@ steps: set -e yarn gulp package-rebuild-extensions yarn gulp vscode-darwin-min-ci - yarn gulp vscode-reh-darwin-min-ci - yarn gulp vscode-reh-web-darwin-min-ci displayName: Build env: VSCODE_MIXIN_PASSWORD: $(github-distro-mixin-password) diff --git a/build/azure-pipelines/exploration-build.yml b/build/azure-pipelines/exploration-build.yml index 0b825b7438..8a61ca4a5e 100644 --- a/build/azure-pipelines/exploration-build.yml +++ b/build/azure-pipelines/exploration-build.yml @@ -31,10 +31,10 @@ steps: git config user.email "vscode@microsoft.com" git config user.name "VSCode" - git checkout origin/electron-x.y.z + git checkout origin/electron-11.x.y git merge origin/master # Push master branch into exploration branch - git push origin HEAD:electron-x.y.z + git push origin HEAD:electron-11.x.y displayName: Sync & Merge Exploration diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 21d963042c..270beb898a 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -52,21 +52,25 @@ steps: git merge $(node -p "require('./package.json').distro") displayName: Merge distro +- script: | + echo -n $VSCODE_ARCH > .build/arch + displayName: Prepare arch cache flag + - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 inputs: - keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' + keyfile: '.build/arch, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' targetfolder: '**/node_modules, !**/node_modules/**/node_modules' vstsFeed: 'npm-vscode' - script: | set -e - CHILD_CONCURRENCY=1 yarn --frozen-lockfile + CHILD_CONCURRENCY=1 npm_config_arch=$(NPM_ARCH) yarn --frozen-lockfile displayName: Install dependencies condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 inputs: - keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' + keyfile: '.build/arch, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' targetfolder: '**/node_modules, !**/node_modules/**/node_modules' vstsFeed: 'npm-vscode' condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) @@ -85,64 +89,64 @@ steps: - script: | set -e VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn gulp vscode-linux-x64-min-ci + yarn gulp vscode-linux-$(VSCODE_ARCH)-min-ci VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn gulp vscode-reh-linux-x64-min-ci + yarn gulp vscode-reh-linux-$(VSCODE_ARCH)-min-ci VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn gulp vscode-reh-web-linux-x64-min-ci + yarn gulp vscode-reh-web-linux-$(VSCODE_ARCH)-min-ci displayName: Build - script: | set -e service xvfb start displayName: Start xvfb - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - script: | set -e DISPLAY=:10 ./scripts/test.sh --build --tfs "Unit Tests" displayName: Run unit tests (Electron) - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - script: | set -e DISPLAY=:10 yarn test-browser --build --browser chromium --tfs "Browser Unit Tests" displayName: Run unit tests (Browser) - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - script: | # Figure out the full absolute path of the product we just built # including the remote server and configure the integration tests # to run with these builds instead of running out of sources. set -e - APP_ROOT=$(agent.builddirectory)/VSCode-linux-x64 + APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName") INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \ - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-x64" \ + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \ DISPLAY=:10 ./scripts/test-integration.sh --build --tfs "Integration Tests" displayName: Run integration tests (Electron) - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - script: | set -e - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-linux-x64" \ + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-linux-$(VSCODE_ARCH)" \ DISPLAY=:10 ./resources/server/test/test-web-integration.sh --browser chromium displayName: Run integration tests (Browser) - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - script: | set -e - APP_ROOT=$(agent.builddirectory)/VSCode-linux-x64 + APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName") INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \ - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-x64" \ + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \ DISPLAY=:10 ./resources/server/test/test-remote-integration.sh displayName: Run remote integration tests (Electron) - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - task: PublishPipelineArtifact@0 inputs: - artifactName: crash-dump-linux + artifactName: 'crash-dump-linux-$(VSCODE_ARCH)' targetPath: .build/crashes displayName: 'Publish Crash Reports' continueOnError: true @@ -157,15 +161,26 @@ steps: - script: | set -e - yarn gulp "vscode-linux-x64-build-deb" - yarn gulp "vscode-linux-x64-build-rpm" - yarn gulp "vscode-linux-x64-prepare-snap" - displayName: Build packages + yarn gulp "vscode-linux-$(VSCODE_ARCH)-build-deb" + yarn gulp "vscode-linux-$(VSCODE_ARCH)-build-rpm" + displayName: Build deb, rpm packages + +- script: | + set -e + yarn gulp "vscode-linux-$(VSCODE_ARCH)-prepare-snap" + displayName: Prepare snap package + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64')) + +# needed for code signing +- task: UseDotNet@2 + displayName: 'Install .NET Core SDK 2.x' + inputs: + version: 2.x - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 inputs: ConnectedServiceName: 'ESRP CodeSign' - FolderPath: '.build/linux/rpm/x86_64' + FolderPath: '.build/linux/rpm' Pattern: '*.rpm' signConfigType: inlineSignParams inlineOperation: | @@ -186,14 +201,16 @@ steps: AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ AZURE_STORAGE_ACCESS_KEY_2="$(vscode-storage-key)" \ VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + VSCODE_ARCH="$(VSCODE_ARCH)" \ ./build/azure-pipelines/linux/publish.sh displayName: Publish - task: PublishPipelineArtifact@0 displayName: 'Publish Pipeline Artifact' inputs: - artifactName: snap-x64 + artifactName: 'snap-$(VSCODE_ARCH)' targetPath: .build/linux/snap-tarball + condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64')) - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 displayName: 'Component Detection' diff --git a/build/azure-pipelines/linux/publish.sh b/build/azure-pipelines/linux/publish.sh index 7e360be6cb..72fe2ad7b3 100755 --- a/build/azure-pipelines/linux/publish.sh +++ b/build/azure-pipelines/linux/publish.sh @@ -4,11 +4,10 @@ REPO="$(pwd)" ROOT="$REPO/.." # Publish tarball -PLATFORM_LINUX="linux-x64" +PLATFORM_LINUX="linux-$VSCODE_ARCH" BUILDNAME="VSCode-$PLATFORM_LINUX" -BUILD="$ROOT/$BUILDNAME" BUILD_VERSION="$(date +%s)" -[ -z "$VSCODE_QUALITY" ] && TARBALL_FILENAME="code-$BUILD_VERSION.tar.gz" || TARBALL_FILENAME="code-$VSCODE_QUALITY-$BUILD_VERSION.tar.gz" +[ -z "$VSCODE_QUALITY" ] && TARBALL_FILENAME="code-$VSCODE_ARCH-$BUILD_VERSION.tar.gz" || TARBALL_FILENAME="code-$VSCODE_QUALITY-$VSCODE_ARCH-$BUILD_VERSION.tar.gz" TARBALL_PATH="$ROOT/$TARBALL_FILENAME" rm -rf $ROOT/code-*.tar.* @@ -28,24 +27,36 @@ rm -rf $ROOT/vscode-server-*.tar.* node build/azure-pipelines/common/createAsset.js "server-$PLATFORM_LINUX" archive-unsigned "$SERVER_TARBALL_FILENAME" "$SERVER_TARBALL_PATH" # Publish DEB -PLATFORM_DEB="linux-deb-x64" -DEB_ARCH="amd64" +case $VSCODE_ARCH in + x64) DEB_ARCH="amd64" ;; + *) DEB_ARCH="$VSCODE_ARCH" ;; +esac + +PLATFORM_DEB="linux-deb-$VSCODE_ARCH" DEB_FILENAME="$(ls $REPO/.build/linux/deb/$DEB_ARCH/deb/)" DEB_PATH="$REPO/.build/linux/deb/$DEB_ARCH/deb/$DEB_FILENAME" node build/azure-pipelines/common/createAsset.js "$PLATFORM_DEB" package "$DEB_FILENAME" "$DEB_PATH" # Publish RPM -PLATFORM_RPM="linux-rpm-x64" -RPM_ARCH="x86_64" +case $VSCODE_ARCH in + x64) RPM_ARCH="x86_64" ;; + armhf) RPM_ARCH="armv7hl" ;; + arm64) RPM_ARCH="aarch64" ;; + *) RPM_ARCH="$VSCODE_ARCH" ;; +esac + +PLATFORM_RPM="linux-rpm-$VSCODE_ARCH" RPM_FILENAME="$(ls $REPO/.build/linux/rpm/$RPM_ARCH/ | grep .rpm)" RPM_PATH="$REPO/.build/linux/rpm/$RPM_ARCH/$RPM_FILENAME" node build/azure-pipelines/common/createAsset.js "$PLATFORM_RPM" package "$RPM_FILENAME" "$RPM_PATH" -# Publish Snap -# Pack snap tarball artifact, in order to preserve file perms -mkdir -p $REPO/.build/linux/snap-tarball -SNAP_TARBALL_PATH="$REPO/.build/linux/snap-tarball/snap-x64.tar.gz" -rm -rf $SNAP_TARBALL_PATH -(cd .build/linux && tar -czf $SNAP_TARBALL_PATH snap) +if [ "$VSCODE_ARCH" == "x64" ]; then + # Publish Snap + # Pack snap tarball artifact, in order to preserve file perms + mkdir -p $REPO/.build/linux/snap-tarball + SNAP_TARBALL_PATH="$REPO/.build/linux/snap-tarball/snap-$VSCODE_ARCH.tar.gz" + rm -rf $SNAP_TARBALL_PATH + (cd .build/linux && tar -czf $SNAP_TARBALL_PATH snap) +fi diff --git a/build/azure-pipelines/linux/sql-product-build-linux.yml b/build/azure-pipelines/linux/sql-product-build-linux.yml index 950a296db7..3edaff0dd1 100644 --- a/build/azure-pipelines/linux/sql-product-build-linux.yml +++ b/build/azure-pipelines/linux/sql-product-build-linux.yml @@ -91,8 +91,7 @@ steps: - script: | set -e yarn gulp vscode-linux-x64-min-ci - yarn gulp vscode-reh-linux-x64-min-ci - yarn gulp vscode-reh-web-linux-x64-min-ci + yarn gulp vscode-web-min-ci displayName: Build env: VSCODE_MIXIN_PASSWORD: $(github-distro-mixin-password) diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index 1f3a080508..7d4246f4b3 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -13,6 +13,12 @@ resources: - container: vscode-x64 image: vscodehub.azurecr.io/vscode-linux-build-agent:x64 endpoint: VSCodeHub + - container: vscode-arm64 + image: vscodehub.azurecr.io/vscode-linux-build-agent:stretch-arm64 + endpoint: VSCodeHub + - container: vscode-armhf + image: vscodehub.azurecr.io/vscode-linux-build-agent:stretch-armhf + endpoint: VSCodeHub - container: snapcraft image: snapcore/snapcraft:stable @@ -64,6 +70,9 @@ stages: - job: Linux condition: and(succeeded(), eq(variables['VSCODE_BUILD_LINUX'], 'true')) container: vscode-x64 + variables: + VSCODE_ARCH: x64 + NPM_ARCH: x64 steps: - template: linux/product-build-linux.yml @@ -72,22 +81,28 @@ stages: - Linux condition: and(succeeded(), eq(variables['VSCODE_BUILD_LINUX'], 'true')) container: snapcraft + variables: + VSCODE_ARCH: x64 steps: - template: linux/snap-build-linux.yml - job: LinuxArmhf condition: and(succeeded(), eq(variables['VSCODE_BUILD_LINUX_ARMHF'], 'true')) + container: vscode-armhf variables: VSCODE_ARCH: armhf + NPM_ARCH: armv7l steps: - - template: linux/product-build-linux-multiarch.yml + - template: linux/product-build-linux.yml - job: LinuxArm64 condition: and(succeeded(), eq(variables['VSCODE_BUILD_LINUX_ARM64'], 'true')) + container: vscode-arm64 variables: VSCODE_ARCH: arm64 + NPM_ARCH: arm64 steps: - - template: linux/product-build-linux-multiarch.yml + - template: linux/product-build-linux.yml - job: LinuxAlpine condition: and(succeeded(), eq(variables['VSCODE_BUILD_LINUX_ALPINE'], 'true')) diff --git a/build/azure-pipelines/product-compile.yml b/build/azure-pipelines/product-compile.yml index ab0dbb932c..f3b54789e5 100644 --- a/build/azure-pipelines/product-compile.yml +++ b/build/azure-pipelines/product-compile.yml @@ -52,9 +52,13 @@ steps: displayName: Merge distro condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) +- script: | + echo -n $VSCODE_ARCH > .build/arch + displayName: Prepare arch cache flag + - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 inputs: - keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' + keyfile: '.build/arch, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' targetfolder: '**/node_modules, !**/node_modules/**/node_modules' vstsFeed: 'npm-vscode' condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) @@ -67,7 +71,7 @@ steps: - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 inputs: - keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' + keyfile: '.build/arch, build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' targetfolder: '**/node_modules, !**/node_modules/**/node_modules' vstsFeed: 'npm-vscode' condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true'), ne(variables['CacheRestored'], 'true')) @@ -112,8 +116,8 @@ steps: yarn gulp compile-build yarn gulp compile-extensions-build yarn gulp minify-vscode - yarn gulp minify-vscode-reh - yarn gulp minify-vscode-reh-web + yarn gulp vscode-reh-linux-x64-min + yarn gulp vscode-reh-web-linux-x64-min displayName: Compile condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) diff --git a/build/azure-pipelines/sql-product-build.yml b/build/azure-pipelines/sql-product-build.yml index 418aab1408..051c6b8517 100644 --- a/build/azure-pipelines/sql-product-build.yml +++ b/build/azure-pipelines/sql-product-build.yml @@ -61,15 +61,15 @@ jobs: steps: - template: web/sql-product-build-web.yml -- job: Docker - condition: and(succeeded(), eq(variables['VSCODE_BUILD_DOCKER'], 'true')) - pool: - vmImage: 'Ubuntu-16.04' - container: linux-x64 - dependsOn: - - Linux - steps: - - template: docker/sql-product-build-docker.yml +# - job: Docker +# condition: and(succeeded(), eq(variables['VSCODE_BUILD_DOCKER'], 'true')) +# pool: +# vmImage: 'Ubuntu-16.04' +# container: linux-x64 +# dependsOn: +# - Linux +# steps: +# - template: docker/sql-product-build-docker.yml - job: Windows condition: and(succeeded(), eq(variables['VSCODE_BUILD_WIN32'], 'true')) @@ -98,7 +98,7 @@ jobs: dependsOn: - macOS - Linux - - Docker + # - Docker - Windows - Windows_Test - LinuxWeb diff --git a/build/azure-pipelines/sql-product-compile.yml b/build/azure-pipelines/sql-product-compile.yml index 2f1ff22fc4..a66fac37f5 100644 --- a/build/azure-pipelines/sql-product-compile.yml +++ b/build/azure-pipelines/sql-product-compile.yml @@ -96,8 +96,8 @@ steps: yarn gulp compile-build yarn gulp compile-extensions-build yarn gulp minify-vscode - yarn gulp minify-vscode-reh - yarn gulp minify-vscode-reh-web + yarn gulp vscode-reh-linux-x64-min + yarn gulp vscode-reh-web-linux-x64-min displayName: Compile condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) diff --git a/build/azure-pipelines/win32/createDrop.ps1 b/build/azure-pipelines/win32/createDrop.ps1 index 4552c2014e..3bdc98c80f 100644 --- a/build/azure-pipelines/win32/createDrop.ps1 +++ b/build/azure-pipelines/win32/createDrop.ps1 @@ -12,9 +12,9 @@ $ServerZipLocation = "$Repo\.build\win32-$Arch\server" $ServerZip = "$ServerZipLocation\azuredatastudio-server-win32-$Arch.zip" # Create server archive -New-Item $ServerZipLocation -ItemType Directory # this will throw even when success for we don't want to exec this +# New-Item $ServerZipLocation -ItemType Directory # this will throw even when success for we don't want to exec this $global:LASTEXITCODE = 0 -exec { Rename-Item -Path $LegacyServer -NewName $ServerName } "Rename Item" -exec { .\node_modules\7zip\7zip-lite\7z.exe a -tzip $ServerZip $Server -r } "Zip Server" +# exec { Rename-Item -Path $LegacyServer -NewName $ServerName } "Rename Item" +# exec { .\node_modules\7zip\7zip-lite\7z.exe a -tzip $ServerZip $Server -r } "Zip Server" exec { node build/azure-pipelines/common/copyArtifacts.js } "Copy Artifacts" diff --git a/build/azure-pipelines/win32/sql-product-build-win32.yml b/build/azure-pipelines/win32/sql-product-build-win32.yml index 96cca91c63..5393829bf7 100644 --- a/build/azure-pipelines/win32/sql-product-build-win32.yml +++ b/build/azure-pipelines/win32/sql-product-build-win32.yml @@ -95,8 +95,8 @@ steps: $ErrorActionPreference = "Stop" exec { yarn gulp "package-rebuild-extensions" } exec { yarn gulp "vscode-win32-x64-min-ci" } - exec { yarn gulp "vscode-reh-win32-x64-min-ci" } - exec { yarn gulp "vscode-reh-web-win32-x64-min-ci" } + exec { yarn gulp "vscode-reh-win32-x64-min" } + exec { yarn gulp "vscode-reh-web-win32-x64-min" } exec { yarn gulp "vscode-win32-x64-code-helper" } exec { yarn gulp "vscode-win32-x64-inno-updater" } displayName: Build @@ -131,7 +131,7 @@ steps: $AppRoot = "$(agent.builddirectory)\azuredatastudio-win32-x64" $AppProductJson = Get-Content -Raw -Path "$AppRoot\resources\app\product.json" | ConvertFrom-Json $AppNameShort = $AppProductJson.nameShort - exec { $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe"; $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\azuredatastudio-reh-win32-x64"; .\scripts\test-integration.bat --build --tfs "Integration Tests" } + # exec { $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe"; $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\azuredatastudio-reh-win32-x64"; .\scripts\test-integration.bat --build --tfs "Integration Tests" } displayName: Run integration tests (Electron) condition: and(succeeded(), eq(variables['RUN_TESTS'], 'true')) diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 8519b83f03..7319578492 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -261,7 +261,7 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op .pipe(fileLengthFilter.restore) .pipe(util.skipDirectories()) .pipe(util.fixWin32DirectoryPermissions()) - .pipe(electron(_.extend({}, config, { platform, arch, ffmpegChromium: true }))) + .pipe(electron(_.extend({}, config, { platform, arch: arch === 'armhf' ? 'arm' : arch, ffmpegChromium: true }))) .pipe(filter(['**', '!LICENSE', '!LICENSES.chromium.html', '!version'], { dot: true })); if (platform === 'linux') { @@ -345,7 +345,7 @@ const BUILD_TARGETS = [ { platform: 'darwin', arch: null, opts: { stats: true } }, { platform: 'linux', arch: 'ia32' }, { platform: 'linux', arch: 'x64' }, - { platform: 'linux', arch: 'arm' }, + { platform: 'linux', arch: 'armhf' }, { platform: 'linux', arch: 'arm64' }, ]; BUILD_TARGETS.forEach(buildTarget => { diff --git a/build/gulpfile.vscode.linux.js b/build/gulpfile.vscode.linux.js index cd9684b4ed..034cd1e7ef 100644 --- a/build/gulpfile.vscode.linux.js +++ b/build/gulpfile.vscode.linux.js @@ -23,7 +23,7 @@ const commit = util.getVersion(root); const linuxPackageRevision = Math.floor(new Date().getTime() / 1000); function getDebPackageArch(arch) { - return { x64: 'amd64', arm: 'armhf', arm64: 'arm64' }[arch]; + return { x64: 'amd64', armhf: 'armhf', arm64: 'arm64' }[arch]; } function prepareDebPackage(arch) { @@ -53,6 +53,11 @@ function prepareDebPackage(arch) { .pipe(replace('@@LICENSE@@', product.licenseName)) .pipe(rename('usr/share/appdata/' + product.applicationName + '.appdata.xml')); + const workspaceMime = gulp.src('resources/linux/code-workspace.xml', { base: '.' }) + .pipe(replace('@@NAME_LONG@@', product.nameLong)) + .pipe(replace('@@NAME@@', product.applicationName)) + .pipe(rename('usr/share/mime/packages/' + product.applicationName + '-workspace.xml')); + const icon = gulp.src('resources/linux/code.png', { base: '.' }) .pipe(rename('usr/share/pixmaps/' + product.linuxIconName + '.png')); @@ -96,7 +101,7 @@ function prepareDebPackage(arch) { .pipe(replace('@@UPDATEURL@@', product.updateUrl || '@@UPDATEURL@@')) .pipe(rename('DEBIAN/postinst')); - const all = es.merge(control, postinst, postrm, prerm, desktops, appdata, icon, bash_completion, zsh_completion, code); + const all = es.merge(control, postinst, postrm, prerm, desktops, appdata, workspaceMime, icon, bash_completion, zsh_completion, code); return all.pipe(vfs.dest(destination)); }; @@ -116,7 +121,7 @@ function getRpmBuildPath(rpmArch) { } function getRpmPackageArch(arch) { - return { x64: 'x86_64', arm: 'armhf', arm64: 'arm64' }[arch]; + return { x64: 'x86_64', armhf: 'armv7hl', arm64: 'aarch64' }[arch]; } function prepareRpmPackage(arch) { @@ -145,6 +150,11 @@ function prepareRpmPackage(arch) { .pipe(replace('@@LICENSE@@', product.licenseName)) .pipe(rename('usr/share/appdata/' + product.applicationName + '.appdata.xml')); + const workspaceMime = gulp.src('resources/linux/code-workspace.xml', { base: '.' }) + .pipe(replace('@@NAME_LONG@@', product.nameLong)) + .pipe(replace('@@NAME@@', product.applicationName)) + .pipe(rename('BUILD/usr/share/mime/packages/' + product.applicationName + '-workspace.xml')); + const icon = gulp.src('resources/linux/code.png', { base: '.' }) .pipe(rename('BUILD/usr/share/pixmaps/' + product.linuxIconName + '.png')); @@ -175,7 +185,7 @@ function prepareRpmPackage(arch) { const specIcon = gulp.src('resources/linux/rpm/code.xpm', { base: '.' }) .pipe(rename('SOURCES/' + product.applicationName + '.xpm')); - const all = es.merge(code, desktops, appdata, icon, bash_completion, zsh_completion, spec, specIcon); + const all = es.merge(code, desktops, appdata, workspaceMime, icon, bash_completion, zsh_completion, spec, specIcon); return all.pipe(vfs.dest(getRpmBuildPath(rpmArch))); }; @@ -249,33 +259,23 @@ function buildSnapPackage(arch) { const BUILD_TARGETS = [ { arch: 'x64' }, - { arch: 'arm' }, + { arch: 'armhf' }, { arch: 'arm64' }, ]; -BUILD_TARGETS.forEach((buildTarget) => { - const arch = buildTarget.arch; +BUILD_TARGETS.forEach(({ arch }) => { + const debArch = getDebPackageArch(arch); + const prepareDebTask = task.define(`vscode-linux-${arch}-prepare-deb`, task.series(util.rimraf(`.build/linux/deb/${debArch}`), prepareDebPackage(arch))); + const buildDebTask = task.define(`vscode-linux-${arch}-build-deb`, task.series(prepareDebTask, buildDebPackage(arch))); + gulp.task(buildDebTask); - { - const debArch = getDebPackageArch(arch); - const prepareDebTask = task.define(`vscode-linux-${arch}-prepare-deb`, task.series(util.rimraf(`.build/linux/deb/${debArch}`), prepareDebPackage(arch))); - // gulp.task(prepareDebTask); - const buildDebTask = task.define(`vscode-linux-${arch}-build-deb`, task.series(prepareDebTask, buildDebPackage(arch))); - gulp.task(buildDebTask); - } + const rpmArch = getRpmPackageArch(arch); + const prepareRpmTask = task.define(`vscode-linux-${arch}-prepare-rpm`, task.series(util.rimraf(`.build/linux/rpm/${rpmArch}`), prepareRpmPackage(arch))); + const buildRpmTask = task.define(`vscode-linux-${arch}-build-rpm`, task.series(prepareRpmTask, buildRpmPackage(arch))); + gulp.task(buildRpmTask); - { - const rpmArch = getRpmPackageArch(arch); - const prepareRpmTask = task.define(`vscode-linux-${arch}-prepare-rpm`, task.series(util.rimraf(`.build/linux/rpm/${rpmArch}`), prepareRpmPackage(arch))); - // gulp.task(prepareRpmTask); - const buildRpmTask = task.define(`vscode-linux-${arch}-build-rpm`, task.series(prepareRpmTask, buildRpmPackage(arch))); - gulp.task(buildRpmTask); - } - - { - const prepareSnapTask = task.define(`vscode-linux-${arch}-prepare-snap`, task.series(util.rimraf(`.build/linux/snap/${arch}`), prepareSnapPackage(arch))); - gulp.task(prepareSnapTask); - const buildSnapTask = task.define(`vscode-linux-${arch}-build-snap`, task.series(prepareSnapTask, buildSnapPackage(arch))); - gulp.task(buildSnapTask); - } + const prepareSnapTask = task.define(`vscode-linux-${arch}-prepare-snap`, task.series(util.rimraf(`.build/linux/snap/${arch}`), prepareSnapPackage(arch))); + gulp.task(prepareSnapTask); + const buildSnapTask = task.define(`vscode-linux-${arch}-build-snap`, task.series(prepareSnapTask, buildSnapPackage(arch))); + gulp.task(buildSnapTask); }); diff --git a/build/lib/electron.js b/build/lib/electron.js index 3cd602a96e..9cf2e89e88 100644 --- a/build/lib/electron.js +++ b/build/lib/electron.js @@ -55,7 +55,7 @@ function getElectron(arch) { return () => { const electronOpts = _.extend({}, exports.config, { platform: process.platform, - arch, + arch: arch === 'armhf' ? 'arm' : arch, ffmpegChromium: true, keepDefaultApp: true }); diff --git a/build/lib/electron.ts b/build/lib/electron.ts index 561e777391..030fc0b4b0 100644 --- a/build/lib/electron.ts +++ b/build/lib/electron.ts @@ -61,7 +61,7 @@ function getElectron(arch: string): () => NodeJS.ReadWriteStream { return () => { const electronOpts = _.extend({}, config, { platform: process.platform, - arch, + arch: arch === 'armhf' ? 'arm' : arch, ffmpegChromium: true, keepDefaultApp: true }); diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index a192a31e57..0d34d1cea6 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -206,6 +206,10 @@ "name": "vs/workbench/contrib/webview", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/webviewPanel", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/customEditor", "project": "vscode-workbench" diff --git a/build/lib/i18n.ts b/build/lib/i18n.ts index 38947dd1aa..be7232f2e6 100644 --- a/build/lib/i18n.ts +++ b/build/lib/i18n.ts @@ -1004,7 +1004,7 @@ function createResource(project: string, slug: string, xlfFile: File, apiHostnam * https://dev.befoolish.co/tx-docs/public/projects/updating-content#what-happens-when-you-update-files */ function updateResource(project: string, slug: string, xlfFile: File, apiHostname: string, credentials: string): Promise { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { const data = JSON.stringify({ content: xlfFile.contents.toString() }); const options = { hostname: apiHostname, diff --git a/build/lib/layersChecker.js b/build/lib/layersChecker.js index 0733d83e34..a688d1e1fc 100644 --- a/build/lib/layersChecker.js +++ b/build/lib/layersChecker.js @@ -53,6 +53,13 @@ const CORE_TYPES = [ 'trimLeft', 'trimRight' ]; +// Types that are defined in a common layer but are known to be only +// available in native environments should not be allowed in browser +const NATIVE_TYPES = [ + 'NativeParsedArgs', + 'INativeEnvironmentService', + 'INativeWindowConfiguration' +]; const RULES = [ // Tests: skip { @@ -68,6 +75,37 @@ const RULES = [ 'MessageEvent', 'data' ], + disallowedTypes: NATIVE_TYPES, + disallowedDefinitions: [ + 'lib.dom.d.ts', + '@types/node' // no node.js + ] + }, + // Common: vs/platform/environment/common/argv.ts + { + target: '**/{vs,sql}/platform/environment/common/argv.ts', + disallowedTypes: [ /* Ignore native types that are defined from here */], + allowedTypes: CORE_TYPES, + disallowedDefinitions: [ + 'lib.dom.d.ts', + '@types/node' // no node.js + ] + }, + // Common: vs/platform/environment/common/environment.ts + { + target: '**/{vs,sql}/platform/environment/common/environment.ts', + disallowedTypes: [ /* Ignore native types that are defined from here */], + allowedTypes: CORE_TYPES, + disallowedDefinitions: [ + 'lib.dom.d.ts', + '@types/node' // no node.js + ] + }, + // Common: vs/platform/windows/common/windows.ts + { + target: '**/{vs,sql}/platform/windows/common/windows.ts', + disallowedTypes: [ /* Ignore native types that are defined from here */], + allowedTypes: CORE_TYPES, disallowedDefinitions: [ 'lib.dom.d.ts', '@types/node' // no node.js @@ -81,6 +119,7 @@ const RULES = [ // Safe access to global 'global' ], + disallowedTypes: NATIVE_TYPES, disallowedDefinitions: [ 'lib.dom.d.ts', '@types/node' // no node.js @@ -90,6 +129,7 @@ const RULES = [ { target: '**/{vs,sql}/**/common/**', allowedTypes: CORE_TYPES, + disallowedTypes: NATIVE_TYPES, disallowedDefinitions: [ 'lib.dom.d.ts', '@types/node' // no node.js @@ -99,6 +139,7 @@ const RULES = [ { target: '**/{vs,sql}/**/browser/**', allowedTypes: CORE_TYPES, + disallowedTypes: NATIVE_TYPES, disallowedDefinitions: [ '@types/node' // no node.js ] @@ -107,6 +148,7 @@ const RULES = [ { target: '**/src/{vs,sql}/editor/contrib/**', allowedTypes: CORE_TYPES, + disallowedTypes: NATIVE_TYPES, disallowedDefinitions: [ '@types/node' // no node.js ] @@ -132,7 +174,7 @@ const RULES = [ }, // Electron (sandbox) { - target: '**/vs/**/electron-sandbox/**', + target: '**/{vs,sql}/**/electron-sandbox/**', allowedTypes: CORE_TYPES, disallowedDefinitions: [ '@types/node' // no node.js @@ -162,7 +204,7 @@ let hasErrors = false; function checkFile(program, sourceFile, rule) { checkNode(sourceFile); function checkNode(node) { - var _a; + var _a, _b; if (node.kind !== ts.SyntaxKind.Identifier) { return ts.forEachChild(node, checkNode); // recurse down } @@ -170,6 +212,12 @@ function checkFile(program, sourceFile, rule) { if ((_a = rule.allowedTypes) === null || _a === void 0 ? void 0 : _a.some(allowed => allowed === text)) { return; // override } + if ((_b = rule.disallowedTypes) === null || _b === void 0 ? void 0 : _b.some(disallowed => disallowed === text)) { + const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); + console.log(`[build/lib/layersChecker.ts]: Reference to '${text}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1})`); + hasErrors = true; + return; + } const checker = program.getTypeChecker(); const symbol = checker.getSymbolAtLocation(node); if (symbol) { diff --git a/build/lib/layersChecker.ts b/build/lib/layersChecker.ts index 172b52e9a1..d9d67b48ff 100644 --- a/build/lib/layersChecker.ts +++ b/build/lib/layersChecker.ts @@ -55,6 +55,14 @@ const CORE_TYPES = [ 'trimRight' ]; +// Types that are defined in a common layer but are known to be only +// available in native environments should not be allowed in browser +const NATIVE_TYPES = [ + 'NativeParsedArgs', + 'INativeEnvironmentService', + 'INativeWindowConfiguration' +]; + const RULES = [ // Tests: skip @@ -73,6 +81,40 @@ const RULES = [ 'MessageEvent', 'data' ], + disallowedTypes: NATIVE_TYPES, + disallowedDefinitions: [ + 'lib.dom.d.ts', // no DOM + '@types/node' // no node.js + ] + }, + + // Common: vs/platform/environment/common/argv.ts + { + target: '**/{vs,sql}/platform/environment/common/argv.ts', + disallowedTypes: [/* Ignore native types that are defined from here */], + allowedTypes: CORE_TYPES, + disallowedDefinitions: [ + 'lib.dom.d.ts', // no DOM + '@types/node' // no node.js + ] + }, + + // Common: vs/platform/environment/common/environment.ts + { + target: '**/{vs,sql}/platform/environment/common/environment.ts', + disallowedTypes: [/* Ignore native types that are defined from here */], + allowedTypes: CORE_TYPES, + disallowedDefinitions: [ + 'lib.dom.d.ts', // no DOM + '@types/node' // no node.js + ] + }, + + // Common: vs/platform/windows/common/windows.ts + { + target: '**/{vs,sql}/platform/windows/common/windows.ts', + disallowedTypes: [/* Ignore native types that are defined from here */], + allowedTypes: CORE_TYPES, disallowedDefinitions: [ 'lib.dom.d.ts', // no DOM '@types/node' // no node.js @@ -88,6 +130,7 @@ const RULES = [ // Safe access to global 'global' ], + disallowedTypes: NATIVE_TYPES, disallowedDefinitions: [ 'lib.dom.d.ts', // no DOM '@types/node' // no node.js @@ -98,6 +141,7 @@ const RULES = [ { target: '**/{vs,sql}/**/common/**', allowedTypes: CORE_TYPES, + disallowedTypes: NATIVE_TYPES, disallowedDefinitions: [ 'lib.dom.d.ts', // no DOM '@types/node' // no node.js @@ -108,6 +152,7 @@ const RULES = [ { target: '**/{vs,sql}/**/browser/**', allowedTypes: CORE_TYPES, + disallowedTypes: NATIVE_TYPES, disallowedDefinitions: [ '@types/node' // no node.js ] @@ -117,6 +162,7 @@ const RULES = [ { target: '**/src/{vs,sql}/editor/contrib/**', allowedTypes: CORE_TYPES, + disallowedTypes: NATIVE_TYPES, disallowedDefinitions: [ '@types/node' // no node.js ] @@ -145,7 +191,7 @@ const RULES = [ // Electron (sandbox) { - target: '**/vs/**/electron-sandbox/**', + target: '**/{vs,sql}/**/electron-sandbox/**', allowedTypes: CORE_TYPES, disallowedDefinitions: [ '@types/node' // no node.js @@ -181,6 +227,7 @@ interface IRule { skip?: boolean; allowedTypes?: string[]; disallowedDefinitions?: string[]; + disallowedTypes?: string[]; } let hasErrors = false; @@ -199,6 +246,14 @@ function checkFile(program: ts.Program, sourceFile: ts.SourceFile, rule: IRule) return; // override } + if (rule.disallowedTypes?.some(disallowed => disallowed === text)) { + const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); + console.log(`[build/lib/layersChecker.ts]: Reference to '${text}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1})`); + + hasErrors = true; + return; + } + const checker = program.getTypeChecker(); const symbol = checker.getSymbolAtLocation(node); if (symbol) { diff --git a/build/lib/preLaunch.ts b/build/lib/preLaunch.ts index 9df6882f4d..3498e57c70 100644 --- a/build/lib/preLaunch.ts +++ b/build/lib/preLaunch.ts @@ -15,7 +15,7 @@ const yarn = process.platform === 'win32' ? 'yarn.cmd' : 'yarn'; const rootDir = path.resolve(__dirname, '..', '..'); function runProcess(command: string, args: ReadonlyArray = []) { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { const child = spawn(command, args, { cwd: rootDir, stdio: 'inherit', env: process.env }); child.on('exit', err => !err ? resolve() : process.exit(err ?? 1)); child.on('error', reject); diff --git a/cgmanifest.json b/cgmanifest.json index e6e8ce8a5c..859af568b1 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -60,12 +60,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "03c7a54dc534ce1867d4393b9b1a6989d4a7e005" + "commitHash": "fb03807cd21915ddc3aa2521ba4f5ba14597bd7e" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "9.2.1" + "version": "9.3.0" }, { "component": { diff --git a/extensions/bat/language-configuration.json b/extensions/bat/language-configuration.json index 17bc92f6a9..e205fb5f5c 100644 --- a/extensions/bat/language-configuration.json +++ b/extensions/bat/language-configuration.json @@ -1,6 +1,6 @@ { "comments": { - "lineComment": "REM" + "lineComment": "@REM" }, "brackets": [ ["{", "}"], @@ -22,8 +22,8 @@ ], "folding": { "markers": { - "start": "^\\s*(::\\s*|REM\\s+)#region", - "end": "^\\s*(::\\s*|REM\\s+)#endregion" + "start": "^\\s*(::|REM|@REM)\\s*#region", + "end": "^\\s*(::|REM|@REM)\\s*#endregion" } } } diff --git a/extensions/configuration-editing/schemas/attachContainer.schema.json b/extensions/configuration-editing/schemas/attachContainer.schema.json index fc53bb896f..2b7952446f 100644 --- a/extensions/configuration-editing/schemas/attachContainer.schema.json +++ b/extensions/configuration-editing/schemas/attachContainer.schema.json @@ -50,6 +50,7 @@ "type": "string", "enum": [ "none", + "loginShell", "loginInteractiveShell", "interactiveShell" ], diff --git a/extensions/configuration-editing/schemas/devContainer.schema.json b/extensions/configuration-editing/schemas/devContainer.schema.json index 999a50e778..b37e07fa65 100644 --- a/extensions/configuration-editing/schemas/devContainer.schema.json +++ b/extensions/configuration-editing/schemas/devContainer.schema.json @@ -96,6 +96,7 @@ "type": "string", "enum": [ "none", + "loginShell", "loginInteractiveShell", "interactiveShell" ], diff --git a/extensions/git/package.json b/extensions/git/package.json index 1e577006c1..6570b4a36c 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -216,6 +216,47 @@ "title": "%command.commitAllAmend%", "category": "Git" }, + { + "command": "git.commitNoVerify", + "title": "%command.commitNoVerify%", + "category": "Git", + "icon": "$(check)" + }, + { + "command": "git.commitStagedNoVerify", + "title": "%command.commitStagedNoVerify%", + "category": "Git" + }, + { + "command": "git.commitEmptyNoVerify", + "title": "%command.commitEmptyNoVerify%", + "category": "Git" + }, + { + "command": "git.commitStagedSignedNoVerify", + "title": "%command.commitStagedSignedNoVerify%", + "category": "Git" + }, + { + "command": "git.commitStagedAmendNoVerify", + "title": "%command.commitStagedAmendNoVerify%", + "category": "Git" + }, + { + "command": "git.commitAllNoVerify", + "title": "%command.commitAllNoVerify%", + "category": "Git" + }, + { + "command": "git.commitAllSignedNoVerify", + "title": "%command.commitAllSignedNoVerify%", + "category": "Git" + }, + { + "command": "git.commitAllAmendNoVerify", + "title": "%command.commitAllAmendNoVerify%", + "category": "Git" + }, { "command": "git.restoreCommitTemplate", "title": "%command.restoreCommitTemplate%", @@ -581,6 +622,38 @@ "command": "git.commitAllAmend", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, + { + "command": "git.commitNoVerify", + "when": "config.git.enabled && !git.missing && config.git.allowNoVerifyCommit && gitOpenRepositoryCount != 0" + }, + { + "command": "git.commitStagedNoVerify", + "when": "config.git.enabled && !git.missing && config.git.allowNoVerifyCommit && gitOpenRepositoryCount != 0" + }, + { + "command": "git.commitEmptyNoVerify", + "when": "config.git.enabled && !git.missing && config.git.allowNoVerifyCommit && gitOpenRepositoryCount != 0" + }, + { + "command": "git.commitStagedSignedNoVerify", + "when": "config.git.enabled && !git.missing && config.git.allowNoVerifyCommit && gitOpenRepositoryCount != 0" + }, + { + "command": "git.commitStagedAmendNoVerify", + "when": "config.git.enabled && !git.missing && config.git.allowNoVerifyCommit && gitOpenRepositoryCount != 0" + }, + { + "command": "git.commitAllNoVerify", + "when": "config.git.enabled && !git.missing && config.git.allowNoVerifyCommit && gitOpenRepositoryCount != 0" + }, + { + "command": "git.commitAllSignedNoVerify", + "when": "config.git.enabled && !git.missing && config.git.allowNoVerifyCommit && gitOpenRepositoryCount != 0" + }, + { + "command": "git.commitAllAmendNoVerify", + "when": "config.git.enabled && !git.missing && config.git.allowNoVerifyCommit && gitOpenRepositoryCount != 0" + }, { "command": "git.restoreCommitTemplate", "when": "false" @@ -1239,13 +1312,38 @@ "command": "git.rebaseAbort", "group": "1_commit@5" }, + { + "command": "git.commitNoVerify", + "group": "1_commit@6", + "when": "config.git.allowNoVerifyCommit" + }, + { + "command": "git.commitStagedNoVerify", + "group": "1_commit@7", + "when": "config.git.allowNoVerifyCommit" + }, + { + "command": "git.commitAllNoVerify", + "group": "1_commit@8", + "when": "config.git.allowNoVerifyCommit" + }, { "command": "git.commitStagedAmend", "group": "2_amend@1" }, { "command": "git.commitAllAmend", - "group": "2_amend@1" + "group": "2_amend@2" + }, + { + "command": "git.commitStagedAmendNoVerify", + "group": "2_amend@3", + "when": "config.git.allowNoVerifyCommit" + }, + { + "command": "git.commitAllAmendNoVerify", + "group": "2_amend@4", + "when": "config.git.allowNoVerifyCommit" }, { "command": "git.commitStagedSigned", @@ -1254,6 +1352,16 @@ { "command": "git.commitAllSigned", "group": "3_signoff@2" + }, + { + "command": "git.commitStagedSignedNoVerify", + "group": "3_signoff@3", + "when": "config.git.allowNoVerifyCommit" + }, + { + "command": "git.commitAllSignedNoVerify", + "group": "3_signoff@4", + "when": "config.git.allowNoVerifyCommit" } ], "git.changes": [ @@ -1407,7 +1515,8 @@ "git.path": { "type": [ "string", - "null" + "null", + "array" ], "markdownDescription": "%config.path%", "default": null, @@ -1730,6 +1839,16 @@ "default": true, "description": "%config.confirmForcePush%" }, + "git.allowNoVerifyCommit": { + "type": "boolean", + "default": false, + "description": "%config.allowNoVerifyCommit%" + }, + "git.confirmNoVerifyCommit": { + "type": "boolean", + "default": true, + "description": "%config.confirmNoVerifyCommit%" + }, "git.openDiffOnClick": { "type": "boolean", "scope": "resource", diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index c55db8a2fd..4912dca308 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -34,6 +34,14 @@ "command.commitAll": "Commit All", "command.commitAllSigned": "Commit All (Signed Off)", "command.commitAllAmend": "Commit All (Amend)", + "command.commitNoVerify": "Commit (No Nerify)", + "command.commitStagedNoVerify": "Commit Staged (No Verify)", + "command.commitEmptyNoVerify": "Commit Empty (No Verify)", + "command.commitStagedSignedNoVerify": "Commit Staged (Signed Off, No Verify)", + "command.commitStagedAmendNoVerify": "Commit Staged (Amend, No Verify)", + "command.commitAllNoVerify": "Commit All (No Verify)", + "command.commitAllSignedNoVerify": "Commit All (Signed Off, No Verify)", + "command.commitAllAmendNoVerify": "Commit All (Amend, No Verify)", "command.restoreCommitTemplate": "Restore Commit Template", "command.undoCommit": "Undo Last Commit", "command.checkout": "Checkout to...", @@ -76,7 +84,7 @@ "command.timelineCopyCommitId": "Copy Commit ID", "command.timelineCopyCommitMessage": "Copy Commit Message", "config.enabled": "Whether git is enabled.", - "config.path": "Path and filename of the git executable, e.g. `C:\\Program Files\\Git\\bin\\git.exe` (Windows).", + "config.path": "Path and filename of the git executable, e.g. `C:\\Program Files\\Git\\bin\\git.exe` (Windows). This can also be an array of string values containing multiple paths to look up.", "config.autoRepositoryDetection": "Configures when repositories should be automatically detected.", "config.autoRepositoryDetection.true": "Scan for both subfolders of the current opened folder and parent folders of open files.", "config.autoRepositoryDetection.false": "Disable automatic repository scanning.", @@ -139,6 +147,8 @@ "config.allowForcePush": "Controls whether force push (with or without lease) is enabled.", "config.useForcePushWithLease": "Controls whether force pushing uses the safer force-with-lease variant.", "config.confirmForcePush": "Controls whether to ask for confirmation before force-pushing.", + "config.allowNoVerifyCommit": "Controls whether commits without running pre-commit and commit-msg hooks are allowed.", + "config.confirmNoVerifyCommit": "Controls whether to ask for confirmation before commiting without verification.", "config.openDiffOnClick": "Controls whether the diff editor should be opened when clicking a change. Otherwise the regular editor will be opened.", "config.supportCancellation": "Controls whether a notification comes up when running the Sync action, which allows the user to cancel the operation.", "config.branchSortOrder": "Controls the sort order for branches.", @@ -166,8 +176,8 @@ "view.workbench.scm.missing": "A valid git installation was not detected, more details can be found in the [git output](command:git.showOutput).\nPlease [install git](https://git-scm.com/), or learn more about how to use git and source control in Azure Data Studio in [our docs](https://aka.ms/vscode-scm).\nIf you're using a different version control system, you can [search the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22) for additional extensions.", "view.workbench.scm.disabled": "If you would like to use git features, please enable git in your [settings](command:workbench.action.openSettings?%5B%22git.enabled%22%5D).\nTo learn more about how to use git and source control in Azure Data Studio [read our docs](https://aka.ms/vscode-scm).", "view.workbench.scm.empty": "In order to use git features, you can open a folder containing a git repository or clone from a URL.\n[Open Folder](command:vscode.openFolder)\n[Clone Repository](command:git.clone)\nTo learn more about how to use git and source control in Azure Data Studio [read our docs](https://aka.ms/vscode-scm).", - "view.workbench.scm.folder": "The folder currently open doesn't have a git repository.\n[Initialize Repository](command:git.init?%5Btrue%5D)\nTo learn more about how to use git and source control in Azure Data Studio [read our docs](https://aka.ms/vscode-scm).", - "view.workbench.scm.workspace": "The workspace currently open doesn't have any folders containing git repositories.\n[Initialize Repository](command:git.init)\nTo learn more about how to use git and source control in Azure Data Studio [read our docs](https://aka.ms/vscode-scm).", + "view.workbench.scm.folder": "The folder currently open doesn't have a git repository. You can initialize a repository which will enable source control features powered by git.\n[Initialize Repository](command:git.init?%5Btrue%5D)\nTo learn more about how to use git and source control in Azure Data Studio [read our docs](https://aka.ms/vscode-scm).", + "view.workbench.scm.workspace": "The workspace currently open doesn't have any folders containing git repositories. You can initialize a repository on a folder which will enable source control features powered by git.\n[Initialize Repository](command:git.init)\nTo learn more about how to use git and source control in Azure Data Studio [read our docs](https://aka.ms/vscode-scm).", "view.workbench.scm.emptyWorkspace": "The workspace currently open doesn't have any folders containing git repositories.\n[Add Folder to Workspace](command:workbench.action.addRootFolder)\nTo learn more about how to use git and source control in Azure Data Studio [read our docs](https://aka.ms/vscode-scm).", "view.workbench.cloneRepository": "You can also clone a repository from a URL. To learn more about how to use git and source control in Azure Data Studio [read our docs](https://aka.ms/vscode-scm).\n[Clone Repository](command:git.clone)" } diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index 9593d8bc70..d55714c219 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -130,6 +130,7 @@ export interface CommitOptions { signoff?: boolean; signCommit?: boolean; empty?: boolean; + noVerify?: boolean; } export interface BranchQuery { diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index b431f970a8..6bf9a25ae3 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -6,7 +6,7 @@ import { lstat, Stats } from 'fs'; import * as os from 'os'; import * as path from 'path'; -import { commands, Disposable, LineChange, MessageOptions, OutputChannel, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env } from 'vscode'; +import { commands, Disposable, LineChange, MessageOptions, OutputChannel, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection } from 'vscode'; import TelemetryReporter from 'vscode-extension-telemetry'; import * as nls from 'vscode-nls'; import { Branch, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourceProvider } from './api/git'; @@ -1002,6 +1002,9 @@ export class CommandCenter { } await this._stageChanges(textEditor, [changes[index]]); + + const firstStagedLine = changes[index].modifiedStartLineNumber - 1; + textEditor.selections = [new Selection(firstStagedLine, 0, firstStagedLine, 0)]; } @command('git.stageSelectedRanges', { diff: true }) @@ -1049,6 +1052,9 @@ export class CommandCenter { } await this._revertChanges(textEditor, [...changes.slice(0, index), ...changes.slice(index + 1)]); + + const firstStagedLine = changes[index].modifiedStartLineNumber - 1; + textEditor.selections = [new Selection(firstStagedLine, 0, firstStagedLine, 0)]; } @command('git.revertSelectedRanges', { diff: true }) @@ -1070,7 +1076,9 @@ export class CommandCenter { return; } + const selectionsBeforeRevert = textEditor.selections; await this._revertChanges(textEditor, selectedChanges); + textEditor.selections = selectionsBeforeRevert; } private async _revertChanges(textEditor: TextEditor, changes: LineChange[]): Promise { @@ -1083,7 +1091,6 @@ export class CommandCenter { const originalUri = toGitUri(modifiedUri, '~'); const originalDocument = await workspace.openTextDocument(originalUri); - const selectionsBeforeRevert = textEditor.selections; const visibleRangesBeforeRevert = textEditor.visibleRanges; const result = applyLineChanges(originalDocument, modifiedDocument, changes); @@ -1093,7 +1100,6 @@ export class CommandCenter { await modifiedDocument.save(); - textEditor.selections = selectionsBeforeRevert; textEditor.revealRange(visibleRangesBeforeRevert[0]); } @@ -1435,6 +1441,26 @@ export class CommandCenter { return false; } + if (opts.noVerify) { + if (!config.get('allowNoVerifyCommit')) { + await window.showErrorMessage(localize('no verify commit not allowed', "Commits without verification are not allowed, please enable them with the 'git.allowNoVerifyCommit' setting.")); + return false; + } + + if (config.get('confirmNoVerifyCommit')) { + const message = localize('confirm no verify commit', "You are about to commit your changes without verification, this skips pre-commit hooks and can be undesirable.\n\nAre you sure to continue?"); + const yes = localize('ok', "OK"); + const neverAgain = localize('never ask again', "OK, Don't Ask Again"); + const pick = await window.showWarningMessage(message, { modal: true }, yes, neverAgain); + + if (pick === neverAgain) { + config.update('confirmNoVerifyCommit', false, true); + } else if (pick !== yes) { + return false; + } + } + } + const message = await getCommitMessage(); if (!message) { @@ -1539,8 +1565,7 @@ export class CommandCenter { await this.commitWithAnyInput(repository, { all: true, amend: true }); } - @command('git.commitEmpty', { repository: true }) - async commitEmpty(repository: Repository): Promise { + private async _commitEmpty(repository: Repository, noVerify?: boolean): Promise { const root = Uri.file(repository.root); const config = workspace.getConfiguration('git', root); const shouldPrompt = config.get('confirmEmptyCommits') === true; @@ -1558,7 +1583,52 @@ export class CommandCenter { } } - await this.commitWithAnyInput(repository, { empty: true }); + await this.commitWithAnyInput(repository, { empty: true, noVerify }); + } + + @command('git.commitEmpty', { repository: true }) + async commitEmpty(repository: Repository): Promise { + await this._commitEmpty(repository); + } + + @command('git.commitNoVerify', { repository: true }) + async commitNoVerify(repository: Repository): Promise { + await this.commitWithAnyInput(repository, { noVerify: true }); + } + + @command('git.commitStagedNoVerify', { repository: true }) + async commitStagedNoVerify(repository: Repository): Promise { + await this.commitWithAnyInput(repository, { all: false, noVerify: true }); + } + + @command('git.commitStagedSignedNoVerify', { repository: true }) + async commitStagedSignedNoVerify(repository: Repository): Promise { + await this.commitWithAnyInput(repository, { all: false, signoff: true, noVerify: true }); + } + + @command('git.commitStagedAmendNoVerify', { repository: true }) + async commitStagedAmendNoVerify(repository: Repository): Promise { + await this.commitWithAnyInput(repository, { all: false, amend: true, noVerify: true }); + } + + @command('git.commitAllNoVerify', { repository: true }) + async commitAllNoVerify(repository: Repository): Promise { + await this.commitWithAnyInput(repository, { all: true, noVerify: true }); + } + + @command('git.commitAllSignedNoVerify', { repository: true }) + async commitAllSignedNoVerify(repository: Repository): Promise { + await this.commitWithAnyInput(repository, { all: true, signoff: true, noVerify: true }); + } + + @command('git.commitAllAmendNoVerify', { repository: true }) + async commitAllAmendNoVerify(repository: Repository): Promise { + await this.commitWithAnyInput(repository, { all: true, amend: true, noVerify: true }); + } + + @command('git.commitEmptyNoVerify', { repository: true }) + async commitEmptyNoVerify(repository: Repository): Promise { + await this._commitEmpty(repository, true); } @command('git.restoreCommitTemplate', { repository: true }) diff --git a/extensions/git/src/decorationProvider.ts b/extensions/git/src/decorationProvider.ts index 8239627b87..00e05f8bb2 100644 --- a/extensions/git/src/decorationProvider.ts +++ b/extensions/git/src/decorationProvider.ts @@ -21,7 +21,7 @@ class GitIgnoreDecorationProvider implements DecorationProvider { constructor(private model: Model) { this.onDidChangeDecorations = fireEvent(anyEvent( - filterEvent(workspace.onDidSaveTextDocument, e => e.fileName.endsWith('.gitignore')), + filterEvent(workspace.onDidSaveTextDocument, e => /\.gitignore$|\.git\/info\/exclude$/.test(e.uri.path)), model.onDidOpenRepository, model.onDidCloseRepository )); diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 938b81b2f4..5a773875f8 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -139,18 +139,28 @@ function findGitWin32(onLookup: (path: string) => void): Promise { .then(undefined, () => findGitWin32InPath(onLookup)); } -export function findGit(hint: string | undefined, onLookup: (path: string) => void): Promise { - const first = hint ? findSpecificGit(hint, onLookup) : Promise.reject(null); +export async function findGit(hint: string | string[] | undefined, onLookup: (path: string) => void): Promise { + const hints = Array.isArray(hint) ? hint : hint ? [hint] : []; - return first - .then(undefined, () => { - switch (process.platform) { - case 'darwin': return findGitDarwin(onLookup); - case 'win32': return findGitWin32(onLookup); - default: return findSpecificGit('git', onLookup); - } - }) - .then(null, () => Promise.reject(new Error('Git installation not found.'))); + for (const hint of hints) { + try { + return await findSpecificGit(hint, onLookup); + } catch { + // noop + } + } + + try { + switch (process.platform) { + case 'darwin': return await findGitDarwin(onLookup); + case 'win32': return await findGitWin32(onLookup); + default: return await findSpecificGit('git', onLookup); + } + } catch { + // noop + } + + throw new Error('Git installation not found.'); } export interface IExecutionResult { @@ -1322,10 +1332,15 @@ export class Repository { if (opts.signCommit) { args.push('-S'); } + if (opts.empty) { args.push('--allow-empty'); } + if (opts.noVerify) { + args.push('--no-verify'); + } + try { await this.run(args, { input: message || '' }); } catch (commitErr) { diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index 4804ba3c81..8199ccf054 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -33,7 +33,7 @@ export async function deactivate(): Promise { } async function createModel(context: ExtensionContext, outputChannel: OutputChannel, telemetryReporter: TelemetryReporter, disposables: Disposable[]): Promise { - const pathHint = workspace.getConfiguration('git').get('path'); + const pathHint = workspace.getConfiguration('git').get('path'); const info = await findGit(pathHint, path => outputChannel.appendLine(localize('looking', "Looking for git in: {0}", path))); const askpass = await Askpass.create(outputChannel, context.storagePath); diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 8f4e1ac6a3..3fcecc314c 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -6,7 +6,7 @@ import { workspace, WorkspaceFoldersChangeEvent, Uri, window, Event, EventEmitter, QuickPickItem, Disposable, SourceControl, SourceControlResourceGroup, TextEditor, Memento, OutputChannel, commands } from 'vscode'; import { Repository, RepositoryState } from './repository'; import { memoize, sequentialize, debounce } from './decorators'; -import { dispose, anyEvent, filterEvent, isDescendant, firstIndex, pathEquals, toDisposable, eventToPromise } from './util'; +import { dispose, anyEvent, filterEvent, isDescendant, pathEquals, toDisposable, eventToPromise } from './util'; import { Git } from './git'; import * as path from 'path'; import * as fs from 'fs'; @@ -372,7 +372,7 @@ export class Model implements IRemoteSourceProviderRegistry, IPushErrorHandlerRe const picks = this.openRepositories.map((e, index) => new RepositoryPick(e.repository, index)); const active = window.activeTextEditor; const repository = active && this.getRepository(active.document.fileName); - const index = firstIndex(picks, pick => pick.repository === repository); + const index = picks.findIndex(pick => pick.repository === repository); // Move repository pick containing the active text editor to appear first if (index > -1) { diff --git a/extensions/git/src/util.ts b/extensions/git/src/util.ts index 23e8f9f8b7..18c8d700de 100644 --- a/extensions/git/src/util.ts +++ b/extensions/git/src/util.ts @@ -182,16 +182,6 @@ export function uniqueFilter(keyFn: (t: T) => string): (t: T) => boolean { }; } -export function firstIndex(array: T[], fn: (t: T) => boolean): number { - for (let i = 0; i < array.length; i++) { - if (fn(array[i])) { - return i; - } - } - - return -1; -} - export function find(array: T[], fn: (t: T) => boolean): T | undefined { let result: T | undefined = undefined; diff --git a/extensions/github/package.nls.json b/extensions/github/package.nls.json index 28906a39f0..34e762b2f9 100644 --- a/extensions/github/package.nls.json +++ b/extensions/github/package.nls.json @@ -2,6 +2,6 @@ "displayName": "GitHub", "description": "GitHub", "config.gitAuthentication": "Controls whether to enable automatic GitHub authentication for git commands within VS Code.", - "welcome.publishFolder": "You can also directly publish this folder to a GitHub repository.\n[$(github) Publish to GitHub](command:github.publish)", - "welcome.publishWorkspaceFolder": "You can also directly publish a workspace folder to a GitHub repository.\n[$(github) Publish to GitHub](command:github.publish)" + "welcome.publishFolder": "You can also directly publish this folder to a GitHub repository. Once published, you'll have access to source control features powered by git and GitHub.\n[$(github) Publish to GitHub](command:github.publish)", + "welcome.publishWorkspaceFolder": "You can also directly publish a workspace folder to a GitHub repository. Once published, you'll have access to source control features powered by git and GitHub.\n[$(github) Publish to GitHub](command:github.publish)" } diff --git a/extensions/json-language-features/server/README.md b/extensions/json-language-features/server/README.md index d04ff913e9..a399a8d223 100644 --- a/extensions/json-language-features/server/README.md +++ b/extensions/json-language-features/server/README.md @@ -143,13 +143,35 @@ In addition to the settings, schemas associations can also be provided through a Notification: - method: 'json/schemaAssociations' -- params: `ISchemaAssociations` defined as follows +- params: `ISchemaAssociations` or `ISchemaAssociation[]` defined as follows ```ts interface ISchemaAssociations { - [pattern: string]: string[]; + /** + * An object where: + * - keys are file names or file paths (using `/` as path separator). `*` can be used as a wildcard. + * - values are an arrays of schema URIs + */ + [pattern: string]: string[]; } + +interface ISchemaAssociation { + /** + * The URI of the schema, which is also the identifier of the schema. + */ + uri: string; + + /** + * A list of file path patterns that are associated to the schema. The '*' wildcard can be used. Exclusion patterns starting with '!'. + * For example '*.schema.json', 'package.json', '!foo*.schema.json'. + * A match succeeds when there is at least one pattern matching and last matching pattern does not start with '!'. + */ + fileMatch: string[]; + +} + ``` +`ISchemaAssociations` - keys: a file names or file path (separated by `/`). `*` can be used as a wildcard. - values: An array of schema URLs diff --git a/extensions/json-language-features/server/src/jsonServer.ts b/extensions/json-language-features/server/src/jsonServer.ts index b9f6a8875e..f0f3b2460f 100644 --- a/extensions/json-language-features/server/src/jsonServer.ts +++ b/extensions/json-language-features/server/src/jsonServer.ts @@ -14,17 +14,10 @@ import { TextDocument, JSONDocument, JSONSchema, getLanguageService, DocumentLan import { getLanguageModelCache } from './languageModelCache'; import { RequestService, basename, resolvePath } from './requests'; -interface ISchemaAssociations { - [pattern: string]: string[]; -} - -interface ISchemaAssociation { - fileMatch: string[]; - uri: string; -} +type ISchemaAssociations = Record; namespace SchemaAssociationNotification { - export const type: NotificationType = new NotificationType('json/schemaAssociations'); + export const type: NotificationType = new NotificationType('json/schemaAssociations'); } namespace VSCodeContentRequest { @@ -212,7 +205,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }(); let jsonConfigurationSettings: JSONSchemaSettings[] | undefined = undefined; - let schemaAssociations: ISchemaAssociations | ISchemaAssociation[] | undefined = undefined; + let schemaAssociations: ISchemaAssociations | SchemaConfiguration[] | undefined = undefined; let formatterRegistration: Thenable | null = null; // The settings have changed. Is send on server activation as well. diff --git a/extensions/microsoft-authentication/src/AADHelper.ts b/extensions/microsoft-authentication/src/AADHelper.ts index a633ee18e0..1d084066cd 100644 --- a/extensions/microsoft-authentication/src/AADHelper.ts +++ b/extensions/microsoft-authentication/src/AADHelper.ts @@ -40,6 +40,7 @@ interface ITokenClaims { tid: string; email?: string; unique_name?: string; + preferred_username?: string; oid?: string; altsecid?: string; ipd?: string; @@ -454,7 +455,7 @@ export class AzureActiveDirectoryService { scope, sessionId: existingId || `${claims.tid}/${(claims.oid || (claims.altsecid || '' + claims.ipd || ''))}/${uuid()}`, account: { - label: claims.email || claims.unique_name || 'user@example.com', + label: claims.email || claims.unique_name || claims.preferred_username || 'user@example.com', id: `${claims.tid}/${(claims.oid || (claims.altsecid || '' + claims.ipd || ''))}` } }; diff --git a/extensions/python/package.json b/extensions/python/package.json index e7c75aa4ea..f39c3d62b2 100644 --- a/extensions/python/package.json +++ b/extensions/python/package.json @@ -15,7 +15,7 @@ "id": "python", "extensions": [ ".py", ".rpy", ".pyw", ".cpy", ".gyp", ".gypi", ".pyi", ".ipy"], "aliases": [ "Python", "py" ], - "filenames": [ "Snakefile" ], + "filenames": [ "Snakefile", "SConstruct", "SConscript" ], "firstLine": "^#!\\s*/?.*\\bpython[0-9.-]*\\b", "configuration": "./language-configuration.json" }], diff --git a/extensions/sql/cgmanifest.json b/extensions/sql/cgmanifest.json index 3ef90a09d1..45e7c7f4e7 100644 --- a/extensions/sql/cgmanifest.json +++ b/extensions/sql/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "Microsoft/vscode-mssql", "repositoryUrl": "https://github.com/Microsoft/vscode-mssql", - "commitHash": "750d30dc48c4c0317b63bb5f1ed3e71487bb84a1" + "commitHash": "61ae0eb21ac53883a23e09913a5ae77a59126ff9" } }, "license": "MIT", diff --git a/extensions/sql/syntaxes/sql.tmLanguage.json b/extensions/sql/syntaxes/sql.tmLanguage.json index 0e66dda068..389a48add7 100644 --- a/extensions/sql/syntaxes/sql.tmLanguage.json +++ b/extensions/sql/syntaxes/sql.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/Microsoft/vscode-mssql/commit/750d30dc48c4c0317b63bb5f1ed3e71487bb84a1", + "version": "https://github.com/Microsoft/vscode-mssql/commit/61ae0eb21ac53883a23e09913a5ae77a59126ff9", "name": "SQL", "scopeName": "source.sql", "patterns": [ diff --git a/extensions/theme-defaults/themes/dark_defaults.json b/extensions/theme-defaults/themes/dark_defaults.json index 54e8ece8b8..5d20a90c62 100644 --- a/extensions/theme-defaults/themes/dark_defaults.json +++ b/extensions/theme-defaults/themes/dark_defaults.json @@ -19,7 +19,8 @@ "statusBarItem.remoteForeground": "#FFF", "statusBarItem.remoteBackground": "#16825D", "sideBarSectionHeader.background": "#0000", - "sideBarSectionHeader.border": "#ccc3" + "sideBarSectionHeader.border": "#ccc3", + "tab.lastPinnedBorder": "#ccc3" }, "semanticHighlighting": true } diff --git a/extensions/theme-defaults/themes/light_defaults.json b/extensions/theme-defaults/themes/light_defaults.json index 3fad0bc14f..cbf573c0e9 100644 --- a/extensions/theme-defaults/themes/light_defaults.json +++ b/extensions/theme-defaults/themes/light_defaults.json @@ -20,6 +20,7 @@ "statusBarItem.remoteBackground": "#16825D", "sideBarSectionHeader.background": "#0000", "sideBarSectionHeader.border": "#61616130", + "tab.lastPinnedBorder": "#81818130", "notebook.focusedCellBackground": "#c8ddf150", "notebook.cellBorderColor": "#dae3e9", "notebook.outputContainerBackgroundColor": "#c8ddf150" diff --git a/extensions/vscode-notebook-tests/src/notebook.test.ts b/extensions/vscode-notebook-tests/src/notebook.test.ts new file mode 100644 index 0000000000..81087d4c7e --- /dev/null +++ b/extensions/vscode-notebook-tests/src/notebook.test.ts @@ -0,0 +1,1376 @@ +/*--------------------------------------------------------------------------------------------- + * 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 * as vscode from 'vscode'; +import { createRandomFile } from './utils'; + +export function timeoutAsync(n: number): Promise { + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, n); + }); +} + +export function once(event: vscode.Event): vscode.Event { + return (listener: any, thisArgs = null, disposables?: any) => { + // we need this, in case the event fires during the listener call + let didFire = false; + let result: vscode.Disposable; + result = event(e => { + if (didFire) { + return; + } else if (result) { + result.dispose(); + } else { + didFire = true; + } + + return listener.call(thisArgs, e); + }, null, disposables); + + if (didFire) { + result.dispose(); + } + + return result; + }; +} + +async function getEventOncePromise(event: vscode.Event): Promise { + return new Promise((resolve, _reject) => { + once(event)((result: T) => resolve(result)); + }); +} + +// Since `workbench.action.splitEditor` command does await properly +// Notebook editor/document events are not guaranteed to be sent to the ext host when promise resolves +// The workaround here is waiting for the first visible notebook editor change event. +async function splitEditor() { + const once = getEventOncePromise(vscode.notebook.onDidChangeVisibleNotebookEditors); + await vscode.commands.executeCommand('workbench.action.splitEditor'); + await once; +} + +async function saveFileAndCloseAll(resource: vscode.Uri) { + const documentClosed = new Promise((resolve, _reject) => { + const d = vscode.notebook.onDidCloseNotebookDocument(e => { + if (e.uri.toString() === resource.toString()) { + d.dispose(); + resolve(); + } + }); + }); + await vscode.commands.executeCommand('workbench.action.files.save'); + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + await documentClosed; +} + +async function saveAllFilesAndCloseAll(resource: vscode.Uri | undefined) { + const documentClosed = new Promise((resolve, _reject) => { + if (!resource) { + return resolve(); + } + const d = vscode.notebook.onDidCloseNotebookDocument(e => { + if (e.uri.toString() === resource.toString()) { + d.dispose(); + resolve(); + } + }); + }); + await vscode.commands.executeCommand('workbench.action.files.saveAll'); + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + await documentClosed; +} + +function assertInitalState() { + // no-op unless we figure out why some documents are opened after the editor is closed + + // assert.equal(vscode.notebook.activeNotebookEditor, undefined); + // assert.equal(vscode.notebook.notebookDocuments.length, 0); + // assert.equal(vscode.notebook.visibleNotebookEditors.length, 0); +} + +suite('Notebook API tests', () => { + // test.only('crash', async function () { + // for (let i = 0; i < 200; i++) { + // let resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + // await vscode.commands.executeCommand('workbench.action.revertAndCloseActiveEditor'); + + // resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './empty.vsctestnb')); + // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + // await vscode.commands.executeCommand('workbench.action.revertAndCloseActiveEditor'); + // } + // }); + + // test.only('crash', async function () { + // for (let i = 0; i < 200; i++) { + // let resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './first.vsctestnb')); + // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + // await vscode.commands.executeCommand('workbench.action.files.save'); + // await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + // resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './empty.vsctestnb')); + // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + // await vscode.commands.executeCommand('workbench.action.files.save'); + // await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + // } + // }); + + test('document open/close event', async function () { + assertInitalState(); + + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + const firstDocumentOpen = getEventOncePromise(vscode.notebook.onDidOpenNotebookDocument); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + await firstDocumentOpen; + + const firstDocumentClose = getEventOncePromise(vscode.notebook.onDidCloseNotebookDocument); + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + await firstDocumentClose; + }); + + test('notebook open/close, all cell-documents are ready', async function () { + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + + const p = getEventOncePromise(vscode.notebook.onDidOpenNotebookDocument).then(notebook => { + for (let cell of notebook.cells) { + const doc = vscode.workspace.textDocuments.find(doc => doc.uri.toString() === cell.uri.toString()); + assert.ok(doc); + assert.strictEqual(doc === cell.document, true); + assert.strictEqual(doc?.languageId, cell.language); + assert.strictEqual(doc?.isDirty, false); + assert.strictEqual(doc?.isClosed, false); + } + }); + + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + await p; + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + }); + + test('notebook open/close, notebook ready when cell-document open event is fired', async function () { + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + let didHappen = false; + const p = getEventOncePromise(vscode.workspace.onDidOpenTextDocument).then(doc => { + if (doc.uri.scheme !== 'vscode-notebook-cell') { + return; + } + const notebook = vscode.notebook.notebookDocuments.find(notebook => { + const cell = notebook.cells.find(cell => cell.document === doc); + return Boolean(cell); + }); + assert.ok(notebook, `notebook for cell ${doc.uri} NOT found`); + didHappen = true; + }); + + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + await p; + assert.strictEqual(didHappen, true); + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + }); + + test('shared document in notebook editors', async function () { + assertInitalState(); + + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + let counter = 0; + const disposables: vscode.Disposable[] = []; + disposables.push(vscode.notebook.onDidOpenNotebookDocument(() => { + counter++; + })); + disposables.push(vscode.notebook.onDidCloseNotebookDocument(() => { + counter--; + })); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.equal(counter, 1); + + await splitEditor(); + assert.equal(counter, 1); + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + assert.equal(counter, 0); + + disposables.forEach(d => d.dispose()); + }); + + test('editor open/close event', async function () { + assertInitalState(); + + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + const firstEditorOpen = getEventOncePromise(vscode.notebook.onDidChangeVisibleNotebookEditors); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + await firstEditorOpen; + + const firstEditorClose = getEventOncePromise(vscode.notebook.onDidChangeVisibleNotebookEditors); + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + await firstEditorClose; + }); + + test('editor open/close event 2', async function () { + assertInitalState(); + + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + let count = 0; + const disposables: vscode.Disposable[] = []; + disposables.push(vscode.notebook.onDidChangeVisibleNotebookEditors(() => { + count = vscode.notebook.visibleNotebookEditors.length; + })); + + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.equal(count, 1); + + await splitEditor(); + assert.equal(count, 2); + + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + assert.equal(count, 0); + }); + + test('editor editing event 2', async function () { + assertInitalState(); + + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + const cellsChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); + await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + const cellChangeEventRet = await cellsChangeEvent; + assert.equal(cellChangeEventRet.document, vscode.notebook.activeNotebookEditor?.document); + assert.equal(cellChangeEventRet.changes.length, 1); + assert.deepEqual(cellChangeEventRet.changes[0], { + start: 1, + deletedCount: 0, + deletedItems: [], + items: [ + vscode.notebook.activeNotebookEditor!.document.cells[1] + ] + }); + + const secondCell = vscode.notebook.activeNotebookEditor!.document.cells[1]; + + const moveCellEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); + await vscode.commands.executeCommand('notebook.cell.moveUp'); + const moveCellEventRet = await moveCellEvent; + assert.deepEqual(moveCellEventRet, { + document: vscode.notebook.activeNotebookEditor!.document, + changes: [ + { + start: 1, + deletedCount: 1, + deletedItems: [secondCell], + items: [] + }, + { + start: 0, + deletedCount: 0, + deletedItems: [], + items: [vscode.notebook.activeNotebookEditor?.document.cells[0]] + } + ] + }); + + const cellOutputChange = getEventOncePromise(vscode.notebook.onDidChangeCellOutputs); + await vscode.commands.executeCommand('notebook.cell.execute'); + const cellOutputsAddedRet = await cellOutputChange; + assert.deepEqual(cellOutputsAddedRet, { + document: vscode.notebook.activeNotebookEditor!.document, + cells: [vscode.notebook.activeNotebookEditor!.document.cells[0]] + }); + assert.equal(cellOutputsAddedRet.cells[0].outputs.length, 1); + + const cellOutputClear = getEventOncePromise(vscode.notebook.onDidChangeCellOutputs); + await vscode.commands.executeCommand('notebook.cell.clearOutputs'); + const cellOutputsCleardRet = await cellOutputClear; + assert.deepEqual(cellOutputsCleardRet, { + document: vscode.notebook.activeNotebookEditor!.document, + cells: [vscode.notebook.activeNotebookEditor!.document.cells[0]] + }); + assert.equal(cellOutputsAddedRet.cells[0].outputs.length, 0); + + // const cellChangeLanguage = getEventOncePromise(vscode.notebook.onDidChangeCellLanguage); + // await vscode.commands.executeCommand('notebook.cell.changeToMarkdown'); + // const cellChangeLanguageRet = await cellChangeLanguage; + // assert.deepEqual(cellChangeLanguageRet, { + // document: vscode.notebook.activeNotebookEditor!.document, + // cells: vscode.notebook.activeNotebookEditor!.document.cells[0], + // language: 'markdown' + // }); + + await vscode.commands.executeCommand('workbench.action.files.save'); + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + }); + + test('editor move cell event', async function () { + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); + await vscode.commands.executeCommand('notebook.focusTop'); + + const activeCell = vscode.notebook.activeNotebookEditor!.selection; + assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.indexOf(activeCell!), 0); + const moveChange = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); + await vscode.commands.executeCommand('notebook.cell.moveDown'); + const ret = await moveChange; + assert.deepEqual(ret, { + document: vscode.notebook.activeNotebookEditor?.document, + changes: [ + { + start: 0, + deletedCount: 1, + deletedItems: [activeCell], + items: [] + }, + { + start: 1, + deletedCount: 0, + deletedItems: [], + items: [activeCell] + } + ] + }); + + await vscode.commands.executeCommand('workbench.action.files.save'); + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + const firstEditor = vscode.notebook.activeNotebookEditor; + assert.equal(firstEditor?.document.cells.length, 1); + + await vscode.commands.executeCommand('workbench.action.files.save'); + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + }); + + test('notebook editor active/visible', async function () { + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + const firstEditor = vscode.notebook.activeNotebookEditor; + assert.equal(firstEditor?.active, true); + assert.equal(firstEditor?.visible, true); + + await splitEditor(); + const secondEditor = vscode.notebook.activeNotebookEditor; + assert.equal(secondEditor?.active, true); + assert.equal(secondEditor?.visible, true); + assert.equal(firstEditor?.active, false); + + assert.equal(vscode.notebook.visibleNotebookEditors.length, 2); + + const untitledEditorChange = getEventOncePromise(vscode.notebook.onDidChangeActiveNotebookEditor); + await vscode.commands.executeCommand('workbench.action.files.newUntitledFile'); + await untitledEditorChange; + assert.equal(firstEditor?.visible, true); + assert.equal(firstEditor?.active, false); + assert.equal(secondEditor?.visible, false); + assert.equal(secondEditor?.active, false); + assert.equal(vscode.notebook.visibleNotebookEditors.length, 1); + + const activeEditorClose = getEventOncePromise(vscode.notebook.onDidChangeActiveNotebookEditor); + await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + await activeEditorClose; + assert.equal(secondEditor?.active, true); + assert.equal(secondEditor?.visible, true); + assert.equal(vscode.notebook.visibleNotebookEditors.length, 2); + + await vscode.commands.executeCommand('workbench.action.files.save'); + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + }); + + test('notebook active editor change', async function () { + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + const firstEditorOpen = getEventOncePromise(vscode.notebook.onDidChangeActiveNotebookEditor); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + await firstEditorOpen; + + const firstEditorDeactivate = getEventOncePromise(vscode.notebook.onDidChangeActiveNotebookEditor); + await vscode.commands.executeCommand('workbench.action.splitEditor'); + await firstEditorDeactivate; + + await saveFileAndCloseAll(resource); + }); + + test('edit API (replaceCells)', async function () { + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + const cellsChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); + await vscode.notebook.activeNotebookEditor!.edit(editBuilder => { + editBuilder.replaceCells(1, 0, [{ cellKind: vscode.CellKind.Code, language: 'javascript', source: 'test 2', outputs: [], metadata: undefined }]); + }); + + const cellChangeEventRet = await cellsChangeEvent; + assert.strictEqual(cellChangeEventRet.document === vscode.notebook.activeNotebookEditor?.document, true); + assert.strictEqual(cellChangeEventRet.document.isDirty, true); + assert.strictEqual(cellChangeEventRet.changes.length, 1); + assert.strictEqual(cellChangeEventRet.changes[0].start, 1); + assert.strictEqual(cellChangeEventRet.changes[0].deletedCount, 0); + assert.strictEqual(cellChangeEventRet.changes[0].items[0] === vscode.notebook.activeNotebookEditor!.document.cells[1], true); + + await saveAllFilesAndCloseAll(resource); + }); + + test('edit API (replaceOutput)', async function () { + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + await vscode.notebook.activeNotebookEditor!.edit(editBuilder => { + editBuilder.replaceCellOutput(0, [{ outputKind: vscode.CellOutputKind.Rich, data: { foo: 'bar' } }]); + }); + + const document = vscode.notebook.activeNotebookEditor?.document!; + assert.strictEqual(document.isDirty, true); + assert.strictEqual(document.cells.length, 1); + assert.strictEqual(document.cells[0].outputs.length, 1); + assert.strictEqual(document.cells[0].outputs[0].outputKind, vscode.CellOutputKind.Rich); + + await saveAllFilesAndCloseAll(undefined); + }); + + test('edit API (replaceOutput, event)', async function () { + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + const outputChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeCellOutputs); + await vscode.notebook.activeNotebookEditor!.edit(editBuilder => { + editBuilder.replaceCellOutput(0, [{ outputKind: vscode.CellOutputKind.Rich, data: { foo: 'bar' } }]); + }); + + const value = await outputChangeEvent; + assert.strictEqual(value.document === vscode.notebook.activeNotebookEditor?.document, true); + assert.strictEqual(value.document.isDirty, true); + assert.strictEqual(value.cells.length, 1); + assert.strictEqual(value.cells[0].outputs.length, 1); + assert.strictEqual(value.cells[0].outputs[0].outputKind, vscode.CellOutputKind.Rich); + + await saveAllFilesAndCloseAll(undefined); + }); + + test('edit API (replaceMetadata)', async function () { + + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + await vscode.notebook.activeNotebookEditor!.edit(editBuilder => { + editBuilder.replaceCellMetadata(0, { inputCollapsed: true, executionOrder: 17 }); + }); + + const document = vscode.notebook.activeNotebookEditor?.document!; + assert.strictEqual(document.cells.length, 1); + assert.strictEqual(document.cells[0].metadata.executionOrder, 17); + assert.strictEqual(document.cells[0].metadata.inputCollapsed, true); + + assert.strictEqual(document.isDirty, true); + await saveFileAndCloseAll(resource); + }); + + test('edit API (replaceMetadata, event)', async function () { + + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + const event = getEventOncePromise(vscode.notebook.onDidChangeCellMetadata); + + await vscode.notebook.activeNotebookEditor!.edit(editBuilder => { + editBuilder.replaceCellMetadata(0, { inputCollapsed: true, executionOrder: 17 }); + }); + + const data = await event; + assert.strictEqual(data.document, vscode.notebook.activeNotebookEditor?.document); + assert.strictEqual(data.cell.metadata.executionOrder, 17); + assert.strictEqual(data.cell.metadata.inputCollapsed, true); + + assert.strictEqual(data.document.isDirty, true); + await saveFileAndCloseAll(resource); + }); + + test('workspace edit API (replaceCells)', async function () { + + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + const { document } = vscode.notebook.activeNotebookEditor!; + assert.strictEqual(document.cells.length, 1); + + // inserting two new cells + { + const edit = new vscode.WorkspaceEdit(); + edit.replaceNotebookCells(document.uri, 0, 0, [{ + cellKind: vscode.CellKind.Markdown, + language: 'markdown', + metadata: undefined, + outputs: [], + source: 'new_markdown' + }, { + cellKind: vscode.CellKind.Code, + language: 'fooLang', + metadata: undefined, + outputs: [], + source: 'new_code' + }]); + + const success = await vscode.workspace.applyEdit(edit); + assert.strictEqual(success, true); + } + + assert.strictEqual(document.cells.length, 3); + assert.strictEqual(document.cells[0].document.getText(), 'new_markdown'); + assert.strictEqual(document.cells[1].document.getText(), 'new_code'); + + // deleting cell 1 and 3 + { + const edit = new vscode.WorkspaceEdit(); + edit.replaceNotebookCells(document.uri, 0, 1, []); + edit.replaceNotebookCells(document.uri, 2, 3, []); + const success = await vscode.workspace.applyEdit(edit); + assert.strictEqual(success, true); + } + + assert.strictEqual(document.cells.length, 1); + assert.strictEqual(document.cells[0].document.getText(), 'new_code'); + + // replacing all cells + { + const edit = new vscode.WorkspaceEdit(); + edit.replaceNotebookCells(document.uri, 0, 1, [{ + cellKind: vscode.CellKind.Markdown, + language: 'markdown', + metadata: undefined, + outputs: [], + source: 'new2_markdown' + }, { + cellKind: vscode.CellKind.Code, + language: 'fooLang', + metadata: undefined, + outputs: [], + source: 'new2_code' + }]); + const success = await vscode.workspace.applyEdit(edit); + assert.strictEqual(success, true); + } + assert.strictEqual(document.cells.length, 2); + assert.strictEqual(document.cells[0].document.getText(), 'new2_markdown'); + assert.strictEqual(document.cells[1].document.getText(), 'new2_code'); + + // remove all cells + { + const edit = new vscode.WorkspaceEdit(); + edit.replaceNotebookCells(document.uri, 0, document.cells.length, []); + const success = await vscode.workspace.applyEdit(edit); + assert.strictEqual(success, true); + } + assert.strictEqual(document.cells.length, 0); + + await saveFileAndCloseAll(resource); + }); + + test('workspace edit API (replaceCells, event)', async function () { + + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + const { document } = vscode.notebook.activeNotebookEditor!; + assert.strictEqual(document.cells.length, 1); + + const edit = new vscode.WorkspaceEdit(); + edit.replaceNotebookCells(document.uri, 0, 0, [{ + cellKind: vscode.CellKind.Markdown, + language: 'markdown', + metadata: undefined, + outputs: [], + source: 'new_markdown' + }, { + cellKind: vscode.CellKind.Code, + language: 'fooLang', + metadata: undefined, + outputs: [], + source: 'new_code' + }]); + + const event = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); + + const success = await vscode.workspace.applyEdit(edit); + assert.strictEqual(success, true); + + const data = await event; + + // check document + assert.strictEqual(document.cells.length, 3); + assert.strictEqual(document.cells[0].document.getText(), 'new_markdown'); + assert.strictEqual(document.cells[1].document.getText(), 'new_code'); + + // check event data + assert.strictEqual(data.document === document, true); + assert.strictEqual(data.changes.length, 1); + assert.strictEqual(data.changes[0].deletedCount, 0); + assert.strictEqual(data.changes[0].deletedItems.length, 0); + assert.strictEqual(data.changes[0].items.length, 2); + assert.strictEqual(data.changes[0].items[0], document.cells[0]); + assert.strictEqual(data.changes[0].items[1], document.cells[1]); + await saveFileAndCloseAll(resource); + }); + + test('edit API batch edits', async function () { + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + const cellsChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); + const cellMetadataChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeCellMetadata); + const version = vscode.notebook.activeNotebookEditor!.document.version; + await vscode.notebook.activeNotebookEditor!.edit(editBuilder => { + editBuilder.replaceCells(1, 0, [{ cellKind: vscode.CellKind.Code, language: 'javascript', source: 'test 2', outputs: [], metadata: undefined }]); + editBuilder.replaceCellMetadata(0, { runnable: false }); + }); + + await cellsChangeEvent; + await cellMetadataChangeEvent; + assert.strictEqual(version + 1, vscode.notebook.activeNotebookEditor!.document.version); + await saveAllFilesAndCloseAll(resource); + }); + + test('edit API batch edits undo/redo', async function () { + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + const cellsChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); + const cellMetadataChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeCellMetadata); + const version = vscode.notebook.activeNotebookEditor!.document.version; + await vscode.notebook.activeNotebookEditor!.edit(editBuilder => { + editBuilder.replaceCells(1, 0, [{ cellKind: vscode.CellKind.Code, language: 'javascript', source: 'test 2', outputs: [], metadata: undefined }]); + editBuilder.replaceCellMetadata(0, { runnable: false }); + }); + + await cellsChangeEvent; + await cellMetadataChangeEvent; + assert.strictEqual(vscode.notebook.activeNotebookEditor!.document.cells.length, 2); + assert.strictEqual(vscode.notebook.activeNotebookEditor!.document.cells[0]?.metadata?.runnable, false); + assert.strictEqual(version + 1, vscode.notebook.activeNotebookEditor!.document.version); + + await vscode.commands.executeCommand('undo'); + assert.strictEqual(version + 2, vscode.notebook.activeNotebookEditor!.document.version); + assert.strictEqual(vscode.notebook.activeNotebookEditor!.document.cells[0]?.metadata?.runnable, undefined); + assert.strictEqual(vscode.notebook.activeNotebookEditor!.document.cells.length, 1); + + await saveAllFilesAndCloseAll(resource); + }); + + test('initialzation should not emit cell change events.', async function () { + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + + let count = 0; + const disposables: vscode.Disposable[] = []; + disposables.push(vscode.notebook.onDidChangeNotebookCells(() => { + count++; + })); + + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.equal(count, 0); + + disposables.forEach(d => d.dispose()); + + await saveFileAndCloseAll(resource); + }); +}); + +suite('notebook workflow', () => { + test('notebook open', async function () { + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); + assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), 'test'); + assert.equal(vscode.notebook.activeNotebookEditor!.selection?.language, 'typescript'); + + await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), ''); + + await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); + const activeCell = vscode.notebook.activeNotebookEditor!.selection; + assert.notEqual(vscode.notebook.activeNotebookEditor!.selection, undefined); + assert.equal(activeCell!.document.getText(), ''); + assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.length, 3); + assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); + + await vscode.commands.executeCommand('workbench.action.files.save'); + await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + }); + + test('notebook cell actions', async function () { + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); + assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), 'test'); + assert.equal(vscode.notebook.activeNotebookEditor!.selection?.language, 'typescript'); + + // ---- insert cell below and focus ---- // + await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), ''); + + // ---- insert cell above and focus ---- // + await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); + let activeCell = vscode.notebook.activeNotebookEditor!.selection; + assert.notEqual(vscode.notebook.activeNotebookEditor!.selection, undefined); + assert.equal(activeCell!.document.getText(), ''); + assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.length, 3); + assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); + + // ---- focus bottom ---- // + await vscode.commands.executeCommand('notebook.focusBottom'); + activeCell = vscode.notebook.activeNotebookEditor!.selection; + assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.indexOf(activeCell!), 2); + + // ---- focus top and then copy down ---- // + await vscode.commands.executeCommand('notebook.focusTop'); + activeCell = vscode.notebook.activeNotebookEditor!.selection; + assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.indexOf(activeCell!), 0); + + await vscode.commands.executeCommand('notebook.cell.copyDown'); + activeCell = vscode.notebook.activeNotebookEditor!.selection; + assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); + assert.equal(activeCell?.document.getText(), 'test'); + + await vscode.commands.executeCommand('notebook.cell.delete'); + activeCell = vscode.notebook.activeNotebookEditor!.selection; + assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); + assert.equal(activeCell?.document.getText(), ''); + + // ---- focus top and then copy up ---- // + await vscode.commands.executeCommand('notebook.focusTop'); + await vscode.commands.executeCommand('notebook.cell.copyUp'); + assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.length, 4); + assert.equal(vscode.notebook.activeNotebookEditor!.document.cells[0].document.getText(), 'test'); + assert.equal(vscode.notebook.activeNotebookEditor!.document.cells[1].document.getText(), 'test'); + assert.equal(vscode.notebook.activeNotebookEditor!.document.cells[2].document.getText(), ''); + assert.equal(vscode.notebook.activeNotebookEditor!.document.cells[3].document.getText(), ''); + activeCell = vscode.notebook.activeNotebookEditor!.selection; + assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.indexOf(activeCell!), 0); + + + // ---- move up and down ---- // + + await vscode.commands.executeCommand('notebook.cell.moveDown'); + assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.indexOf(vscode.notebook.activeNotebookEditor!.selection!), 1, + `first move down, active cell ${vscode.notebook.activeNotebookEditor!.selection!.uri.toString()}, ${vscode.notebook.activeNotebookEditor!.selection!.document.getText()}`); + + // await vscode.commands.executeCommand('notebook.cell.moveDown'); + // activeCell = vscode.notebook.activeNotebookEditor!.selection; + + // assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.indexOf(activeCell!), 2, + // `second move down, active cell ${vscode.notebook.activeNotebookEditor!.selection!.uri.toString()}, ${vscode.notebook.activeNotebookEditor!.selection!.document.getText()}`); + // assert.equal(vscode.notebook.activeNotebookEditor!.document.cells[0].document.getText(), 'test'); + // assert.equal(vscode.notebook.activeNotebookEditor!.document.cells[1].document.getText(), ''); + // assert.equal(vscode.notebook.activeNotebookEditor!.document.cells[2].document.getText(), 'test'); + // assert.equal(vscode.notebook.activeNotebookEditor!.document.cells[3].document.getText(), ''); + + // ---- ---- // + + await vscode.commands.executeCommand('workbench.action.files.save'); + await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + }); + + test('notebook join cells', async function () { + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); + assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), 'test'); + assert.equal(vscode.notebook.activeNotebookEditor!.selection?.language, 'typescript'); + + await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), ''); + const edit = new vscode.WorkspaceEdit(); + edit.insert(vscode.notebook.activeNotebookEditor!.selection!.uri, new vscode.Position(0, 0), 'var abc = 0;'); + await vscode.workspace.applyEdit(edit); + + const cellsChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); + await vscode.commands.executeCommand('notebook.cell.joinAbove'); + await cellsChangeEvent; + + assert.deepEqual(vscode.notebook.activeNotebookEditor!.selection?.document.getText().split(/\r\n|\r|\n/), ['test', 'var abc = 0;']); + + await vscode.commands.executeCommand('workbench.action.files.save'); + await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + }); + + test('move cells will not recreate cells in ExtHost', async function () { + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); + await vscode.commands.executeCommand('notebook.focusTop'); + + const activeCell = vscode.notebook.activeNotebookEditor!.selection; + assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.indexOf(activeCell!), 0); + await vscode.commands.executeCommand('notebook.cell.moveDown'); + await vscode.commands.executeCommand('notebook.cell.moveDown'); + + const newActiveCell = vscode.notebook.activeNotebookEditor!.selection; + assert.deepEqual(activeCell, newActiveCell); + + await saveFileAndCloseAll(resource); + // TODO@rebornix, there are still some events order issue. + // assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.indexOf(newActiveCell!), 2); + }); + + // test.only('document metadata is respected', async function () { + // const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + // assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); + // const editor = vscode.notebook.activeNotebookEditor!; + + // assert.equal(editor.document.cells.length, 1); + // editor.document.metadata.editable = false; + // await editor.edit(builder => builder.delete(0)); + // assert.equal(editor.document.cells.length, 1, 'should not delete cell'); // Not editable, no effect + // await editor.edit(builder => builder.insert(0, 'test', 'python', vscode.CellKind.Code, [], undefined)); + // assert.equal(editor.document.cells.length, 1, 'should not insert cell'); // Not editable, no effect + + // editor.document.metadata.editable = true; + // await editor.edit(builder => builder.delete(0)); + // assert.equal(editor.document.cells.length, 0, 'should delete cell'); // Editable, it worked + // await editor.edit(builder => builder.insert(0, 'test', 'python', vscode.CellKind.Code, [], undefined)); + // assert.equal(editor.document.cells.length, 1, 'should insert cell'); // Editable, it worked + + // // await vscode.commands.executeCommand('workbench.action.files.save'); + // await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + // }); + + test('cell runnable metadata is respected', async () => { + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); + const editor = vscode.notebook.activeNotebookEditor!; + + await vscode.commands.executeCommand('notebook.focusTop'); + const cell = editor.document.cells[0]; + assert.equal(cell.outputs.length, 0); + + let metadataChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeCellMetadata); + cell.metadata.runnable = false; + await metadataChangeEvent; + + await vscode.commands.executeCommand('notebook.cell.execute'); + assert.equal(cell.outputs.length, 0, 'should not execute'); // not runnable, didn't work + + metadataChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeCellMetadata); + cell.metadata.runnable = true; + await metadataChangeEvent; + + await vscode.commands.executeCommand('notebook.cell.execute'); + assert.equal(cell.outputs.length, 1, 'should execute'); // runnable, it worked + + await vscode.commands.executeCommand('workbench.action.files.save'); + await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + }); + + test('document runnable metadata is respected', async () => { + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); + const editor = vscode.notebook.activeNotebookEditor!; + + const cell = editor.document.cells[0]; + assert.equal(cell.outputs.length, 0); + let metadataChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookDocumentMetadata); + editor.document.metadata.runnable = false; + await metadataChangeEvent; + + await vscode.commands.executeCommand('notebook.execute'); + assert.equal(cell.outputs.length, 0, 'should not execute'); // not runnable, didn't work + + metadataChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookDocumentMetadata); + editor.document.metadata.runnable = true; + await metadataChangeEvent; + + await vscode.commands.executeCommand('notebook.execute'); + assert.equal(cell.outputs.length, 1, 'should execute'); // runnable, it worked + + await vscode.commands.executeCommand('workbench.action.files.save'); + await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + }); +}); + +suite('notebook dirty state', () => { + test('notebook open', async function () { + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); + assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), 'test'); + assert.equal(vscode.notebook.activeNotebookEditor!.selection?.language, 'typescript'); + + await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), ''); + + await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); + const activeCell = vscode.notebook.activeNotebookEditor!.selection; + assert.notEqual(vscode.notebook.activeNotebookEditor!.selection, undefined); + assert.equal(activeCell!.document.getText(), ''); + assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.length, 3); + assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); + + const edit = new vscode.WorkspaceEdit(); + edit.insert(activeCell!.uri, new vscode.Position(0, 0), 'var abc = 0;'); + await vscode.workspace.applyEdit(edit); + assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true); + assert.equal(vscode.notebook.activeNotebookEditor?.selection !== undefined, true); + assert.deepEqual(vscode.notebook.activeNotebookEditor?.document.cells[1], vscode.notebook.activeNotebookEditor?.selection); + assert.equal(vscode.notebook.activeNotebookEditor?.selection?.document.getText(), 'var abc = 0;'); + + await saveFileAndCloseAll(resource); + }); +}); + +suite('notebook undo redo', () => { + test('notebook open', async function () { + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); + assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), 'test'); + assert.equal(vscode.notebook.activeNotebookEditor!.selection?.language, 'typescript'); + + await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), ''); + + await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); + const activeCell = vscode.notebook.activeNotebookEditor!.selection; + assert.notEqual(vscode.notebook.activeNotebookEditor!.selection, undefined); + assert.equal(activeCell!.document.getText(), ''); + assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.length, 3); + assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); + + + // modify the second cell, delete it + const edit = new vscode.WorkspaceEdit(); + edit.insert(vscode.notebook.activeNotebookEditor!.selection!.uri, new vscode.Position(0, 0), 'var abc = 0;'); + await vscode.workspace.applyEdit(edit); + await vscode.commands.executeCommand('notebook.cell.delete'); + assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.length, 2); + assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.indexOf(vscode.notebook.activeNotebookEditor!.selection!), 1); + + + // undo should bring back the deleted cell, and revert to previous content and selection + await vscode.commands.executeCommand('undo'); + assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.length, 3); + assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.indexOf(vscode.notebook.activeNotebookEditor!.selection!), 1); + assert.equal(vscode.notebook.activeNotebookEditor?.selection?.document.getText(), 'var abc = 0;'); + + // redo + // await vscode.commands.executeCommand('notebook.redo'); + // assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.length, 2); + // assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.indexOf(vscode.notebook.activeNotebookEditor!.selection!), 1); + // assert.equal(vscode.notebook.activeNotebookEditor?.selection?.document.getText(), 'test'); + + await saveFileAndCloseAll(resource); + }); + + test.skip('execute and then undo redo', async function () { + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + const cellsChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); + await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + const cellChangeEventRet = await cellsChangeEvent; + assert.equal(cellChangeEventRet.document, vscode.notebook.activeNotebookEditor?.document); + assert.equal(cellChangeEventRet.changes.length, 1); + assert.deepEqual(cellChangeEventRet.changes[0], { + start: 1, + deletedCount: 0, + deletedItems: [], + items: [ + vscode.notebook.activeNotebookEditor!.document.cells[1] + ] + }); + + const secondCell = vscode.notebook.activeNotebookEditor!.document.cells[1]; + + const moveCellEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); + await vscode.commands.executeCommand('notebook.cell.moveUp'); + const moveCellEventRet = await moveCellEvent; + assert.deepEqual(moveCellEventRet, { + document: vscode.notebook.activeNotebookEditor!.document, + changes: [ + { + start: 1, + deletedCount: 1, + deletedItems: [secondCell], + items: [] + }, + { + start: 0, + deletedCount: 0, + deletedItems: [], + items: [vscode.notebook.activeNotebookEditor?.document.cells[0]] + } + ] + }); + + const cellOutputChange = getEventOncePromise(vscode.notebook.onDidChangeCellOutputs); + await vscode.commands.executeCommand('notebook.cell.execute'); + const cellOutputsAddedRet = await cellOutputChange; + assert.deepEqual(cellOutputsAddedRet, { + document: vscode.notebook.activeNotebookEditor!.document, + cells: [vscode.notebook.activeNotebookEditor!.document.cells[0]] + }); + assert.equal(cellOutputsAddedRet.cells[0].outputs.length, 1); + + const cellOutputClear = getEventOncePromise(vscode.notebook.onDidChangeCellOutputs); + await vscode.commands.executeCommand('undo'); + const cellOutputsCleardRet = await cellOutputClear; + assert.deepEqual(cellOutputsCleardRet, { + document: vscode.notebook.activeNotebookEditor!.document, + cells: [vscode.notebook.activeNotebookEditor!.document.cells[0]] + }); + assert.equal(cellOutputsAddedRet.cells[0].outputs.length, 0); + + await saveFileAndCloseAll(resource); + }); + +}); + +suite('notebook working copy', () => { + // test('notebook revert on close', async function () { + // const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + // await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + // assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), ''); + + // await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); + // await vscode.commands.executeCommand('default:type', { text: 'var abc = 0;' }); + + // // close active editor from command will revert the file + // await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + // assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true); + // assert.equal(vscode.notebook.activeNotebookEditor?.selection !== undefined, true); + // assert.deepEqual(vscode.notebook.activeNotebookEditor?.document.cells[0], vscode.notebook.activeNotebookEditor?.selection); + // assert.equal(vscode.notebook.activeNotebookEditor?.selection?.document.getText(), 'test'); + + // await vscode.commands.executeCommand('workbench.action.files.save'); + // await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + // }); + + // test('notebook revert', async function () { + // const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + // await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + // assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), ''); + + // await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); + // await vscode.commands.executeCommand('default:type', { text: 'var abc = 0;' }); + // await vscode.commands.executeCommand('workbench.action.files.revert'); + + // assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true); + // assert.equal(vscode.notebook.activeNotebookEditor?.selection !== undefined, true); + // assert.deepEqual(vscode.notebook.activeNotebookEditor?.document.cells[0], vscode.notebook.activeNotebookEditor?.selection); + // assert.deepEqual(vscode.notebook.activeNotebookEditor?.document.cells.length, 1); + // assert.equal(vscode.notebook.activeNotebookEditor?.selection?.document.getText(), 'test'); + + // await vscode.commands.executeCommand('workbench.action.files.saveAll'); + // await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + // }); + + test('multiple tabs: dirty + clean', async function () { + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), ''); + + await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); + const edit = new vscode.WorkspaceEdit(); + edit.insert(vscode.notebook.activeNotebookEditor!.selection!.uri, new vscode.Position(0, 0), 'var abc = 0;'); + await vscode.workspace.applyEdit(edit); + + const secondResource = await createRandomFile('', undefined, 'second', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); + await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + + // make sure that the previous dirty editor is still restored in the extension host and no data loss + assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true); + assert.equal(vscode.notebook.activeNotebookEditor?.selection !== undefined, true); + assert.deepEqual(vscode.notebook.activeNotebookEditor?.document.cells[1], vscode.notebook.activeNotebookEditor?.selection); + assert.deepEqual(vscode.notebook.activeNotebookEditor?.document.cells.length, 3); + assert.equal(vscode.notebook.activeNotebookEditor?.selection?.document.getText(), 'var abc = 0;'); + + await saveFileAndCloseAll(resource); + }); + + test('multiple tabs: two dirty tabs and switching', async function () { + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), ''); + + await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); + const edit = new vscode.WorkspaceEdit(); + edit.insert(vscode.notebook.activeNotebookEditor!.selection!.uri, new vscode.Position(0, 0), 'var abc = 0;'); + await vscode.workspace.applyEdit(edit); + + const secondResource = await createRandomFile('', undefined, 'second', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); + await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), ''); + + // switch to the first editor + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true); + assert.equal(vscode.notebook.activeNotebookEditor?.selection !== undefined, true); + assert.deepEqual(vscode.notebook.activeNotebookEditor?.document.cells[1], vscode.notebook.activeNotebookEditor?.selection); + assert.deepEqual(vscode.notebook.activeNotebookEditor?.document.cells.length, 3); + assert.equal(vscode.notebook.activeNotebookEditor?.selection?.document.getText(), 'var abc = 0;'); + + // switch to the second editor + await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); + assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true); + assert.equal(vscode.notebook.activeNotebookEditor?.selection !== undefined, true); + assert.deepEqual(vscode.notebook.activeNotebookEditor?.document.cells[1], vscode.notebook.activeNotebookEditor?.selection); + assert.deepEqual(vscode.notebook.activeNotebookEditor?.document.cells.length, 2); + assert.equal(vscode.notebook.activeNotebookEditor?.selection?.document.getText(), ''); + + await saveAllFilesAndCloseAll(secondResource); + // await vscode.commands.executeCommand('workbench.action.files.saveAll'); + // await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + }); + + test('multiple tabs: different editors with same document', async function () { + assertInitalState(); + + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + const firstNotebookEditor = vscode.notebook.activeNotebookEditor; + assert.equal(firstNotebookEditor !== undefined, true, 'notebook first'); + assert.equal(firstNotebookEditor!.selection?.document.getText(), 'test'); + assert.equal(firstNotebookEditor!.selection?.language, 'typescript'); + + await splitEditor(); + const secondNotebookEditor = vscode.notebook.activeNotebookEditor; + assert.equal(secondNotebookEditor !== undefined, true, 'notebook first'); + assert.equal(secondNotebookEditor!.selection?.document.getText(), 'test'); + assert.equal(secondNotebookEditor!.selection?.language, 'typescript'); + + assert.notEqual(firstNotebookEditor, secondNotebookEditor); + assert.equal(firstNotebookEditor?.document, secondNotebookEditor?.document, 'split notebook editors share the same document'); + assert.notEqual(firstNotebookEditor?.asWebviewUri(vscode.Uri.file('./hello.png')), secondNotebookEditor?.asWebviewUri(vscode.Uri.file('./hello.png'))); + + await saveAllFilesAndCloseAll(resource); + + // await vscode.commands.executeCommand('workbench.action.files.saveAll'); + // await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + }); +}); + +suite('metadata', () => { + test('custom metadata should be supported', async function () { + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); + assert.equal(vscode.notebook.activeNotebookEditor!.document.metadata.custom!['testMetadata'] as boolean, false); + assert.equal(vscode.notebook.activeNotebookEditor!.selection?.metadata.custom!['testCellMetadata'] as number, 123); + assert.equal(vscode.notebook.activeNotebookEditor!.selection?.language, 'typescript'); + + await saveFileAndCloseAll(resource); + }); + + + // TODO@rebornix skip as it crashes the process all the time + test.skip('custom metadata should be supported 2', async function () { + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); + assert.equal(vscode.notebook.activeNotebookEditor!.document.metadata.custom!['testMetadata'] as boolean, false); + assert.equal(vscode.notebook.activeNotebookEditor!.selection?.metadata.custom!['testCellMetadata'] as number, 123); + assert.equal(vscode.notebook.activeNotebookEditor!.selection?.language, 'typescript'); + + // TODO see #101462 + // await vscode.commands.executeCommand('notebook.cell.copyDown'); + // const activeCell = vscode.notebook.activeNotebookEditor!.selection; + // assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); + // assert.equal(activeCell?.metadata.custom!['testCellMetadata'] as number, 123); + + await saveFileAndCloseAll(resource); + }); +}); + +suite('regression', () => { + // test('microsoft/vscode-github-issue-notebooks#26. Insert template cell in the new empty document', async function () { + // assertInitalState(); + // await vscode.commands.executeCommand('workbench.action.files.newUntitledFile', { "viewType": "notebookCoreTest" }); + // assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); + // assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), ''); + // assert.equal(vscode.notebook.activeNotebookEditor!.selection?.language, 'typescript'); + // await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + // }); + + test('#106657. Opening a notebook from markers view is broken ', async function () { + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + const document = vscode.notebook.activeNotebookEditor?.document!; + const [cell] = document.cells; + + await saveAllFilesAndCloseAll(document.uri); + assert.strictEqual(vscode.notebook.activeNotebookEditor, undefined); + + // opening a cell-uri opens a notebook editor + await vscode.commands.executeCommand('vscode.open', cell.uri, vscode.ViewColumn.Active); + + assert.strictEqual(!!vscode.notebook.activeNotebookEditor, true); + assert.strictEqual(vscode.notebook.activeNotebookEditor?.document.uri.toString(), resource.toString()); + }); + + test('Cannot open notebook from cell-uri with vscode.open-command', async function () { + this.skip(); + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + const document = vscode.notebook.activeNotebookEditor?.document!; + const [cell] = document.cells; + + await saveAllFilesAndCloseAll(document.uri); + assert.strictEqual(vscode.notebook.activeNotebookEditor, undefined); + + // BUG is that the editor opener (https://github.com/microsoft/vscode/blob/8e7877bdc442f1e83a7fec51920d82b696139129/src/vs/editor/browser/services/openerService.ts#L69) + // removes the fragment if it matches something numeric. For notebooks that's not wanted... + await vscode.commands.executeCommand('vscode.open', cell.uri); + + assert.strictEqual(vscode.notebook.activeNotebookEditor?.document.uri.toString(), resource.toString()); + }); + + test('#97830, #97764. Support switch to other editor types', async function () { + assertInitalState(); + const resource = await createRandomFile('', undefined, 'empty', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + const edit = new vscode.WorkspaceEdit(); + edit.insert(vscode.notebook.activeNotebookEditor!.selection!.uri, new vscode.Position(0, 0), 'var abc = 0;'); + await vscode.workspace.applyEdit(edit); + + assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); + assert.equal(vscode.notebook.activeNotebookEditor!.selection?.document.getText(), 'var abc = 0;'); + assert.equal(vscode.notebook.activeNotebookEditor!.selection?.language, 'typescript'); + + await vscode.commands.executeCommand('vscode.openWith', resource, 'default'); + assert.equal(vscode.window.activeTextEditor?.document.uri.path, resource.path); + + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + }); + + // open text editor, pin, and then open a notebook + test('#96105 - dirty editors', async function () { + assertInitalState(); + const resource = await createRandomFile('', undefined, 'empty', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'default'); + const edit = new vscode.WorkspaceEdit(); + edit.insert(resource, new vscode.Position(0, 0), 'var abc = 0;'); + await vscode.workspace.applyEdit(edit); + + // now it's dirty, open the resource with notebook editor should open a new one + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.notEqual(vscode.notebook.activeNotebookEditor, undefined, 'notebook first'); + // assert.notEqual(vscode.window.activeTextEditor, undefined); + + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + }); + + test('#102411 - untitled notebook creation failed', async function () { + assertInitalState(); + await vscode.commands.executeCommand('workbench.action.files.newUntitledFile', { viewType: 'notebookCoreTest' }); + assert.notEqual(vscode.notebook.activeNotebookEditor, undefined, 'untitled notebook editor is not undefined'); + + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + }); + + test('#102423 - copy/paste shares the same text buffer', async function () { + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + let activeCell = vscode.notebook.activeNotebookEditor!.selection; + assert.equal(activeCell?.document.getText(), 'test'); + + await vscode.commands.executeCommand('notebook.cell.copyDown'); + await vscode.commands.executeCommand('notebook.cell.edit'); + activeCell = vscode.notebook.activeNotebookEditor!.selection; + assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); + assert.equal(activeCell?.document.getText(), 'test'); + + const edit = new vscode.WorkspaceEdit(); + edit.insert(vscode.notebook.activeNotebookEditor!.selection!.uri, new vscode.Position(0, 0), 'var abc = 0;'); + await vscode.workspace.applyEdit(edit); + + assert.equal(vscode.notebook.activeNotebookEditor!.document.cells.length, 2); + assert.notEqual(vscode.notebook.activeNotebookEditor!.document.cells[0].document.getText(), vscode.notebook.activeNotebookEditor!.document.cells[1].document.getText()); + + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + }); +}); + +suite('webview', () => { + // for web, `asWebUri` gets `https`? + // test('asWebviewUri', async function () { + // if (vscode.env.uiKind === vscode.UIKind.Web) { + // return; + // } + + // const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + // assert.equal(vscode.notebook.activeNotebookEditor !== undefined, true, 'notebook first'); + // const uri = vscode.notebook.activeNotebookEditor!.asWebviewUri(vscode.Uri.file('./hello.png')); + // assert.equal(uri.scheme, 'vscode-webview-resource'); + // await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + // }); + + + // 404 on web + // test('custom renderer message', async function () { + // if (vscode.env.uiKind === vscode.UIKind.Web) { + // return; + // } + + // const resource = vscode.Uri.file(join(vscode.workspace.rootPath || '', './customRenderer.vsctestnb')); + // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + + // const editor = vscode.notebook.activeNotebookEditor; + // const promise = new Promise(resolve => { + // const messageEmitter = editor?.onDidReceiveMessage(e => { + // if (e.type === 'custom_renderer_initialize') { + // resolve(); + // messageEmitter?.dispose(); + // } + // }); + // }); + + // await vscode.commands.executeCommand('notebook.cell.execute'); + // await promise; + // await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + // }); +}); diff --git a/package.json b/package.json index 70a941dffc..3aba564a29 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "azuredatastudio", "version": "1.23.0", - "distro": "05f08ef15e584dd5d5b5e3d03d447265869d2b0d", + "distro": "3b5783b2180557bf55e44b2b873cd47d4bd6ccd0", "author": { "name": "Microsoft Corporation" }, @@ -63,7 +63,7 @@ "ansi_up": "^3.0.0", "applicationinsights": "1.0.8", "chart.js": "^2.6.0", - "chokidar": "3.2.3", + "chokidar": "3.4.2", "graceful-fs": "4.2.3", "html-query-plan": "git://github.com/kburtram/html-query-plan.git#2.6", "http-proxy-agent": "^2.1.0", @@ -78,7 +78,7 @@ "native-keymap": "2.2.0", "native-watchdog": "1.3.0", "ng2-charts": "^1.6.0", - "node-pty": "0.10.0-beta8", + "node-pty": "0.10.0-beta17", "plotly.js-dist-min": "^1.53.0", "reflect-metadata": "^0.1.8", "rxjs": "5.4.0", @@ -93,13 +93,13 @@ "vscode-nsfw": "1.2.8", "vscode-oniguruma": "1.3.1", "vscode-proxy-agent": "^0.5.2", - "vscode-ripgrep": "^1.8.0", + "vscode-ripgrep": "^1.9.0", "vscode-sqlite3": "4.0.10", "vscode-textmate": "5.2.0", - "xterm": "4.9.0-beta.8", + "xterm": "4.9.0-beta.32", "xterm-addon-search": "0.7.0", "xterm-addon-unicode11": "0.2.0", - "xterm-addon-webgl": "0.8.0", + "xterm-addon-webgl": "0.9.0-beta.4", "yauzl": "^2.9.2", "yazl": "^2.4.3", "zone.js": "^0.8.4" @@ -139,7 +139,7 @@ "css-loader": "^3.2.0", "debounce": "^1.0.0", "deemon": "^1.4.0", - "electron": "9.2.1", + "electron": "9.3.0", "eslint": "6.8.0", "eslint-plugin-jsdoc": "^19.1.0", "event-stream": "3.3.4", @@ -185,7 +185,7 @@ "opn": "^6.0.0", "optimist": "0.3.5", "p-all": "^1.0.0", - "playwright": "1.0.1", + "playwright": "1.3.0", "pump": "^1.0.1", "queue": "3.0.6", "rcedit": "^1.1.0", diff --git a/remote/package.json b/remote/package.json index 624f56f4d7..ed37242083 100644 --- a/remote/package.json +++ b/remote/package.json @@ -14,7 +14,7 @@ "angular2-grid": "2.0.6", "ansi_up": "^3.0.0", "chart.js": "^2.6.0", - "chokidar": "3.2.3", + "chokidar": "3.4.2", "cookie": "^0.4.0", "graceful-fs": "4.2.3", "html-query-plan": "git://github.com/kburtram/html-query-plan.git#2.6", @@ -27,7 +27,7 @@ "minimist": "^1.2.5", "native-watchdog": "1.3.0", "ng2-charts": "^1.6.0", - "node-pty": "0.10.0-beta8", + "node-pty": "0.10.0-beta17", "reflect-metadata": "^0.1.8", "rxjs": "5.4.0", "sanitize-html": "^1.19.1", @@ -39,12 +39,12 @@ "vscode-nsfw": "1.2.8", "vscode-oniguruma": "1.3.1", "vscode-proxy-agent": "^0.5.2", - "vscode-ripgrep": "^1.8.0", + "vscode-ripgrep": "^1.9.0", "vscode-textmate": "5.2.0", - "xterm": "4.9.0-beta.8", + "xterm": "4.9.0-beta.32", "xterm-addon-search": "0.7.0", "xterm-addon-unicode11": "0.2.0", - "xterm-addon-webgl": "0.8.0", + "xterm-addon-webgl": "0.9.0-beta.4", "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 40ae41d1ef..f15cde8131 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -28,10 +28,9 @@ "turndown-plugin-gfm": "^1.0.2", "vscode-oniguruma": "1.3.1", "vscode-textmate": "5.2.0", - "xterm": "4.9.0-beta.8", + "xterm": "4.9.0-beta.32", "xterm-addon-search": "0.7.0", "xterm-addon-unicode11": "0.2.0", - "xterm-addon-webgl": "0.8.0", - "zone.js": "^0.8.4" + "xterm-addon-webgl": "0.9.0-beta.4" } } diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index d8c290cb11..d057a6c6c8 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -1033,17 +1033,12 @@ xterm-addon-unicode11@0.2.0: resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.2.0.tgz#9ed0c482b353908bba27778893ca80823382737c" integrity sha512-rjFDItPc/IDoSiEnoDFwKroNwLD/7t9vYKENjrcKVZg5tgJuuUj8D4rZtP6iVCjSB1LTLYmUs4L/EmCqIyLR/Q== -xterm-addon-webgl@0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.8.0.tgz#4bc6bb4dbfea5b0d2d7978d6c5cef922d584fb4f" - integrity sha512-dlpYPsv0C9S6v6+T/h/d/otSbdUTizMJdxvSoS34tUpMOHev6iW7Zqt5KRFqYxl4vCqpDk9Wmhb3fKL3kwX5fQ== +xterm-addon-webgl@0.9.0-beta.4: + version "0.9.0-beta.4" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.9.0-beta.4.tgz#5f5fde50db5c06b116471bcf56ad9930884b36fa" + integrity sha512-GuCvF7Eg1nKLX6zUbJLkt5cqeeccUjf/G6fugCfrkR0EWWC6Ik5mEsEOs5UWm9vvY2kX9t16BdCrgqp8KJegEg== -xterm@4.9.0-beta.8: - version "4.9.0-beta.8" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.9.0-beta.8.tgz#ca121934d63f88668d2d5b11d9b2fc3bde7bd805" - integrity sha512-EEonYBLANDUBfEeEnHG632bZdgBaAUWst8LFr6oC6f2uLFfJGHQvVJuLaEkPtRvS+jOeoorEXZRPmso1/ANHXA== - -zone.js@^0.8.4: - version "0.8.29" - resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.8.29.tgz#8dce92aa0dd553b50bc5bfbb90af9986ad845a12" - integrity sha512-mla2acNCMkWXBD+c+yeUrBUrzOxYMNFdQ6FGfigGGtEVBPJx07BQeJekjt9DmH1FtZek4E9rE1eRR9qQpxACOQ== +xterm@4.9.0-beta.32: + version "4.9.0-beta.32" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.9.0-beta.32.tgz#d1243d3be211cc06aad3418e696e4eced995c20c" + integrity sha512-jloHNBnj6XRJt+oPkapvrXJZVsYq6se/PEgzErl0iZn9qzSB3jmWE4byumoEjXJR6EgU5ZOmNljeeEDA9jO/jA== diff --git a/remote/yarn.lock b/remote/yarn.lock index e735fb03b9..50b0ecba64 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -240,10 +240,10 @@ chartjs-color@^2.1.0: chartjs-color-string "^0.6.0" color-convert "^1.9.3" -chokidar@3.2.3: - version "3.2.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.2.3.tgz#b9270a565d14f02f6bfdd537a6a2bbf5549b8c8c" - integrity sha512-GtrxGuRf6bzHQmXWRepvsGnXpkQkVU+D2/9a7dAe4a7v1NhrfZOZ2oKf76M3nOs46fFYL8D+Q8JYA4GYeJ8Cjw== +chokidar@3.4.2: + version "3.4.2" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.2.tgz#38dc8e658dec3809741eb3ef7bb0a47fe424232d" + integrity sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A== dependencies: anymatch "~3.1.1" braces "~3.0.2" @@ -251,9 +251,9 @@ chokidar@3.2.3: is-binary-path "~2.1.0" is-glob "~4.0.1" normalize-path "~3.0.0" - readdirp "~3.2.0" + readdirp "~3.4.0" optionalDependencies: - fsevents "~2.1.1" + fsevents "~2.1.2" color-convert@^1.9.0, color-convert@^1.9.3: version "1.9.3" @@ -537,10 +537,10 @@ fs-extra@^7.0.0: jsonfile "^4.0.0" universalify "^0.1.0" -fsevents@~2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.1.tgz#74c64e21df71721845d0c44fe54b7f56b82995a9" - integrity sha512-4FRPXWETxtigtJW/gxzEDsX1LVbPAM93VleB83kZB+ellqbHMkyt2aJfuzNLRvFPnGi6bcE5SvfxgbXPeKteJw== +fsevents@~2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" + integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== getpass@^0.1.1: version "0.1.7" @@ -904,10 +904,10 @@ node-addon-api@1.6.2: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.6.2.tgz#d8aad9781a5cfc4132cc2fecdbdd982534265217" integrity sha512-479Bjw9nTE5DdBSZZWprFryHGjUaQC31y1wHo19We/k0BZlrmhqQitWoUL0cD8+scljCbIUL+E58oRDEakdGGA== -node-pty@0.10.0-beta8: - version "0.10.0-beta8" - resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.10.0-beta8.tgz#f4aa56c71a794f4580373a3030dfebd25240c6db" - integrity sha512-Ul/hLsadC0SvvShxpne+kq2ebSMcitewlNhrwoXXBvFdCqxJt7Ai1AgMhH7AKBUp06uBeYXThJ2ihTszrkdnYw== +node-pty@0.10.0-beta17: + version "0.10.0-beta17" + resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.10.0-beta17.tgz#962d4a3f4dc6772385e0cad529c209cef3bc79e6" + integrity sha512-tn7EANQacnAvnOQCImvgag1DL0tVmUoY/1yIZbh3u/BBpvCcGHLZJNn7TXheodRLr6hmGSUS2VbfcUr9p0gOug== dependencies: nan "^2.14.0" @@ -963,6 +963,11 @@ picomatch@^2.0.4: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.0.7.tgz#514169d8c7cd0bdbeecc8a2609e34a7163de69f6" integrity sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA== +picomatch@^2.2.1: + version "2.2.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" + integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== + postcss@^7.0.5: version "7.0.21" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.21.tgz#06bb07824c19c2021c5d056d5b10c35b989f7e17" @@ -1006,12 +1011,12 @@ readable-stream@^3.1.1: string_decoder "^1.1.1" util-deprecate "^1.0.1" -readdirp@~3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.2.0.tgz#c30c33352b12c96dfb4b895421a49fd5a9593839" - integrity sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ== +readdirp@~3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.4.0.tgz#9fdccdf9e9155805449221ac645e8303ab5b9ada" + integrity sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ== dependencies: - picomatch "^2.0.4" + picomatch "^2.2.1" reflect-metadata@^0.1.8: version "0.1.13" @@ -1331,10 +1336,10 @@ vscode-proxy-agent@^0.5.2: https-proxy-agent "^2.2.3" socks-proxy-agent "^4.0.1" -vscode-ripgrep@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.8.0.tgz#dfe7c2ae2a2032df8a8108765c2feef73474888a" - integrity sha512-/Q5XtePkTLLi8yplr5ai24pVEymRF62xH9xXrtj35GTaDCJg3zq1s1/L1UqhVbfNDv4OcMBYjyIAt/quEi3d5w== +vscode-ripgrep@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.9.0.tgz#d6cdea4d290f3c2919472cdcfe2440d5fb1f99db" + integrity sha512-7jyAC/NNfvMPZgCVkyqIn0STYJ7wIk3PF2qA2cX1sEutx1g/e2VtgKAodXnfpreJq4993JT/BSIigOv/0lBSzg== dependencies: https-proxy-agent "^4.0.0" proxy-from-env "^1.1.0" @@ -1436,15 +1441,15 @@ xterm-addon-unicode11@0.2.0: resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.2.0.tgz#9ed0c482b353908bba27778893ca80823382737c" integrity sha512-rjFDItPc/IDoSiEnoDFwKroNwLD/7t9vYKENjrcKVZg5tgJuuUj8D4rZtP6iVCjSB1LTLYmUs4L/EmCqIyLR/Q== -xterm-addon-webgl@0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.8.0.tgz#4bc6bb4dbfea5b0d2d7978d6c5cef922d584fb4f" - integrity sha512-dlpYPsv0C9S6v6+T/h/d/otSbdUTizMJdxvSoS34tUpMOHev6iW7Zqt5KRFqYxl4vCqpDk9Wmhb3fKL3kwX5fQ== +xterm-addon-webgl@0.9.0-beta.4: + version "0.9.0-beta.4" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.9.0-beta.4.tgz#5f5fde50db5c06b116471bcf56ad9930884b36fa" + integrity sha512-GuCvF7Eg1nKLX6zUbJLkt5cqeeccUjf/G6fugCfrkR0EWWC6Ik5mEsEOs5UWm9vvY2kX9t16BdCrgqp8KJegEg== -xterm@4.9.0-beta.8: - version "4.9.0-beta.8" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.9.0-beta.8.tgz#ca121934d63f88668d2d5b11d9b2fc3bde7bd805" - integrity sha512-EEonYBLANDUBfEeEnHG632bZdgBaAUWst8LFr6oC6f2uLFfJGHQvVJuLaEkPtRvS+jOeoorEXZRPmso1/ANHXA== +xterm@4.9.0-beta.32: + version "4.9.0-beta.32" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.9.0-beta.32.tgz#d1243d3be211cc06aad3418e696e4eced995c20c" + integrity sha512-jloHNBnj6XRJt+oPkapvrXJZVsYq6se/PEgzErl0iZn9qzSB3jmWE4byumoEjXJR6EgU5ZOmNljeeEDA9jO/jA== yauzl@^2.9.2: version "2.10.0" diff --git a/resources/linux/code-workspace.xml b/resources/linux/code-workspace.xml new file mode 100644 index 0000000000..9bca182e43 --- /dev/null +++ b/resources/linux/code-workspace.xml @@ -0,0 +1,7 @@ + + + + @@NAME_LONG@@ Workspace + + + diff --git a/resources/linux/code.desktop b/resources/linux/code.desktop index 53ad8dc607..1a3c77311d 100755 --- a/resources/linux/code.desktop +++ b/resources/linux/code.desktop @@ -8,7 +8,7 @@ Type=Application StartupNotify=false StartupWMClass=@@NAME_SHORT@@ Categories=Utility;TextEditor;Development;IDE; -MimeType=text/plain;inode/directory; +MimeType=text/plain;inode/directory;application/x-@@NAME@@-workspace; Actions=new-empty-window; Keywords=azuredatastudio; diff --git a/resources/linux/debian/postinst.template b/resources/linux/debian/postinst.template index 196f4c6b0b..2c5ed24a8f 100644 --- a/resources/linux/debian/postinst.template +++ b/resources/linux/debian/postinst.template @@ -18,6 +18,11 @@ if hash desktop-file-install 2>/dev/null; then desktop-file-install /usr/share/applications/@@NAME@@-url-handler.desktop fi +# Update mimetype database to pickup workspace mimetype +if hash update-mime-database 2>/dev/null; then + update-mime-database /usr/share/mime +fi + if [ "@@NAME@@" != "code-oss" ]; then # Remove the legacy bin command if this is the stable build if [ "@@NAME@@" = "code" ]; then diff --git a/resources/linux/debian/postrm.template b/resources/linux/debian/postrm.template index 026784dd70..d612229b28 100644 --- a/resources/linux/debian/postrm.template +++ b/resources/linux/debian/postrm.template @@ -3,4 +3,9 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the Source EULA. See License.txt in the project root for license information. -rm -f /usr/bin/@@NAME@@ \ No newline at end of file +rm -f /usr/bin/@@NAME@@ + +# Update mimetype database for removed workspace mimetype +if hash update-mime-database 2>/dev/null; then + update-mime-database /usr/share/mime +fi diff --git a/resources/linux/rpm/code.spec.template b/resources/linux/rpm/code.spec.template index 93e20cd482..3407d4a0c5 100644 --- a/resources/linux/rpm/code.spec.template +++ b/resources/linux/rpm/code.spec.template @@ -20,9 +20,11 @@ mkdir -p %{buildroot}/usr/share/applications mkdir -p %{buildroot}/usr/share/pixmaps mkdir -p %{buildroot}/usr/share/bash-completion/completions mkdir -p %{buildroot}/usr/share/zsh/site-functions +mkdir -p %{buildroot}/usr/share/mime/packages cp -r usr/share/@@NAME@@/* %{buildroot}/usr/share/@@NAME@@ cp -r usr/share/applications/@@NAME@@.desktop %{buildroot}/usr/share/applications cp -r usr/share/applications/@@NAME@@-url-handler.desktop %{buildroot}/usr/share/applications +cp -r usr/share/mime/packages/@@NAME@@-workspace.xml %{buildroot}/usr/share/mime/packages/@@NAME@@-workspace.xml cp -r usr/share/pixmaps/@@ICON@@.png %{buildroot}/usr/share/pixmaps cp usr/share/bash-completion/completions/@@NAME@@ %{buildroot}/usr/share/bash-completion/completions/@@NAME@@ cp usr/share/zsh/site-functions/_@@NAME@@ %{buildroot}/usr/share/zsh/site-functions/_@@NAME@@ @@ -46,17 +48,24 @@ ln -sf /usr/share/@@NAME@@/bin/@@NAME@@ %{_bindir}/@@NAME@@ # fi #fi +# Update mimetype database to pickup workspace mimetype +update-mime-database /usr/share/mime &> /dev/null || : + %postun if [ $1 = 0 ]; then rm -f /usr/bin/@@NAME@@ fi +# Update mimetype database for removed workspace mimetype +update-mime-database /usr/share/mime &> /dev/null || : + %files %defattr(-,root,root) /usr/share/@@NAME@@/ /usr/share/applications/@@NAME@@.desktop /usr/share/applications/@@NAME@@-url-handler.desktop +/usr/share/mime/packages/@@NAME@@-workspace.xml /usr/share/pixmaps/@@ICON@@.png /usr/share/bash-completion/completions/@@NAME@@ /usr/share/zsh/site-functions/_@@NAME@@ diff --git a/resources/linux/rpm/dependencies.json b/resources/linux/rpm/dependencies.json index 34f127e1ae..07e2b307fc 100644 --- a/resources/linux/rpm/dependencies.json +++ b/resources/linux/rpm/dependencies.json @@ -63,5 +63,135 @@ "libxcb.so.1()(64bit)", "libxkbfile.so.1()(64bit)", "libsecret-1.so.0()(64bit)" + ], + "aarch64": [ + "libpthread.so.0()(aarch64)", + "libpthread.so.0(GLIBC_2.2.5)(aarch64)", + "libpthread.so.0(GLIBC_2.3.2)(aarch64)", + "libpthread.so.0(GLIBC_2.3.3)(aarch64)", + "libgtk-3.so.0()(aarch64)", + "libgdk-x11-2.0.so.0()(aarch64)", + "libatk-1.0.so.0()(aarch64)", + "libgio-2.0.so.0()(aarch64)", + "libpangocairo-1.0.so.0()(aarch64)", + "libgdk_pixbuf-2.0.so.0()(aarch64)", + "libcairo.so.2()(aarch64)", + "libpango-1.0.so.0()(aarch64)", + "libfreetype.so.6()(aarch64)", + "libfontconfig.so.1()(aarch64)", + "libgobject-2.0.so.0()(aarch64)", + "libdbus-1.so.3()(aarch64)", + "libXi.so.6()(aarch64)", + "libXcursor.so.1()(aarch64)", + "libXdamage.so.1()(aarch64)", + "libXrandr.so.2()(aarch64)", + "libXcomposite.so.1()(aarch64)", + "libXext.so.6()(aarch64)", + "libXfixes.so.3()(aarch64)", + "libXrender.so.1()(aarch64)", + "libX11.so.6()(aarch64)", + "libXss.so.1()(aarch64)", + "libXtst.so.6()(aarch64)", + "libgmodule-2.0.so.0()(aarch64)", + "librt.so.1()(aarch64)", + "libglib-2.0.so.0()(aarch64)", + "libnss3.so()(aarch64)", + "libnssutil3.so()(aarch64)", + "libsmime3.so()(aarch64)", + "libnspr4.so()(aarch64)", + "libasound.so.2()(aarch64)", + "libcups.so.2()(aarch64)", + "libdl.so.2()(aarch64)", + "libexpat.so.1()(aarch64)", + "libstdc++.so.6()(aarch64)", + "libstdc++.so.6(GLIBCXX_3.4)(aarch64)", + "libstdc++.so.6(GLIBCXX_3.4.10)(aarch64)", + "libstdc++.so.6(GLIBCXX_3.4.11)(aarch64)", + "libstdc++.so.6(GLIBCXX_3.4.14)(aarch64)", + "libstdc++.so.6(GLIBCXX_3.4.15)(aarch64)", + "libstdc++.so.6(GLIBCXX_3.4.9)(aarch64)", + "libm.so.6()(aarch64)", + "libm.so.6(GLIBC_2.2.5)(aarch64)", + "libgcc_s.so.1()(aarch64)", + "libgcc_s.so.1(GCC_3.0)(aarch64)", + "libgcc_s.so.1(GCC_4.0.0)(aarch64)", + "libc.so.6()(aarch64)", + "libc.so.6(GLIBC_2.11)(aarch64)", + "libc.so.6(GLIBC_2.2.5)(aarch64)", + "libc.so.6(GLIBC_2.3)(aarch64)", + "libc.so.6(GLIBC_2.3.2)(aarch64)", + "libc.so.6(GLIBC_2.3.4)(aarch64)", + "libc.so.6(GLIBC_2.4)(aarch64)", + "libc.so.6(GLIBC_2.6)(aarch64)", + "libc.so.6(GLIBC_2.7)(aarch64)", + "libc.so.6(GLIBC_2.9)(aarch64)", + "libxcb.so.1()(aarch64)", + "libxkbfile.so.1()(aarch64)", + "libsecret-1.so.0()(aarch64)" + ], + "armv7hl": [ + "libpthread.so.0()(armv7hl)", + "libpthread.so.0(GLIBC_2.2.5)(armv7hl)", + "libpthread.so.0(GLIBC_2.3.2)(armv7hl)", + "libpthread.so.0(GLIBC_2.3.3)(armv7hl)", + "libgtk-3.so.0()(armv7hl)", + "libgdk-x11-2.0.so.0()(armv7hl)", + "libatk-1.0.so.0()(armv7hl)", + "libgio-2.0.so.0()(armv7hl)", + "libpangocairo-1.0.so.0()(armv7hl)", + "libgdk_pixbuf-2.0.so.0()(armv7hl)", + "libcairo.so.2()(armv7hl)", + "libpango-1.0.so.0()(armv7hl)", + "libfreetype.so.6()(armv7hl)", + "libfontconfig.so.1()(armv7hl)", + "libgobject-2.0.so.0()(armv7hl)", + "libdbus-1.so.3()(armv7hl)", + "libXi.so.6()(armv7hl)", + "libXcursor.so.1()(armv7hl)", + "libXdamage.so.1()(armv7hl)", + "libXrandr.so.2()(armv7hl)", + "libXcomposite.so.1()(armv7hl)", + "libXext.so.6()(armv7hl)", + "libXfixes.so.3()(armv7hl)", + "libXrender.so.1()(armv7hl)", + "libX11.so.6()(armv7hl)", + "libXss.so.1()(armv7hl)", + "libXtst.so.6()(armv7hl)", + "libgmodule-2.0.so.0()(armv7hl)", + "librt.so.1()(armv7hl)", + "libglib-2.0.so.0()(armv7hl)", + "libnss3.so()(armv7hl)", + "libnssutil3.so()(armv7hl)", + "libsmime3.so()(armv7hl)", + "libnspr4.so()(armv7hl)", + "libasound.so.2()(armv7hl)", + "libcups.so.2()(armv7hl)", + "libdl.so.2()(armv7hl)", + "libexpat.so.1()(armv7hl)", + "libstdc++.so.6()(armv7hl)", + "libstdc++.so.6(GLIBCXX_3.4)(armv7hl)", + "libstdc++.so.6(GLIBCXX_3.4.10)(armv7hl)", + "libstdc++.so.6(GLIBCXX_3.4.11)(armv7hl)", + "libstdc++.so.6(GLIBCXX_3.4.14)(armv7hl)", + "libstdc++.so.6(GLIBCXX_3.4.15)(armv7hl)", + "libstdc++.so.6(GLIBCXX_3.4.9)(armv7hl)", + "libm.so.6()(armv7hl)", + "libm.so.6(GLIBC_2.2.5)(armv7hl)", + "libgcc_s.so.1()(armv7hl)", + "libgcc_s.so.1(GCC_3.0)(armv7hl)", + "libgcc_s.so.1(GCC_4.0.0)(armv7hl)", + "libc.so.6()(armv7hl)", + "libc.so.6(GLIBC_2.11)(armv7hl)", + "libc.so.6(GLIBC_2.2.5)(armv7hl)", + "libc.so.6(GLIBC_2.3)(armv7hl)", + "libc.so.6(GLIBC_2.3.2)(armv7hl)", + "libc.so.6(GLIBC_2.3.4)(armv7hl)", + "libc.so.6(GLIBC_2.4)(armv7hl)", + "libc.so.6(GLIBC_2.6)(armv7hl)", + "libc.so.6(GLIBC_2.7)(armv7hl)", + "libc.so.6(GLIBC_2.9)(armv7hl)", + "libxcb.so.1()(armv7hl)", + "libxkbfile.so.1()(armv7hl)", + "libsecret-1.so.0()(armv7hl)" ] -} \ No newline at end of file +} diff --git a/resources/web/code-web.js b/resources/web/code-web.js index 1ab1b7cde5..e0742e5f83 100644 --- a/resources/web/code-web.js +++ b/resources/web/code-web.js @@ -28,7 +28,7 @@ const BUILTIN_MARKETPLACE_EXTENSIONS_ROOT = path.join(APP_ROOT, '.build', 'built const WEB_DEV_EXTENSIONS_ROOT = path.join(APP_ROOT, '.build', 'builtInWebDevExtensions'); const WEB_MAIN = path.join(APP_ROOT, 'src', 'vs', 'code', 'browser', 'workbench', 'workbench-dev.html'); -const WEB_PLAYGROUND_VERSION = '0.0.8'; +const WEB_PLAYGROUND_VERSION = '0.0.9'; const args = minimist(process.argv, { boolean: [ @@ -395,8 +395,7 @@ async function handleRoot(req, res) { .replace('{{WORKBENCH_WEB_CONFIGURATION}}', () => escapeAttribute(JSON.stringify(webConfigJSON))) // use a replace function to avoid that regexp replace patterns ($&, $0, ...) are applied .replace('{{WORKBENCH_BUILTIN_EXTENSIONS}}', () => escapeAttribute(JSON.stringify(dedupedBuiltInExtensions))) .replace('{{WORKBENCH_CREDENTIALS}}', () => escapeAttribute(JSON.stringify(credentials))) - .replace('{{WEBVIEW_ENDPOINT}}', '') - .replace('{{REMOTE_USER_DATA_URI}}', ''); + .replace('{{WEBVIEW_ENDPOINT}}', ''); const headers = { 'Content-Type': 'text/html' }; diff --git a/src/main.js b/src/main.js index 4f98109820..b131331adf 100644 --- a/src/main.js +++ b/src/main.js @@ -207,9 +207,9 @@ async function onReady() { } /** - * @typedef {{ [arg: string]: any; '--'?: string[]; _: string[]; }} ParsedArgs + * @typedef {{ [arg: string]: any; '--'?: string[]; _: string[]; }} NativeParsedArgs * - * @param {ParsedArgs} cliArgs + * @param {NativeParsedArgs} cliArgs */ function configureCommandlineSwitchesSync(cliArgs) { const SUPPORTED_ELECTRON_SWITCHES = [ @@ -362,7 +362,7 @@ function getArgvConfigPath() { } /** - * @param {ParsedArgs} cliArgs + * @param {NativeParsedArgs} cliArgs * @returns {string} */ function getJSFlags(cliArgs) { @@ -382,7 +382,7 @@ function getJSFlags(cliArgs) { } /** - * @param {ParsedArgs} cliArgs + * @param {NativeParsedArgs} cliArgs * * @returns {string} */ @@ -395,7 +395,7 @@ function getUserDataPath(cliArgs) { } /** - * @returns {ParsedArgs} + * @returns {NativeParsedArgs} */ function parseCLIArgs() { const minimist = require('minimist'); diff --git a/src/sql/base/browser/ui/buttonMenu/buttonMenu.ts b/src/sql/base/browser/ui/buttonMenu/buttonMenu.ts index 1f2b2ca7d3..67ca3149d4 100644 --- a/src/sql/base/browser/ui/buttonMenu/buttonMenu.ts +++ b/src/sql/base/browser/ui/buttonMenu/buttonMenu.ts @@ -64,18 +64,14 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { return null; }; + const isActionsArray = Array.isArray(this.menuActionsOrProvider); const options: IDropdownMenuOptions = { contextMenuProvider: this.contextMenuProvider, - labelRenderer: labelRenderer + labelRenderer: labelRenderer, + actions: isActionsArray ? this.menuActionsOrProvider as IAction[] : undefined, + actionProvider: isActionsArray ? undefined : this.menuActionsOrProvider as IActionProvider }; - // Render the DropdownMenu around a simple action to toggle it - if (Array.isArray(this.menuActionsOrProvider)) { - options.actions = this.menuActionsOrProvider; - } else { - options.actionProvider = this.menuActionsOrProvider as IActionProvider; - } - this.dropdownMenu = this._register(new DropdownMenu(container, options)); this.dropdownMenu.menuOptions = { actionViewItemProvider: this.actionViewItemProvider, diff --git a/src/sql/base/browser/ui/panel/panel.component.ts b/src/sql/base/browser/ui/panel/panel.component.ts index add79079da..4aba758dfc 100644 --- a/src/sql/base/browser/ui/panel/panel.component.ts +++ b/src/sql/base/browser/ui/panel/panel.component.ts @@ -18,7 +18,6 @@ import * as types from 'vs/base/common/types'; import { mixin } from 'vs/base/common/objects'; import { Disposable } from 'vs/base/common/lifecycle'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; -import { firstIndex } from 'vs/base/common/arrays'; import * as nls from 'vs/nls'; import { TabHeaderComponent } from 'sql/base/browser/ui/panel/tabHeader.component'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -250,7 +249,7 @@ export class PanelComponent extends Disposable implements IThemable { * Select on the next tab */ public selectOnNextTab(): void { - let activeIndex = firstIndex(this._tabs.toArray(), i => i === this._activeTab); + let activeIndex = this._tabs.toArray().findIndex(i => i === this._activeTab); let nextTabIndex = activeIndex + 1; if (nextTabIndex === this._tabs.length) { nextTabIndex = 0; @@ -284,7 +283,7 @@ export class PanelComponent extends Disposable implements IThemable { } private findAndRemoveTabFromMRU(tab: TabComponent): void { - let mruIndex = firstIndex(this._mru, i => i === tab); + let mruIndex = this._mru.findIndex(i => i === tab); if (mruIndex !== -1) { // Remove old index diff --git a/src/sql/base/browser/ui/panel/panel.ts b/src/sql/base/browser/ui/panel/panel.ts index 64ac8d94d5..0936ddb29a 100644 --- a/src/sql/base/browser/ui/panel/panel.ts +++ b/src/sql/base/browser/ui/panel/panel.ts @@ -14,7 +14,6 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Color } from 'vs/base/common/color'; import { isUndefinedOrNull } from 'vs/base/common/types'; -import { firstIndex } from 'vs/base/common/arrays'; export interface ITabbedPanelStyles { titleActiveForeground?: Color; @@ -176,11 +175,11 @@ export class TabbedPanel extends Disposable { e.stopImmediatePropagation(); } if (event.equals(KeyCode.RightArrow)) { - let currentIndex = firstIndex(this._tabOrder, x => x === tab.tab.identifier); + let currentIndex = this._tabOrder.findIndex(x => x === tab.tab.identifier); this.focusNextTab(currentIndex + 1); } if (event.equals(KeyCode.LeftArrow)) { - let currentIndex = firstIndex(this._tabOrder, x => x === tab.tab.identifier); + let currentIndex = this._tabOrder.findIndex(x => x === tab.tab.identifier); this.focusNextTab(currentIndex - 1); } if (event.equals(KeyCode.Tab)) { @@ -271,7 +270,7 @@ export class TabbedPanel extends Disposable { } actualTab.disposables.dispose(); this._tabMap.delete(tab); - let index = firstIndex(this._tabOrder, t => t === tab); + let index = this._tabOrder.findIndex(t => t === tab); this._tabOrder.splice(index, 1); if (this._shownTabId === tab) { this._shownTabId = undefined; diff --git a/src/sql/base/browser/ui/table/highPerf/tableView.ts b/src/sql/base/browser/ui/table/highPerf/tableView.ts index 0dc1383de9..f202260028 100644 --- a/src/sql/base/browser/ui/table/highPerf/tableView.ts +++ b/src/sql/base/browser/ui/table/highPerf/tableView.ts @@ -18,7 +18,6 @@ import { Range, IRange } from 'vs/base/common/range'; import { getOrDefault } from 'vs/base/common/objects'; import { memoize } from 'vs/base/common/decorators'; import { Sash, Orientation, ISashEvent as IBaseSashEvent } from 'vs/base/browser/ui/sash/sash'; -import { firstIndex } from 'vs/base/common/arrays'; import { CellCache, ICell } from 'sql/base/browser/ui/table/highPerf/cellCache'; import { ITableRenderer, ITableDataSource, ITableMouseEvent, IStaticTableRenderer, ITableColumn } from 'sql/base/browser/ui/table/highPerf/table'; @@ -279,7 +278,7 @@ export class TableView implements IDisposable { } private onSashStart({ sash, start }: ISashEvent): void { - const index = firstIndex(this.columnSashs, item => item.sash === sash); + const index = this.columnSashs.findIndex(item => item.sash === sash); const sizes = this.columns.map(i => i.width!); const lefts = this.columns.map(i => i.left!); this.sashDragState = { start, current: start, index, sizes, lefts }; @@ -506,7 +505,7 @@ export class TableView implements IDisposable { } indexOfColumn(columnId: string): number | undefined { - return firstIndex(this.columns, v => v.id === columnId); + return this.columns.findIndex(v => v.id === columnId); } get renderHeight(): number { diff --git a/src/sql/base/browser/ui/table/highPerf/tableWidget.ts b/src/sql/base/browser/ui/table/highPerf/tableWidget.ts index 19ba7f8f94..daf740cfee 100644 --- a/src/sql/base/browser/ui/table/highPerf/tableWidget.ts +++ b/src/sql/base/browser/ui/table/highPerf/tableWidget.ts @@ -8,7 +8,6 @@ import { ITableEvent, ITableRenderer, ITableMouseEvent, ITableContextMenuEvent, import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; import { memoize } from 'vs/base/common/decorators'; import { Event, Emitter, EventBufferer } from 'vs/base/common/event'; -import { firstIndex, find } from 'vs/base/common/arrays'; import * as DOM from 'vs/base/browser/dom'; import { TableView, ITableViewOptions } from 'sql/base/browser/ui/table/highPerf/tableView'; import { ScrollEvent } from 'vs/base/common/scrollable'; @@ -52,7 +51,7 @@ class TraitRenderer implements ITableRenderer } renderCell(element: T, row: number, cell: number, columnId: string, templateData: ITraitTemplateData): void { - const renderedElementIndex = firstIndex(this.renderedElements, el => el.templateData === templateData); + const renderedElementIndex = this.renderedElements.findIndex(el => el.templateData === templateData); if (renderedElementIndex >= 0) { const rendered = this.renderedElements[renderedElementIndex]; @@ -68,14 +67,14 @@ class TraitRenderer implements ITableRenderer renderIndexes(indexes: IGridRange[]): void { for (const { index, templateData } of this.renderedElements) { - if (!!find(indexes, v => GridRange.containsPosition(v, index))) { + if (!!indexes.find(v => GridRange.containsPosition(v, index))) { this.trait.renderIndex(index, templateData); } } } disposeTemplate(templateData: ITraitTemplateData): void { - const index = firstIndex(this.renderedElements, el => el.templateData === templateData); + const index = this.renderedElements.findIndex(el => el.templateData === templateData); if (index < 0) { return; @@ -194,7 +193,7 @@ class Trait implements IDisposable { } contains(index: GridPosition): boolean { - return !!find(this.indexes, v => GridRange.containsPosition(v, index)); + return !!this.indexes.find(v => GridRange.containsPosition(v, index)); } dispose() { diff --git a/src/sql/platform/accounts/common/accountStore.ts b/src/sql/platform/accounts/common/accountStore.ts index b43d3f9495..db927585ef 100644 --- a/src/sql/platform/accounts/common/accountStore.ts +++ b/src/sql/platform/accounts/common/accountStore.ts @@ -7,7 +7,6 @@ import * as azdata from 'azdata'; import { AccountAdditionResult } from 'sql/platform/accounts/common/eventTypes'; import { IAccountStore } from 'sql/platform/accounts/common/interfaces'; import { deepClone } from 'vs/base/common/objects'; -import { firstIndex } from 'vs/base/common/arrays'; import { ILogService } from 'vs/platform/log/common/log'; export default class AccountStore implements IAccountStore { @@ -29,7 +28,7 @@ export default class AccountStore implements IAccountStore { return this.readFromMemento() .then(accounts => { // Determine if account exists and proceed accordingly - const match = firstIndex(accounts, account => AccountStore.findAccountByKey(account.key, newAccount.key)); + const match = accounts.findIndex(account => AccountStore.findAccountByKey(account.key, newAccount.key)); return match < 0 ? this.addToAccountList(accounts, newAccount) : this.updateAccountList(accounts, newAccount.key, matchAccount => AccountStore.mergeAccounts(newAccount, matchAccount)); @@ -134,7 +133,7 @@ export default class AccountStore implements IAccountStore { private addToAccountList(accounts: azdata.Account[], accountToAdd: azdata.Account): AccountListOperationResult { // Check if the entry already exists - const match = firstIndex(accounts, account => AccountStore.findAccountByKey(account.key, accountToAdd.key)); + const match = accounts.findIndex(account => AccountStore.findAccountByKey(account.key, accountToAdd.key)); if (match >= 0) { // Account already exists, we won't do anything return { @@ -159,7 +158,7 @@ export default class AccountStore implements IAccountStore { private removeFromAccountList(accounts: azdata.Account[], accountToRemove: azdata.AccountKey): AccountListOperationResult { // Check if the entry exists - const match = firstIndex(accounts, account => AccountStore.findAccountByKey(account.key, accountToRemove)); + const match = accounts.findIndex(account => AccountStore.findAccountByKey(account.key, accountToRemove)); if (match >= 0) { // Account exists, remove it from the account list accounts.splice(match, 1); @@ -176,7 +175,7 @@ export default class AccountStore implements IAccountStore { private updateAccountList(accounts: azdata.Account[], accountToUpdate: azdata.AccountKey, updateOperation: (account: azdata.Account) => void): AccountListOperationResult { // Check if the entry exists - const match = firstIndex(accounts, account => AccountStore.findAccountByKey(account.key, accountToUpdate)); + const match = accounts.findIndex(account => AccountStore.findAccountByKey(account.key, accountToUpdate)); if (match < 0) { // Account doesn't exist, we won't do anything return { diff --git a/src/sql/platform/connection/common/connectionConfig.ts b/src/sql/platform/connection/common/connectionConfig.ts index 2fc1a4116b..4c33d9c49c 100644 --- a/src/sql/platform/connection/common/connectionConfig.ts +++ b/src/sql/platform/connection/common/connectionConfig.ts @@ -12,7 +12,6 @@ import * as Utils from 'sql/platform/connection/common/utils'; import { generateUuid } from 'vs/base/common/uuid'; import * as nls from 'vs/nls'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { find, firstIndex } from 'vs/base/common/arrays'; import { deepClone } from 'vs/base/common/objects'; const GROUPS_CONFIG_KEY = 'datasource.connectionGroups'; @@ -45,7 +44,7 @@ export class ConnectionConfig { if (userValue) { if (workspaceValue) { - userValue = userValue.filter(x => find(workspaceValue, f => this.isSameGroupName(f, x)) === undefined); + userValue = userValue.filter(x => workspaceValue.find(f => this.isSameGroupName(f, x)) === undefined); allGroups = allGroups.concat(workspaceValue); } allGroups = allGroups.concat(userValue); @@ -73,12 +72,12 @@ export class ConnectionConfig { let newProfile = ConnectionProfile.convertToProfileStore(this._capabilitiesService, connectionProfile); // Remove the profile if already set - let sameProfileInList = find(profiles, value => { + let sameProfileInList = profiles.find(value => { let providerConnectionProfile = ConnectionProfile.createFromStoredProfile(value, this._capabilitiesService); return matcher(providerConnectionProfile, connectionProfile); }); if (sameProfileInList) { - let profileIndex = firstIndex(profiles, value => value === sameProfileInList); + let profileIndex = profiles.findIndex(value => value === sameProfileInList); newProfile.id = sameProfileInList.id; connectionProfile.id = sameProfileInList.id; profiles[profileIndex] = newProfile; @@ -125,7 +124,7 @@ export class ConnectionConfig { return Promise.resolve(profileGroup.id); } else { let groups = deepClone(this.configurationService.inspect(GROUPS_CONFIG_KEY).userValue); - let sameNameGroup = groups ? find(groups, group => group.name === profileGroup.name) : undefined; + let sameNameGroup = groups ? groups.find(group => group.name === profileGroup.name) : undefined; if (sameNameGroup) { let errMessage: string = nls.localize('invalidServerName', "A server group with the same name already exists."); return Promise.reject(errMessage); @@ -282,7 +281,7 @@ export class ConnectionConfig { */ public canChangeConnectionConfig(profile: ConnectionProfile, newGroupID: string): boolean { let profiles = this.getIConnectionProfileStores(true); - let existingProfile = find(profiles, p => + let existingProfile = profiles.find(p => p.providerName === profile.providerName && p.options.authenticationType === profile.options.authenticationType && p.options.database === profile.options.database && @@ -338,7 +337,7 @@ export class ConnectionConfig { public editGroup(source: ConnectionProfileGroup): Promise { let groups = deepClone(this.configurationService.inspect(GROUPS_CONFIG_KEY).userValue); - let sameNameGroup = groups ? find(groups, group => group.name === source.name && group.id !== source.id) : undefined; + let sameNameGroup = groups ? groups.find(group => group.name === source.name && group.id !== source.id) : undefined; if (sameNameGroup) { let errMessage: string = nls.localize('invalidServerName', "A server group with the same name already exists."); return Promise.reject(errMessage); @@ -379,7 +378,7 @@ export class ConnectionConfig { color: color, description: description } as IConnectionProfileGroup; - let found = find(groupTree, group => this.isSameGroupName(group, newGroup)); + let found = groupTree.find(group => this.isSameGroupName(group, newGroup)); if (found) { if (index === groupNames.length - 1) { newGroupId = found.id; diff --git a/src/sql/platform/connection/common/connectionProfileGroup.ts b/src/sql/platform/connection/common/connectionProfileGroup.ts index cc488e9fb4..38a38b9b08 100644 --- a/src/sql/platform/connection/common/connectionProfileGroup.ts +++ b/src/sql/platform/connection/common/connectionProfileGroup.ts @@ -7,7 +7,6 @@ import { ConnectionProfile } from 'sql/platform/connection/common/connectionProf import { Disposable } from 'vs/base/common/lifecycle'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { assign } from 'vs/base/common/objects'; -import { find } from 'vs/base/common/arrays'; export interface INewConnectionProfileGroup { id?: string; @@ -108,7 +107,7 @@ export class ConnectionProfileGroup extends Disposable implements IConnectionPro * Returns true if all connections in the tree have valid options using the correct capabilities */ public get hasValidConnections(): boolean { - let invalidConnections = find(this._childConnections, c => !c.isConnectionOptionsValid); + let invalidConnections = this._childConnections.find(c => !c.isConnectionOptionsValid); if (invalidConnections !== undefined) { return false; } else { diff --git a/src/sql/platform/connection/common/connectionStatusManager.ts b/src/sql/platform/connection/common/connectionStatusManager.ts index 807e847bfa..256bea8315 100644 --- a/src/sql/platform/connection/common/connectionStatusManager.ts +++ b/src/sql/platform/connection/common/connectionStatusManager.ts @@ -16,7 +16,6 @@ import * as azdata from 'azdata'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { startsWith } from 'vs/base/common/strings'; import { values } from 'vs/base/common/collections'; -import { firstIndex, find } from 'vs/base/common/arrays'; export class ConnectionStatusManager { @@ -39,7 +38,7 @@ export class ConnectionStatusManager { } public findConnectionByProfileId(profileId: string): ConnectionManagementInfo | undefined { - return find(values(this._connections), connection => connection.connectionProfile.id === profileId); + return values(this._connections).find(connection => connection.connectionProfile.id === profileId); } public findConnectionProfile(connectionProfile: IConnectionProfile): ConnectionManagementInfo | undefined { @@ -231,10 +230,10 @@ export class ConnectionStatusManager { public getActiveConnectionProfiles(providers?: string[]): ConnectionProfile[] { let profiles = values(this._connections).map((connectionInfo: ConnectionManagementInfo) => connectionInfo.connectionProfile); // Remove duplicate profiles that may be listed multiple times under different URIs by filtering for profiles that don't have the same ID as an earlier profile in the list - profiles = profiles.filter((profile, index) => firstIndex(profiles, otherProfile => otherProfile.id === profile.id) === index); + profiles = profiles.filter((profile, index) => profiles.findIndex(otherProfile => otherProfile.id === profile.id) === index); if (providers) { - profiles = profiles.filter(f => find(providers, x => x === f.providerName)); + profiles = profiles.filter(f => providers.find(x => x === f.providerName)); } return profiles; } diff --git a/src/sql/platform/connection/common/connectionStore.ts b/src/sql/platform/connection/common/connectionStore.ts index b1de5f9106..1a632eac62 100644 --- a/src/sql/platform/connection/common/connectionStore.ts +++ b/src/sql/platform/connection/common/connectionStore.ts @@ -13,7 +13,6 @@ import { IConnectionProfile, ProfileMatcher } from 'sql/platform/connection/comm import { ICredentialsService } from 'sql/platform/credentials/common/credentialsService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { find } from 'vs/base/common/arrays'; const MAX_CONNECTIONS_DEFAULT = 25; @@ -148,7 +147,7 @@ export class ConnectionStore { public getRecentlyUsedConnections(providers?: string[]): ConnectionProfile[] { let mru = this.mru.slice(); if (providers && providers.length > 0) { - mru = mru.filter(c => find(providers, x => x === c.providerName)); + mru = mru.filter(c => providers.find(x => x === c.providerName)); } return this.convertConfigValuesToConnectionProfiles(mru); } @@ -275,7 +274,7 @@ export class ConnectionStore { if (!withoutConnections) { profilesInConfiguration = this.connectionConfig.getConnections(true); if (providers && providers.length > 0) { - profilesInConfiguration = profilesInConfiguration.filter(x => find(providers, p => p === x.providerName)); + profilesInConfiguration = profilesInConfiguration.filter(x => providers.find(p => p === x.providerName)); } } const groups = this.connectionConfig.getAllGroups(); @@ -313,7 +312,7 @@ export class ConnectionStore { public getGroupFromId(groupId: string): IConnectionProfileGroup | undefined { const groups = this.connectionConfig.getAllGroups(); - return find(groups, group => group.id === groupId); + return groups.find(group => group.id === groupId); } private getMaxRecentConnectionsCount(): number { diff --git a/src/sql/platform/connection/common/providerConnectionInfo.ts b/src/sql/platform/connection/common/providerConnectionInfo.ts index 535ad656b1..b86a1b6ed0 100644 --- a/src/sql/platform/connection/common/providerConnectionInfo.ts +++ b/src/sql/platform/connection/common/providerConnectionInfo.ts @@ -10,7 +10,6 @@ import * as azdata from 'azdata'; import * as Constants from 'sql/platform/connection/common/constants'; import { ICapabilitiesService, ConnectionProviderProperties } from 'sql/platform/capabilities/common/capabilitiesService'; import { assign } from 'vs/base/common/objects'; -import { find } from 'vs/base/common/arrays'; import { ConnectionOptionSpecialType, ServiceOptionType } from 'sql/platform/connection/common/interfaces'; type SettableProperty = 'serverName' | 'authenticationType' | 'databaseName' | 'password' | 'connectionName' | 'userName'; @@ -198,7 +197,7 @@ export class ProviderConnectionInfo extends Disposable implements azdata.Connect return false; } - let optionMetadata = find(this._serverCapabilities.connectionOptions, + let optionMetadata = this._serverCapabilities.connectionOptions.find( option => option.specialValueType === ConnectionOptionSpecialType.password)!; // i guess we are going to assume there is a password field let isPasswordRequired = optionMetadata.isRequired; if (this.providerName === Constants.mssqlProviderName) { @@ -269,7 +268,7 @@ export class ProviderConnectionInfo extends Disposable implements azdata.Connect public getSpecialTypeOptionName(type: string): string | undefined { if (this._serverCapabilities) { - let optionMetadata = find(this._serverCapabilities.connectionOptions, o => o.specialValueType === type); + let optionMetadata = this._serverCapabilities.connectionOptions.find(o => o.specialValueType === type); return !!optionMetadata ? optionMetadata.name : undefined; } else { return type.toString(); @@ -284,7 +283,7 @@ export class ProviderConnectionInfo extends Disposable implements azdata.Connect } public get authenticationTypeDisplayName(): string { - let optionMetadata = this._serverCapabilities ? find(this._serverCapabilities.connectionOptions, o => o.specialValueType === ConnectionOptionSpecialType.authType) : undefined; + let optionMetadata = this._serverCapabilities ? this._serverCapabilities.connectionOptions.find(o => o.specialValueType === ConnectionOptionSpecialType.authType) : undefined; let authType = this.authenticationType; let displayName: string = authType; diff --git a/src/sql/platform/connection/test/common/connectionConfig.test.ts b/src/sql/platform/connection/test/common/connectionConfig.test.ts index f77be58011..378babaadf 100644 --- a/src/sql/platform/connection/test/common/connectionConfig.test.ts +++ b/src/sql/platform/connection/test/common/connectionConfig.test.ts @@ -16,7 +16,6 @@ import * as TypeMoq from 'typemoq'; import { Emitter } from 'vs/base/common/event'; import { deepClone, deepFreeze } from 'vs/base/common/objects'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; -import { find } from 'vs/base/common/arrays'; suite('ConnectionConfig', () => { let capabilitiesService: TypeMoq.Mock; @@ -210,7 +209,7 @@ suite('ConnectionConfig', () => { } for (let group of groups1) { - let g2 = find(groups2, g => g.name === group.name); + let g2 = groups2.find(g => g.name === group.name); // if we couldn't find the group it means they must not be equal if (!g2) { return false; @@ -373,12 +372,12 @@ suite('ConnectionConfig', () => { let allConnections = config.getConnections(false); assert.equal(allConnections.length, testConnections.length); allConnections.forEach(connection => { - let userConnection = find(testConnections, u => u.options['serverName'] === connection.serverName); + let userConnection = testConnections.find(u => u.options['serverName'] === connection.serverName); if (userConnection !== undefined) { assert.notEqual(connection.id, connection.getOptionsKey()); assert.ok(!!connection.id); } else { - let workspaceConnection = find(workspaceConnections, u => u.options['serverName'] === connection.serverName); + let workspaceConnection = workspaceConnections.find(u => u.options['serverName'] === connection.serverName); assert.notEqual(connection.id, connection.getOptionsKey()); assert.equal(workspaceConnection!.id, connection.id); } @@ -394,7 +393,7 @@ suite('ConnectionConfig', () => { let result: ISaveGroupResult = config.saveGroup(groups, newGroups, color, newGroups); assert.ok(!!result); assert.equal(result.groups.length, testGroups.length + 2, 'The result groups length is invalid'); - let newGroup = find(result.groups, g => g.name === 'new-group2'); + let newGroup = result.groups.find(g => g.name === 'new-group2'); assert.equal(result.newGroupId, newGroup!.id, 'The groups id is invalid'); }); @@ -407,7 +406,7 @@ suite('ConnectionConfig', () => { let result: ISaveGroupResult = config.saveGroup(groups, newGroups, color, newGroups); assert.ok(!!result); assert.equal(result.groups.length, testGroups.length + 1, 'The result groups length is invalid'); - let newGroup = find(result.groups, g => g.name === 'g2-5'); + let newGroup = result.groups.find(g => g.name === 'g2-5'); assert.equal(result.newGroupId, newGroup!.id, 'The groups id is invalid'); }); @@ -420,7 +419,7 @@ suite('ConnectionConfig', () => { let result: ISaveGroupResult = config.saveGroup(groups, newGroups, color, newGroups); assert.ok(!!result); assert.equal(result.groups.length, testGroups.length, 'The result groups length is invalid'); - let newGroup = find(result.groups, g => g.name === 'g2-1'); + let newGroup = result.groups.find(g => g.name === 'g2-1'); assert.equal(result.newGroupId, newGroup!.id, 'The groups id is invalid'); }); @@ -530,7 +529,7 @@ suite('ConnectionConfig', () => { let editedGroups = configurationService.inspect('datasource.connectionGroups').userValue!; assert.equal(editedGroups.length, testGroups.length); - let editedGroup = find(editedGroups, group => group.id === 'g2'); + let editedGroup = editedGroups.find(group => group.id === 'g2'); assert.ok(!!editedGroup); assert.equal(editedGroup!.name, 'g-renamed'); }); @@ -547,7 +546,7 @@ suite('ConnectionConfig', () => { assert.fail(); } catch (e) { let groups = configurationService.inspect('datasource.connectionGroups').userValue!; - let originalGroup = find(groups, g => g.id === 'g2'); + let originalGroup = groups.find(g => g.id === 'g2'); assert.ok(!!originalGroup); assert.equal(originalGroup!.name, 'g2'); } @@ -565,7 +564,7 @@ suite('ConnectionConfig', () => { let editedGroups = configurationService.inspect('datasource.connectionGroups').userValue!; assert.equal(editedGroups.length, testGroups.length); - let editedGroup = find(editedGroups, group => group.id === 'g2'); + let editedGroup = editedGroups.find(group => group.id === 'g2'); assert.ok(!!editedGroup); assert.equal(editedGroup!.parentId, 'g3'); }); @@ -622,7 +621,7 @@ suite('ConnectionConfig', () => { let editedConnections = configurationService.inspect('datasource.connections').userValue!; // two assert.equal(editedConnections.length, _testConnections.length); - let editedConnection = find(editedConnections, con => con.id === 'server3-2'); + let editedConnection = editedConnections.find(con => con.id === 'server3-2'); assert.ok(!!editedConnection); assert.equal(editedConnection!.groupId, 'g3'); } @@ -658,7 +657,7 @@ suite('ConnectionConfig', () => { let editedConnections = configurationService.inspect('datasource.connections').userValue!; assert.equal(editedConnections.length, testConnections.length); - let editedConnection = find(editedConnections, con => con.id === 'server3'); + let editedConnection = editedConnections.find(con => con.id === 'server3'); assert.ok(!!editedConnection); assert.equal(editedConnection!.groupId, 'newid'); }); diff --git a/src/sql/platform/connection/test/common/connectionStore.test.ts b/src/sql/platform/connection/test/common/connectionStore.test.ts index 77477cbcbb..09c1fdb0e1 100644 --- a/src/sql/platform/connection/test/common/connectionStore.test.ts +++ b/src/sql/platform/connection/test/common/connectionStore.test.ts @@ -18,7 +18,6 @@ import { ConfigurationTarget } from 'vs/platform/configuration/common/configurat import { mssqlProviderName } from 'sql/platform/connection/common/constants'; import { ConnectionProviderProperties } from 'sql/platform/capabilities/common/capabilitiesService'; import { InMemoryStorageService } from 'vs/platform/storage/common/storage'; -import { find } from 'vs/base/common/arrays'; import { generateUuid } from 'vs/base/common/uuid'; suite('ConnectionStore', () => { @@ -464,7 +463,7 @@ suite('ConnectionStore', () => { const connectionGroups = connectionStore.getConnectionProfileGroups(); for (const group of connectionGroups) { - const foundGroup = find(groups, g => g.id === group.id); + const foundGroup = groups.find(g => g.id === group.id); assert.ok(foundGroup); } }); diff --git a/src/sql/platform/connection/test/node/connectionStatusManager.test.ts b/src/sql/platform/connection/test/node/connectionStatusManager.test.ts index e0ec0942cc..9b285a7252 100644 --- a/src/sql/platform/connection/test/node/connectionStatusManager.test.ts +++ b/src/sql/platform/connection/test/node/connectionStatusManager.test.ts @@ -81,7 +81,7 @@ suite('SQL ConnectionStatusManager tests', () => { capabilitiesService = new TestCapabilitiesService(); connectionProfileObject = new ConnectionProfile(capabilitiesService, connectionProfile); - const environmentService = new EnvironmentService(parseArgs(process.argv, OPTIONS), process.execPath); + const environmentService = new EnvironmentService(parseArgs(process.argv, OPTIONS)); connections = new ConnectionStatusManager(capabilitiesService, new NullLogService(), environmentService, new TestNotificationService()); connection1Id = Utils.generateUri(connectionProfile); connection2Id = 'connection2Id'; diff --git a/src/sql/platform/serialization/common/serializationService.ts b/src/sql/platform/serialization/common/serializationService.ts index b7ecdbebee..7b968542a7 100644 --- a/src/sql/platform/serialization/common/serializationService.ts +++ b/src/sql/platform/serialization/common/serializationService.ts @@ -9,7 +9,6 @@ import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilit import * as azdata from 'azdata'; import { localize } from 'vs/nls'; import { getErrorMessage } from 'vs/base/common/errors'; -import { find } from 'vs/base/common/arrays'; export const SERVICE_ID = 'serializationService'; @@ -96,7 +95,7 @@ export class SerializationService implements ISerializationService { let providerCapabilities = this._capabilitiesService.getLegacyCapabilities(providerId); if (providerCapabilities) { - return find(providerCapabilities.features, f => f.featureName === SERVICE_ID); + return providerCapabilities.features.find(f => f.featureName === SERVICE_ID); } return undefined; diff --git a/src/sql/workbench/api/browser/mainThreadAccountManagement.ts b/src/sql/workbench/api/browser/mainThreadAccountManagement.ts index 9b56f2cc8d..16d0b5ccd3 100644 --- a/src/sql/workbench/api/browser/mainThreadAccountManagement.ts +++ b/src/sql/workbench/api/browser/mainThreadAccountManagement.ts @@ -16,7 +16,6 @@ import { IExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { UpdateAccountListEventParams } from 'sql/platform/accounts/common/eventTypes'; import { values } from 'vs/base/common/collections'; -import { firstIndex } from 'vs/base/common/arrays'; @extHostNamedCustomer(SqlMainContext.MainThreadAccountManagement) export class MainThreadAccountManagement extends Disposable implements MainThreadAccountManagementShape { @@ -38,7 +37,7 @@ export class MainThreadAccountManagement extends Disposable implements MainThrea return; } - const providerMetadataIndex = firstIndex(values(this._providerMetadata), (providerMetadata: azdata.AccountProviderMetadata) => providerMetadata.id === e.providerId); + const providerMetadataIndex = values(this._providerMetadata).findIndex((providerMetadata: azdata.AccountProviderMetadata) => providerMetadata.id === e.providerId); if (providerMetadataIndex === -1) { return; } diff --git a/src/sql/workbench/api/browser/mainThreadDashboardWebview.ts b/src/sql/workbench/api/browser/mainThreadDashboardWebview.ts index 739902b010..ec2d02efb6 100644 --- a/src/sql/workbench/api/browser/mainThreadDashboardWebview.ts +++ b/src/sql/workbench/api/browser/mainThreadDashboardWebview.ts @@ -7,7 +7,6 @@ import { MainThreadDashboardWebviewShape, SqlMainContext, ExtHostDashboardWebvie import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { IExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; import { IDashboardViewService, IDashboardWebview } from 'sql/platform/dashboard/browser/dashboardViewService'; -import { find } from 'vs/base/common/arrays'; @extHostNamedCustomer(SqlMainContext.MainThreadDashboardWebview) export class MainThreadDashboardWebview implements MainThreadDashboardWebviewShape { @@ -24,7 +23,7 @@ export class MainThreadDashboardWebview implements MainThreadDashboardWebviewSha ) { this._proxy = context.getProxy(SqlExtHostContext.ExtHostDashboardWebviews); viewService.onRegisteredWebview(e => { - if (find(this.knownWidgets, x => x === e.id)) { + if (this.knownWidgets.find(x => x === e.id)) { let handle = MainThreadDashboardWebview._handlePool++; this._dialogs.set(handle, e); this._proxy.$registerWidget(handle, e.id, e.connection, e.serverInfo); diff --git a/src/sql/workbench/api/browser/mainThreadModelView.ts b/src/sql/workbench/api/browser/mainThreadModelView.ts index 22837542ce..a407bda894 100644 --- a/src/sql/workbench/api/browser/mainThreadModelView.ts +++ b/src/sql/workbench/api/browser/mainThreadModelView.ts @@ -11,7 +11,6 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IModelViewService } from 'sql/platform/modelComponents/browser/modelViewService'; import { IItemConfig, IComponentShape, IModelView } from 'sql/platform/model/browser/modelViewService'; -import { find } from 'vs/base/common/arrays'; @extHostNamedCustomer(SqlMainContext.MainThreadModelView) @@ -29,7 +28,7 @@ export class MainThreadModelView extends Disposable implements MainThreadModelVi super(); this._proxy = _context.getProxy(SqlExtHostContext.ExtHostModelView); viewService.onRegisteredModelView(view => { - if (find(this.knownWidgets, x => x === view.id)) { + if (this.knownWidgets.find(x => x === view.id)) { let handle = MainThreadModelView._handlePool++; this._dialogs.set(handle, view); this._proxy.$registerWidget(handle, view.id, view.connection, view.serverInfo); diff --git a/src/sql/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts b/src/sql/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts index 0e1684fb59..d1736641da 100644 --- a/src/sql/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts +++ b/src/sql/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts @@ -33,7 +33,6 @@ import { localize } from 'vs/nls'; import { IFileService } from 'vs/platform/files/common/files'; import { UntitledNotebookInput } from 'sql/workbench/contrib/notebook/browser/models/untitledNotebookInput'; import { FileNotebookInput } from 'sql/workbench/contrib/notebook/browser/models/fileNotebookInput'; -import { find } from 'vs/base/common/arrays'; import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; @@ -384,7 +383,7 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements let cell: ICellModel; if (cellUri) { let uriString = URI.revive(cellUri).toString(); - cell = find(editor.cells, c => c.cellUri.toString() === uriString); + cell = editor.cells.find(c => c.cellUri.toString() === uriString); // If it's markdown what should we do? Show notification?? } else { // Use the active cell in this case, or 1st cell if there's none active @@ -406,11 +405,11 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements let endCell: ICellModel; if (startCellUri) { let uriString = URI.revive(startCellUri).toString(); - startCell = find(editor.cells, c => c.cellUri.toString() === uriString); + startCell = editor.cells.find(c => c.cellUri.toString() === uriString); } if (endCellUri) { let uriString = URI.revive(endCellUri).toString(); - endCell = find(editor.cells, c => c.cellUri.toString() === uriString); + endCell = editor.cells.find(c => c.cellUri.toString() === uriString); } return editor.runAllCells(startCell, endCell); } @@ -424,7 +423,7 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements let cell: ICellModel; if (cellUri) { let uriString = URI.revive(cellUri).toString(); - cell = find(editor.cells, c => c.cellUri.toString() === uriString); + cell = editor.cells.find(c => c.cellUri.toString() === uriString); // If it's markdown what should we do? Show notification?? } else { // Use the active cell in this case, or 1st cell if there's none active diff --git a/src/sql/workbench/api/common/extHostAccountManagement.ts b/src/sql/workbench/api/common/extHostAccountManagement.ts index 2f6bef5732..005ef47e54 100644 --- a/src/sql/workbench/api/common/extHostAccountManagement.ts +++ b/src/sql/workbench/api/common/extHostAccountManagement.ts @@ -13,7 +13,6 @@ import { import { AzureResource } from 'sql/workbench/api/common/sqlExtHostTypes'; import { IMainContext } from 'vs/workbench/api/common/extHost.protocol'; import { Event, Emitter } from 'vs/base/common/event'; -import { firstIndex } from 'vs/base/common/arrays'; import { values } from 'vs/base/common/collections'; export class ExtHostAccountManagement extends ExtHostAccountManagementShape { @@ -93,7 +92,7 @@ export class ExtHostAccountManagement extends ExtHostAccountManagementShape { return this.$getAllAccounts().then(() => { for (const handle in this._accounts) { const providerHandle = parseInt(handle); - if (firstIndex(this._accounts[handle], (acct) => acct.key.accountId === account.key.accountId) !== -1) { + if (this._accounts[handle].findIndex((acct) => acct.key.accountId === account.key.accountId) !== -1) { return this._withProvider(providerHandle, (provider: azdata.AccountProvider) => provider.getSecurityToken(account, resource)); } } @@ -106,7 +105,7 @@ export class ExtHostAccountManagement extends ExtHostAccountManagementShape { return this.$getAllAccounts().then(() => { for (const handle in this._accounts) { const providerHandle = parseInt(handle); - if (firstIndex(this._accounts[handle], (acct) => acct.key.accountId === account.key.accountId) !== -1) { + if (this._accounts[handle].findIndex((acct) => acct.key.accountId === account.key.accountId) !== -1) { return this._withProvider(providerHandle, (provider: azdata.AccountProvider) => provider.getAccountSecurityToken(account, tenant, resource)); } } @@ -128,7 +127,7 @@ export class ExtHostAccountManagement extends ExtHostAccountManagementShape { let self = this; // Look for any account providers that have the same provider ID - let matchingProviderIndex = firstIndex(values(this._providers), (provider: AccountProviderWithMetadata) => { + let matchingProviderIndex = values(this._providers).findIndex((provider: AccountProviderWithMetadata) => { return provider.metadata.id === providerMetadata.id; }); if (matchingProviderIndex >= 0) { diff --git a/src/sql/workbench/api/common/extHostDataProtocol.ts b/src/sql/workbench/api/common/extHostDataProtocol.ts index 602a60c443..f787c1d461 100644 --- a/src/sql/workbench/api/common/extHostDataProtocol.ts +++ b/src/sql/workbench/api/common/extHostDataProtocol.ts @@ -12,7 +12,6 @@ import { SqlMainContext, MainThreadDataProtocolShape, ExtHostDataProtocolShape } import { DataProviderType } from 'sql/workbench/api/common/sqlExtHostTypes'; import { IURITransformer } from 'vs/base/common/uriIpc'; import { URI } from 'vs/base/common/uri'; -import { find } from 'vs/base/common/arrays'; import { RunOnceScheduler } from 'vs/base/common/async'; import { mapToSerializable } from 'sql/base/common/map'; @@ -77,7 +76,7 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape { if (!providersForType) { return undefined; } - return find(providersForType, provider => provider.providerId === providerId) as T; + return providersForType.find(provider => provider.providerId === providerId) as T; } public getProvidersByType(providerType: azdata.DataProviderType): T[] { diff --git a/src/sql/workbench/api/common/extHostModelView.ts b/src/sql/workbench/api/common/extHostModelView.ts index f2067bd485..ee9f6f8f73 100644 --- a/src/sql/workbench/api/common/extHostModelView.ts +++ b/src/sql/workbench/api/common/extHostModelView.ts @@ -17,7 +17,6 @@ import * as azdata from 'azdata'; import { SqlMainContext, ExtHostModelViewShape, MainThreadModelViewShape, ExtHostModelViewTreeViewsShape } from 'sql/workbench/api/common/sqlExtHost.protocol'; import { IItemConfig, ModelComponentTypes, IComponentShape, IComponentEventArgs, ComponentEventType, ColumnSizingMode, ModelViewAction } from 'sql/workbench/api/common/sqlExtHostTypes'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { firstIndex } from 'vs/base/common/arrays'; import { ILogService } from 'vs/platform/log/common/log'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -457,7 +456,7 @@ class FormContainerBuilder extends GenericContainerBuilder x.component.id === firstComponent.component.id); + let index = this._component.itemConfigs.findIndex(x => x.component.id === firstComponent.component.id); if (index !== -1) { result = this._component.removeItemAt(index - 1); } @@ -709,7 +708,7 @@ class ComponentWrapper implements azdata.Component { } public removeItem(item: azdata.Component): boolean { - let index = firstIndex(this.itemConfigs, c => c.component.id === item.id); + let index = this.itemConfigs.findIndex(c => c.component.id === item.id); if (index >= 0 && index < this.itemConfigs.length) { return this.removeItemAt(index); } diff --git a/src/sql/workbench/api/common/extHostNotebookEditor.ts b/src/sql/workbench/api/common/extHostNotebookEditor.ts index 80fa6fdcbb..ed7481457c 100644 --- a/src/sql/workbench/api/common/extHostNotebookEditor.ts +++ b/src/sql/workbench/api/common/extHostNotebookEditor.ts @@ -13,7 +13,6 @@ import { readonly } from 'vs/base/common/errors'; import { MainThreadNotebookDocumentsAndEditorsShape } from 'sql/workbench/api/common/sqlExtHost.protocol'; import { ExtHostNotebookDocumentData } from 'sql/workbench/api/common/extHostNotebookDocumentData'; import { CellRange, ISingleNotebookEditOperation, ICellRange } from 'sql/workbench/api/common/sqlExtHostTypes'; -import { find } from 'vs/base/common/arrays'; import { HideInputTag } from 'sql/platform/notebooks/common/outputRegistry'; export interface INotebookEditOperation { @@ -96,7 +95,7 @@ export class NotebookEditorEdit { value.metadata = { tags: [HideInputTag] }; } else if (!value.metadata.tags) { value.metadata.tags = [HideInputTag]; - } else if (!find(value.metadata.tags, x => x === HideInputTag)) { + } else if (!value.metadata.tags.find(x => x === HideInputTag)) { value.metadata.tags.push(HideInputTag); } } diff --git a/src/sql/workbench/api/common/extHostResourceProvider.ts b/src/sql/workbench/api/common/extHostResourceProvider.ts index c008fc9f7d..3479fab60c 100644 --- a/src/sql/workbench/api/common/extHostResourceProvider.ts +++ b/src/sql/workbench/api/common/extHostResourceProvider.ts @@ -12,7 +12,6 @@ import { SqlMainContext, } from 'sql/workbench/api/common/sqlExtHost.protocol'; import { values } from 'vs/base/common/collections'; -import { firstIndex } from 'vs/base/common/arrays'; export class ExtHostResourceProvider extends ExtHostResourceProviderShape { private _handlePool: number = 0; @@ -38,7 +37,7 @@ export class ExtHostResourceProvider extends ExtHostResourceProviderShape { let self = this; // Look for any account providers that have the same provider ID - let matchingProviderIndex = firstIndex(values(this._providers), (provider: ResourceProviderWithMetadata) => { + let matchingProviderIndex = values(this._providers).findIndex((provider: ResourceProviderWithMetadata) => { return provider.metadata.id === providerMetadata.id; }); if (matchingProviderIndex >= 0) { diff --git a/src/sql/workbench/browser/editor/profiler/profilerInput.ts b/src/sql/workbench/browser/editor/profiler/profilerInput.ts index d466197fa8..7468a65bf6 100644 --- a/src/sql/workbench/browser/editor/profiler/profilerInput.ts +++ b/src/sql/workbench/browser/editor/profiler/profilerInput.ts @@ -19,7 +19,6 @@ import * as types from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { FilterData } from 'sql/workbench/services/profiler/browser/profilerFilter'; import { uriPrefixes } from 'sql/platform/connection/common/utils'; -import { find } from 'vs/base/common/arrays'; export interface ColumnDefinition extends Slick.Column { name: string; @@ -208,11 +207,11 @@ export class ProfilerInput extends EditorInput implements IProfilerSession { this._notificationService.error(nls.localize("profiler.sessionCreationError", "Error while starting new session")); } else { this._sessionName = params.sessionName; - let sessionTemplate = find(this._profilerService.getSessionTemplates(), (template) => { + let sessionTemplate = this._profilerService.getSessionTemplates().find((template) => { return template.name === params.templateName; }); if (!types.isUndefinedOrNull(sessionTemplate)) { - let newView = find(this._profilerService.getViewTemplates(), (view) => { + let newView = this._profilerService.getViewTemplates().find((view) => { return view.name === sessionTemplate!.defaultView; }); if (!types.isUndefinedOrNull(newView)) { diff --git a/src/sql/workbench/browser/modal/modal.ts b/src/sql/workbench/browser/modal/modal.ts index 67af20548f..ea31e1071f 100644 --- a/src/sql/workbench/browser/modal/modal.ts +++ b/src/sql/workbench/browser/modal/modal.ts @@ -23,7 +23,6 @@ import { ILogService } from 'vs/platform/log/common/log'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; -import { find, firstIndex } from 'vs/base/common/arrays'; import { IThemable } from 'vs/base/common/styler'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; @@ -436,7 +435,7 @@ export abstract class Modal extends Disposable implements IThemable { * @param onSelect The callback to call when the button is selected */ protected findFooterButton(label: string): Button | undefined { - return find(this._footerButtons, e => { + return this._footerButtons.find(e => { try { return e && e.element.innerText === label; } catch { @@ -450,7 +449,7 @@ export abstract class Modal extends Disposable implements IThemable { * @param label Label on the button */ protected removeFooterButton(label: string): void { - let buttonIndex = firstIndex(this._footerButtons, e => { + let buttonIndex = this._footerButtons.findIndex(e => { return e && e.element && e.element.innerText === label; }); if (buttonIndex > -1 && buttonIndex < this._footerButtons.length) { diff --git a/src/sql/workbench/browser/modelComponents/componentBase.ts b/src/sql/workbench/browser/modelComponents/componentBase.ts index fda779102c..7c0551d4f6 100644 --- a/src/sql/workbench/browser/modelComponents/componentBase.ts +++ b/src/sql/workbench/browser/modelComponents/componentBase.ts @@ -18,7 +18,6 @@ import { URI } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; import { EventType, addDisposableListener } from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { firstIndex } from 'vs/base/common/arrays'; import { IComponentDescriptor, IComponent, IModelStore, IComponentEventArgs, ComponentEventType } from 'sql/platform/dashboard/browser/interfaces'; import { convertSize } from 'sql/base/browser/dom'; @@ -319,7 +318,7 @@ export abstract class ContainerBase item.descriptor.id === componentDescriptor.id && item.descriptor.type === componentDescriptor.type); + let index = this.items.findIndex(item => item.descriptor.id === componentDescriptor.id && item.descriptor.type === componentDescriptor.type); if (index >= 0) { this.items.splice(index, 1); this._changeRef.detectChanges(); diff --git a/src/sql/workbench/browser/modelComponents/declarativeTable.component.ts b/src/sql/workbench/browser/modelComponents/declarativeTable.component.ts index 4759e28586..86bf77f264 100644 --- a/src/sql/workbench/browser/modelComponents/declarativeTable.component.ts +++ b/src/sql/workbench/browser/modelComponents/declarativeTable.component.ts @@ -13,7 +13,7 @@ import * as azdata from 'azdata'; import { ContainerBase } from 'sql/workbench/browser/modelComponents/componentBase'; import { ISelectData } from 'vs/base/browser/ui/selectBox/selectBox'; -import { find, equals as arrayEquals } from 'vs/base/common/arrays'; +import { equals as arrayEquals } from 'vs/base/common/arrays'; import { localize } from 'vs/nls'; import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/platform/dashboard/browser/interfaces'; import { convertSize } from 'sql/base/browser/dom'; @@ -125,7 +125,7 @@ export default class DeclarativeTableComponent extends ContainerBase c.displayName === e); + let category = column.categoryValues.find(c => c.displayName === e); if (category) { this.onCellDataChanged(category.name, rowIdx, colIdx); } else { @@ -184,7 +184,7 @@ export default class DeclarativeTableComponent extends ContainerBase v.name === cellData.value); + let category = column.categoryValues.find(v => v.name === cellData.value); if (category) { return category.displayName; } else if (this.isEditableSelectBox(colIdx)) { diff --git a/src/sql/workbench/browser/modelComponents/dropdown.component.ts b/src/sql/workbench/browser/modelComponents/dropdown.component.ts index 5f9e048e30..8f29ebf723 100644 --- a/src/sql/workbench/browser/modelComponents/dropdown.component.ts +++ b/src/sql/workbench/browser/modelComponents/dropdown.component.ts @@ -19,7 +19,6 @@ import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { find } from 'vs/base/common/arrays'; import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/platform/dashboard/browser/interfaces'; import { localize } from 'vs/nls'; @@ -186,7 +185,7 @@ export default class DropDownComponent extends ComponentBase 0 && this.valuesHaveDisplayName()) { let selectedValue = this.value || this.values[0]; - let valueCategory = find(this.values, v => v.name === selectedValue.name); + let valueCategory = (this.values).find(v => v.name === selectedValue.name); return valueCategory && valueCategory.displayName; } else { if (!this.value && this.values && this.values.length > 0) { @@ -198,7 +197,7 @@ export default class DropDownComponent extends ComponentBasethis.values), v => v.displayName === newValue); + let valueCategory = (this.values).find(v => v.displayName === newValue); this.value = valueCategory; } else { this.value = newValue; diff --git a/src/sql/workbench/browser/modelComponents/formContainer.component.ts b/src/sql/workbench/browser/modelComponents/formContainer.component.ts index 2e96f0e975..4647fb8b23 100644 --- a/src/sql/workbench/browser/modelComponents/formContainer.component.ts +++ b/src/sql/workbench/browser/modelComponents/formContainer.component.ts @@ -12,7 +12,6 @@ import { import { FormLayout, FormItemLayout } from 'azdata'; import { ContainerBase } from 'sql/workbench/browser/modelComponents/componentBase'; -import { find } from 'vs/base/common/arrays'; import { IComponentDescriptor, IComponent, IModelStore } from 'sql/platform/dashboard/browser/interfaces'; import { convertSize } from 'sql/base/browser/dom'; @@ -186,7 +185,7 @@ export default class FormContainer extends ContainerBase impleme let itemConfig = item.config; if (itemConfig && itemConfig.actions) { let resultItems = itemConfig.actions.map(x => { - let actionComponent = find(items, i => i.descriptor.id === x); + let actionComponent = items.find(i => i.descriptor.id === x); return actionComponent; }); diff --git a/src/sql/workbench/browser/modelComponents/modelStore.ts b/src/sql/workbench/browser/modelComponents/modelStore.ts index fe7f5ef756..dbd6399498 100644 --- a/src/sql/workbench/browser/modelComponents/modelStore.ts +++ b/src/sql/workbench/browser/modelComponents/modelStore.ts @@ -5,7 +5,6 @@ import { Deferred } from 'sql/base/common/promise'; import { entries } from 'sql/base/common/collections'; -import { find } from 'vs/base/common/arrays'; import { IComponentDescriptor, IModelStore, IComponent } from 'sql/platform/dashboard/browser/interfaces'; class ComponentDescriptor implements IComponentDescriptor { @@ -65,7 +64,7 @@ export class ModelStore implements IModelStore { } validate(component: IComponent): Thenable { - let componentId = find(entries(this._componentMappings), ([id, mappedComponent]) => component === mappedComponent)[0]; + let componentId = entries(this._componentMappings).find(([id, mappedComponent]) => component === mappedComponent)[0]; return Promise.all(this._validationCallbacks.map(callback => callback(componentId))).then(validations => validations.every(validation => validation === true)); } diff --git a/src/sql/workbench/browser/modelComponents/treeComponentRenderer.ts b/src/sql/workbench/browser/modelComponents/treeComponentRenderer.ts index 2f33759c44..379985c1ae 100644 --- a/src/sql/workbench/browser/modelComponents/treeComponentRenderer.ts +++ b/src/sql/workbench/browser/modelComponents/treeComponentRenderer.ts @@ -5,12 +5,13 @@ import * as dom from 'vs/base/browser/dom'; import { ITree, IRenderer } from 'vs/base/parts/tree/browser/tree'; -import { LIGHT, IThemeService } from 'vs/platform/theme/common/themeService'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; import { Disposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import { ITreeComponentItem } from 'sql/workbench/common/views'; import { TreeViewDataProvider } from './treeViewDataProvider'; import { URI } from 'vs/base/common/uri'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; export enum TreeCheckboxState { Intermediate = 0, @@ -138,7 +139,7 @@ export class TreeComponentRenderer extends Disposable implements IRenderer { * Render a element, given an object bag returned by the template */ public renderElement(tree: ITree, element: ITreeComponentItem, templateId: string, templateData: TreeDataTemplate): void { - const icon = this.themeService.getColorTheme().type === LIGHT ? element.icon : element.iconDark; + const icon = this.themeService.getColorTheme().type === ColorScheme.LIGHT ? element.icon : element.iconDark; const iconUri = icon ? URI.revive(icon) : null; templateData.icon.style.backgroundImage = iconUri ? `url('${iconUri.toString(true)}')` : ''; templateData.icon.style.backgroundRepeat = 'no-repeat'; diff --git a/src/sql/workbench/contrib/assessment/browser/asmtResultsView.component.ts b/src/sql/workbench/contrib/assessment/browser/asmtResultsView.component.ts index c391bfb80f..6ea3ed2250 100644 --- a/src/sql/workbench/contrib/assessment/browser/asmtResultsView.component.ts +++ b/src/sql/workbench/contrib/assessment/browser/asmtResultsView.component.ts @@ -20,7 +20,6 @@ import { IDashboardService } from 'sql/platform/dashboard/browser/dashboardServi import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IColorTheme } from 'vs/platform/theme/common/themeService'; import { attachButtonStyler } from 'sql/platform/theme/common/styler'; -import { find } from 'vs/base/common/arrays'; import { RowDetailView, ExtendedItem } from 'sql/base/browser/ui/table/plugins/rowDetailView'; import { IAssessmentComponent, @@ -478,13 +477,13 @@ export class AsmtResultsViewComponent extends TabChild implements IAssessmentCom let filterValues = col.filterValues; if (filterValues && filterValues.length > 0) { if (item._parent) { - value = value && find(filterValues, x => x === item._parent[col.field]); + value = value && filterValues.find(x => x === item._parent[col.field]); } else { let colValue = item[col.field]; if (colValue instanceof Array) { - value = value && find(filterValues, x => colValue.indexOf(x) >= 0); + value = value && filterValues.find(x => colValue.indexOf(x) >= 0); } else { - value = value && find(filterValues, x => x === colValue); + value = value && filterValues.find(x => x === colValue); } } } diff --git a/src/sql/workbench/contrib/commandLine/electron-browser/commandLine.ts b/src/sql/workbench/contrib/commandLine/electron-browser/commandLine.ts index 9a207ad20c..7ac5a8497b 100644 --- a/src/sql/workbench/contrib/commandLine/electron-browser/commandLine.ts +++ b/src/sql/workbench/contrib/commandLine/electron-browser/commandLine.ts @@ -10,7 +10,6 @@ import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectio import { equalsIgnoreCase } from 'vs/base/common/strings'; import { IConnectionManagementService, IConnectionCompletionOptions, ConnectionType, RunQueryOnConnectionMode } from 'sql/platform/connection/common/connectionManagement'; import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; -import { ParsedArgs } from 'vs/platform/environment/node/argv'; import * as Constants from 'sql/platform/connection/common/constants'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -28,9 +27,8 @@ import { openNewQuery } from 'sql/workbench/contrib/query/browser/queryActions'; import { IURLService, IURLHandler } from 'vs/platform/url/common/url'; import { getErrorMessage } from 'vs/base/common/errors'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { find } from 'vs/base/common/arrays'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; +import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; export interface SqlArgs { _?: string[]; @@ -79,7 +77,7 @@ export class CommandLineWorkbenchContribution implements IWorkbenchContribution, @IDialogService private readonly dialogService: IDialogService ) { if (ipc) { - ipc.on('ads:processCommandLine', (event: any, args: ParsedArgs) => this.onLaunched(args)); + ipc.on('ads:processCommandLine', (event: any, args: NativeParsedArgs) => this.onLaunched(args)); } // we only get the ipc from main during window reuse if (environmentService) { @@ -90,7 +88,7 @@ export class CommandLineWorkbenchContribution implements IWorkbenchContribution, } } - private onLaunched(args: ParsedArgs) { + private onLaunched(args: NativeParsedArgs) { let sqlProvider = this._capabilitiesService.getCapabilities(Constants.mssqlProviderName); // We can't connect to object explorer until the MSSQL connection provider is registered if (sqlProvider) { @@ -310,7 +308,7 @@ export class CommandLineWorkbenchContribution implements IWorkbenchContribution, if (groups && groups.length > 0) { let rootGroup = groups[0]; let connections = ConnectionProfileGroup.getConnectionsInGroup(rootGroup); - match = find(connections, (c) => this.matchProfile(profile, c)); + match = connections.find((c) => this.matchProfile(profile, c)); } return match ? match : profile; } diff --git a/src/sql/workbench/contrib/commandLine/test/electron-browser/commandLine.test.ts b/src/sql/workbench/contrib/commandLine/test/electron-browser/commandLine.test.ts index 3e025918ca..42cb647ef4 100644 --- a/src/sql/workbench/contrib/commandLine/test/electron-browser/commandLine.test.ts +++ b/src/sql/workbench/contrib/commandLine/test/electron-browser/commandLine.test.ts @@ -10,7 +10,6 @@ import { ConnectionProfile } from 'sql/platform/connection/common/connectionProf import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup'; import { CommandLineWorkbenchContribution, SqlArgs } from 'sql/workbench/contrib/commandLine/electron-browser/commandLine'; import * as Constants from 'sql/platform/connection/common/constants'; -import { ParsedArgs } from 'vs/platform/environment/node/argv'; import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; import { TestCapabilitiesService } from 'sql/platform/capabilities/test/common/testCapabilitiesService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -34,8 +33,9 @@ import { isUndefinedOrNull } from 'vs/base/common/types'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { FileQueryEditorInput } from 'sql/workbench/contrib/query/common/fileQueryEditorInput'; import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; +import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; -class TestParsedArgs implements ParsedArgs, SqlArgs { +class TestParsedArgs implements NativeParsedArgs, SqlArgs { [arg: string]: any; _: string[]; database?: string; diff --git a/src/sql/workbench/contrib/dashboard/browser/containers/dashboardContainer.contribution.ts b/src/sql/workbench/contrib/dashboard/browser/containers/dashboardContainer.contribution.ts index 4b0526de79..f404a64c5f 100644 --- a/src/sql/workbench/contrib/dashboard/browser/containers/dashboardContainer.contribution.ts +++ b/src/sql/workbench/contrib/dashboard/browser/containers/dashboardContainer.contribution.ts @@ -13,7 +13,6 @@ import { WIDGETS_CONTAINER, validateWidgetContainerContribution } from 'sql/work import { GRID_CONTAINER, validateGridContainerContribution } from 'sql/workbench/contrib/dashboard/browser/containers/dashboardGridContainer.contribution'; import { WEBVIEW_CONTAINER } from 'sql/workbench/contrib/dashboard/browser/containers/dashboardWebviewContainer.contribution'; import { values } from 'vs/base/common/collections'; -import { find } from 'vs/base/common/arrays'; import { NavSectionConfig } from 'sql/workbench/contrib/dashboard/browser/core/dashboardWidget'; const containerTypes = [ @@ -78,7 +77,7 @@ ExtensionsRegistry.registerExtensionPoint c === containerkey); + const containerTypeFound = containerTypes.find(c => c === containerkey); if (!containerTypeFound) { extension.collector.error(localize('dashboardTab.contribution.unKnownContainerType', "Unknown container type defines in dashboard container for extension.")); return; diff --git a/src/sql/workbench/contrib/dashboard/browser/containers/dashboardGridContainer.contribution.ts b/src/sql/workbench/contrib/dashboard/browser/containers/dashboardGridContainer.contribution.ts index 21fd258500..2993fa85da 100644 --- a/src/sql/workbench/contrib/dashboard/browser/containers/dashboardGridContainer.contribution.ts +++ b/src/sql/workbench/contrib/dashboard/browser/containers/dashboardGridContainer.contribution.ts @@ -9,7 +9,6 @@ import * as nls from 'vs/nls'; import { generateDashboardGridLayoutSchema } from 'sql/workbench/contrib/dashboard/browser/pages/dashboardPageContribution'; import { registerContainerType, registerNavSectionContainerType } from 'sql/platform/dashboard/common/dashboardContainerRegistry'; -import { find } from 'vs/base/common/arrays'; export const GRID_CONTAINER = 'grid-container'; @@ -26,7 +25,7 @@ export function validateGridContainerContribution(extension: IExtensionPointUser let result = true; gridConfigs.forEach(widgetConfig => { const allKeys = Object.keys(widgetConfig); - const widgetOrWebviewKey = find(allKeys, key => key === 'widget' || key === 'webview'); + const widgetOrWebviewKey = allKeys.find(key => key === 'widget' || key === 'webview'); if (!widgetOrWebviewKey) { result = false; extension.collector.error(nls.localize('gridContainer.invalidInputs', "widgets or webviews are expected inside widgets-container for extension.")); 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 517e97c493..88488432eb 100644 --- a/src/sql/workbench/contrib/dashboard/browser/containers/dashboardNavSection.component.ts +++ b/src/sql/workbench/contrib/dashboard/browser/containers/dashboardNavSection.component.ts @@ -19,7 +19,6 @@ import * as dashboardHelper from 'sql/workbench/contrib/dashboard/browser/core/d import { Event, Emitter } from 'vs/base/common/event'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ILogService } from 'vs/platform/log/common/log'; -import { find } from 'vs/base/common/arrays'; import { values } from 'vs/base/common/collections'; @Component({ @@ -129,7 +128,7 @@ export class DashboardNavSection extends DashboardTab implements OnDestroy, OnCh } private addNewTab(tab: TabConfig): void { - const existedTab = find(this.tabs, i => i.id === tab.id); + const existedTab = this.tabs.find(i => i.id === tab.id); if (!existedTab) { this.tabs.push(tab); this._cd.detectChanges(); diff --git a/src/sql/workbench/contrib/dashboard/browser/containers/dashboardWidgetContainer.contribution.ts b/src/sql/workbench/contrib/dashboard/browser/containers/dashboardWidgetContainer.contribution.ts index 56709eb58f..e96318ed49 100644 --- a/src/sql/workbench/contrib/dashboard/browser/containers/dashboardWidgetContainer.contribution.ts +++ b/src/sql/workbench/contrib/dashboard/browser/containers/dashboardWidgetContainer.contribution.ts @@ -9,7 +9,6 @@ import * as nls from 'vs/nls'; import { generateDashboardWidgetSchema } from 'sql/workbench/contrib/dashboard/browser/pages/dashboardPageContribution'; import { registerContainerType, registerNavSectionContainerType } from 'sql/platform/dashboard/common/dashboardContainerRegistry'; -import { find } from 'vs/base/common/arrays'; export const WIDGETS_CONTAINER = 'widgets-container'; @@ -26,7 +25,7 @@ export function validateWidgetContainerContribution(extension: IExtensionPointUs let result = true; WidgetConfigs.forEach(widgetConfig => { const allKeys = Object.keys(widgetConfig); - const widgetKey = find(allKeys, key => key === 'widget'); + const widgetKey = allKeys.find(key => key === 'widget'); if (!widgetKey) { result = false; extension.collector.error(nls.localize('widgetContainer.invalidInputs', "The list of widgets is expected inside widgets-container for extension.")); diff --git a/src/sql/workbench/contrib/dashboard/browser/contents/widgetContent.component.ts b/src/sql/workbench/contrib/dashboard/browser/contents/widgetContent.component.ts index a7deac9c3d..28c046739c 100644 --- a/src/sql/workbench/contrib/dashboard/browser/contents/widgetContent.component.ts +++ b/src/sql/workbench/contrib/dashboard/browser/contents/widgetContent.component.ts @@ -21,7 +21,6 @@ import { Event, Emitter } from 'vs/base/common/event'; import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { getContentHeight, addDisposableListener, EventType } from 'vs/base/browser/dom'; -import { find, firstIndex } from 'vs/base/common/arrays'; /** * Sorting function for dashboard widgets @@ -209,10 +208,10 @@ export class WidgetContent extends AngularDisposable implements AfterViewInit { this._grid.enableResize(); this._grid.enableDrag(); this._editDispose.push(this.dashboardService.onDeleteWidget(e => { - let index = firstIndex(this.widgets, i => i.id === e); + let index = this.widgets.findIndex(i => i.id === e); this.widgets.splice(index, 1); - index = firstIndex(this.originalConfig, i => i.id === e); + index = this.originalConfig.findIndex(i => i.id === e); this.originalConfig.splice(index, 1); this._rewriteConfig(); @@ -221,7 +220,7 @@ export class WidgetContent extends AngularDisposable implements AfterViewInit { this._editDispose.push(subscriptionToDisposable(this._grid.onResizeStop.subscribe((e: NgGridItem) => { this._onResize.fire(); const event = e.getEventOutput(); - const config = find(this.originalConfig, i => i.id === event.payload.id); + const config = this.originalConfig.find(i => i.id === event.payload.id); if (!config.gridItemConfig) { config.gridItemConfig = {}; @@ -239,7 +238,7 @@ export class WidgetContent extends AngularDisposable implements AfterViewInit { this._onResize.fire(); const event = e.getEventOutput(); this._items.forEach(i => { - const config = find(this.originalConfig, j => j.id === i.getEventOutput().payload.id); + const config = this.originalConfig.find(j => j.id === i.getEventOutput().payload.id); if ((config.gridItemConfig && config.gridItemConfig.col) || config.id === event.payload.id) { if (!config.gridItemConfig) { config.gridItemConfig = {}; diff --git a/src/sql/workbench/contrib/dashboard/browser/core/actions.ts b/src/sql/workbench/contrib/dashboard/browser/core/actions.ts index fa4600904c..80abad8e2b 100644 --- a/src/sql/workbench/contrib/dashboard/browser/core/actions.ts +++ b/src/sql/workbench/contrib/dashboard/browser/core/actions.ts @@ -11,7 +11,6 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IAngularEventingService, AngularEventType, IAngularEvent } from 'sql/platform/angularEventing/browser/angularEventingService'; import { INewDashboardTabDialogService } from 'sql/workbench/services/dashboard/browser/newDashboardTabDialog'; import { IDashboardTab } from 'sql/workbench/services/dashboard/browser/common/interfaces'; -import { find, firstIndex } from 'vs/base/common/arrays'; import { CellContext } from 'sql/workbench/contrib/notebook/browser/cellViews/codeActions'; import { ILogService } from 'vs/platform/log/common/log'; @@ -202,14 +201,14 @@ export class AddFeatureTabAction extends Action { case AngularEventType.NEW_TABS: const openedTabs = event.payload.dashboardTabs; openedTabs.forEach(tab => { - const existedTab = find(this._openedTabs, i => i === tab); + const existedTab = this._openedTabs.find(i => i === tab); if (!existedTab) { this._openedTabs.push(tab); } }); break; case AngularEventType.CLOSE_TAB: - const index = firstIndex(this._openedTabs, i => i.id === event.payload.id); + const index = this._openedTabs.findIndex(i => i.id === event.payload.id); this._openedTabs.splice(index, 1); break; } diff --git a/src/sql/workbench/contrib/dashboard/browser/core/dashboardHelper.ts b/src/sql/workbench/contrib/dashboard/browser/core/dashboardHelper.ts index 3bdb55c72f..74e60503fa 100644 --- a/src/sql/workbench/contrib/dashboard/browser/core/dashboardHelper.ts +++ b/src/sql/workbench/contrib/dashboard/browser/core/dashboardHelper.ts @@ -22,7 +22,6 @@ import { IDashboardContainerRegistry, Extensions as DashboardContainerExtensions import { SingleConnectionManagementService } from 'sql/workbench/services/bootstrap/browser/commonServiceInterface.service'; import * as Constants from 'sql/platform/connection/common/constants'; import { ILogService } from 'vs/platform/log/common/log'; -import { find } from 'vs/base/common/arrays'; const dashboardcontainerRegistry = Registry.as(DashboardContainerExtensions.dashboardContainerContributions); const containerTypes = [ @@ -163,7 +162,7 @@ function hasCompatibleProvider(provider: string | string[], contextKeyService: I const connectionProvider = contextKeyService.getContextKeyValue(Constants.connectionProviderContextKey); if (connectionProvider) { const providers = (provider instanceof Array) ? provider : [provider]; - const matchingProvider = find(providers, (p) => p === connectionProvider || p === Constants.anyProviderName); + const matchingProvider = providers.find((p) => p === connectionProvider || p === Constants.anyProviderName); isCompatible = (matchingProvider !== undefined); } // Else there's no connection context so skip the check return isCompatible; @@ -175,7 +174,7 @@ function hasCompatibleProvider(provider: string | string[], contextKeyService: I */ export function getDashboardContainer(container: object, logService: ILogService): { result: boolean, message: string, container: { [key: string]: any } } { const key = Object.keys(container)[0]; - const containerTypeFound = find(containerTypes, c => (c === key)); + const containerTypeFound = containerTypes.find(c => (c === key)); if (!containerTypeFound) { const dashboardContainer = dashboardcontainerRegistry.getRegisteredContainer(key); if (!dashboardContainer) { 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 8a3cd2e88d..880cbb21b8 100644 --- a/src/sql/workbench/contrib/dashboard/browser/core/dashboardPage.component.ts +++ b/src/sql/workbench/contrib/dashboard/browser/core/dashboardPage.component.ts @@ -36,7 +36,6 @@ import Severity from 'vs/base/common/severity'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IContextKeyService, ContextKeyExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ILogService } from 'vs/platform/log/common/log'; -import { firstIndex, find } from 'vs/base/common/arrays'; import { values } from 'vs/base/common/collections'; import { RefreshWidgetAction, ToolbarAction } from 'sql/workbench/contrib/dashboard/browser/core/actions'; import { Taskbar, ITaskbarContent } from 'sql/base/browser/ui/taskbar/taskbar'; @@ -187,7 +186,7 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig primary.forEach(a => { if (a instanceof MenuItemAction) { // Need to ensure that we don't add the same action multiple times - let foundIndex = firstIndex(tasks, act => act.action && act.action.id === a.id); + let foundIndex = tasks.findIndex(act => act.action && act.action.id === a.id); if (foundIndex < 0) { tasks.push({ action: a }); } @@ -318,7 +317,7 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig this._cd.detectChanges(); this._tabsDispose.push(this.dashboardService.onPinUnpinTab(e => { - const tabConfig = find(this._tabSettingConfigs, i => i.tabId === e.tabId); + const tabConfig = this._tabSettingConfigs.find(i => i.tabId === e.tabId); if (tabConfig) { tabConfig.isPinned = e.isPinned; } else { @@ -395,7 +394,7 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig iconClass: 'home-tab-icon' }; - const homeTabIndex = firstIndex(allTabs, (tab) => tab.isHomeTab === true); + const homeTabIndex = allTabs.findIndex(tab => tab.isHomeTab === true); if (homeTabIndex !== undefined && homeTabIndex > -1) { // Have a tab: get its information and copy over to the home tab definition const homeTab = allTabs.splice(homeTabIndex, 1)[0]; @@ -491,7 +490,7 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig } private addNewTab(tab: TabConfig): void { - const existedTab = find(this.tabs, i => i.id === tab.id); + const existedTab = this.tabs.find(i => i.id === tab.id); if (!existedTab) { if (!tab.iconClass && tab.type !== 'group-header') { tab.iconClass = 'default-tab-icon'; @@ -553,7 +552,7 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig } public handleTabClose(tab: TabComponent): void { - const index = firstIndex(this.tabs, i => i.id === tab.identifier); + const index = this.tabs.findIndex(i => i.id === tab.identifier); this.tabs.splice(index, 1); this.angularEventingService.sendAngularEvent(this.dashboardService.getUnderlyingUri(), AngularEventType.CLOSE_TAB, { id: tab.identifier }); } diff --git a/src/sql/workbench/contrib/dashboard/browser/dashboardRegistry.ts b/src/sql/workbench/contrib/dashboard/browser/dashboardRegistry.ts index 8c81535d95..1e8827e17c 100644 --- a/src/sql/workbench/contrib/dashboard/browser/dashboardRegistry.ts +++ b/src/sql/workbench/contrib/dashboard/browser/dashboardRegistry.ts @@ -12,7 +12,6 @@ import { IExtensionPointUser, ExtensionsRegistry } from 'vs/workbench/services/e import { DATABASE_DASHBOARD_TABS } from 'sql/workbench/contrib/dashboard/browser/pages/databaseDashboardPage.contribution'; import { SERVER_DASHBOARD_TABS } from 'sql/workbench/contrib/dashboard/browser/pages/serverDashboardPage.contribution'; import { DASHBOARD_CONFIG_ID, DASHBOARD_TABS_KEY_PROPERTY } from 'sql/workbench/contrib/dashboard/browser/pages/dashboardPageContribution'; -import { find } from 'vs/base/common/arrays'; import { IDashboardTab, IDashboardTabGroup } from 'sql/workbench/services/dashboard/browser/common/interfaces'; import { ILogService } from 'vs/platform/log/common/log'; @@ -169,7 +168,7 @@ class DashboardRegistry implements IDashboardRegistry { public registerTab(tab: IDashboardTab): void { this._tabs.push(tab); - let dashboardConfig = find(this._configurationRegistry.getConfigurations(), c => c.id === DASHBOARD_CONFIG_ID); + let dashboardConfig = this._configurationRegistry.getConfigurations().find(c => c.id === DASHBOARD_CONFIG_ID); if (dashboardConfig) { let dashboardDatabaseTabProperty = (dashboardConfig.properties[DATABASE_DASHBOARD_TABS].items).properties[DASHBOARD_TABS_KEY_PROPERTY]; diff --git a/src/sql/workbench/contrib/editData/browser/editDataActions.ts b/src/sql/workbench/contrib/editData/browser/editDataActions.ts index c8b5cbd205..a4c0f3b292 100644 --- a/src/sql/workbench/contrib/editData/browser/editDataActions.ts +++ b/src/sql/workbench/contrib/editData/browser/editDataActions.ts @@ -16,7 +16,6 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import Severity from 'vs/base/common/severity'; import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { firstIndex } from 'vs/base/common/arrays'; const $ = dom.$; /** @@ -196,7 +195,7 @@ export class ChangeMaxRowsActionItem extends Disposable implements IActionViewIt } public set setCurrentOptionIndex(selection: number) { - this._currentOptionsIndex = firstIndex(this._options, x => x === selection.toString()); + this._currentOptionsIndex = this._options.findIndex(x => x === selection.toString()); this._refreshOptions(); } @@ -214,7 +213,7 @@ export class ChangeMaxRowsActionItem extends Disposable implements IActionViewIt private _registerListeners(): void { this._register(this.selectBox.onDidSelect(selection => { - this._currentOptionsIndex = firstIndex(this._options, x => x === selection.selected); + this._currentOptionsIndex = this._options.findIndex(x => x === selection.selected); this._editor.editDataInput.onRowDropDownSet(Number(selection.selected)); })); this._register(attachSelectBoxStyler(this.selectBox, this._themeService)); diff --git a/src/sql/workbench/contrib/jobManagement/browser/jobHistory.component.ts b/src/sql/workbench/contrib/jobManagement/browser/jobHistory.component.ts index 984c626bf8..b26ece7bed 100644 --- a/src/sql/workbench/contrib/jobManagement/browser/jobHistory.component.ts +++ b/src/sql/workbench/contrib/jobManagement/browser/jobHistory.component.ts @@ -33,7 +33,6 @@ import { IDashboardService } from 'sql/platform/dashboard/browser/dashboardServi import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; -import { find } from 'vs/base/common/arrays'; export const DASHBOARD_SELECTOR: string = 'jobhistory-component'; @@ -199,10 +198,10 @@ export class JobHistoryComponent extends JobManagementView implements OnInit { const self = this; let cachedHistory = self._jobCacheObject.getJobHistory(element.jobID); if (cachedHistory) { - self.agentJobHistoryInfo = find(cachedHistory, + self.agentJobHistoryInfo = cachedHistory.find( history => self.formatTime(history.runDate) === self.formatTime(element.runDate)); } else { - self.agentJobHistoryInfo = find(self._treeController.jobHistories, + self.agentJobHistoryInfo = self._treeController.jobHistories.find( history => self.formatTime(history.runDate) === self.formatTime(element.runDate)); } if (self.agentJobHistoryInfo) { diff --git a/src/sql/workbench/contrib/jobManagement/browser/jobsView.component.ts b/src/sql/workbench/contrib/jobManagement/browser/jobsView.component.ts index 321782e1a2..cc8ae5e182 100644 --- a/src/sql/workbench/contrib/jobManagement/browser/jobsView.component.ts +++ b/src/sql/workbench/contrib/jobManagement/browser/jobsView.component.ts @@ -32,7 +32,6 @@ import { tableBackground, cellBackground, cellBorderColor } from 'sql/platform/t import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; import { attachButtonStyler } from 'sql/platform/theme/common/styler'; -import { find } from 'vs/base/common/arrays'; import { IColorTheme } from 'vs/platform/theme/common/themeService'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -311,7 +310,7 @@ export class JobsViewComponent extends JobManagementView implements OnInit, OnDe this._table.grid.removeCellCssStyles('error-row' + i.toString()); let item = this.dataView.getItemByIdx(i); // current filter - if (find(filterValues, x => x === item[args.column.field])) { + if (filterValues.find(x => x === item[args.column.field])) { // check all previous filters if (this.checkPreviousFilters(item)) { if (item.lastRunOutcome === 'Failed') { @@ -563,7 +562,9 @@ export class JobsViewComponent extends JobManagementView implements OnInit, OnDe private checkPreviousFilters(item): boolean { for (let column in this.filterValueMap) { if (column !== 'start' && this.filterValueMap[column][0].length > 0) { - if (!find(this.filterValueMap[column][0], x => x === item[JobManagementUtilities.convertColNameToField(column)])) { + let temp = this.filterValueMap[column][0] as unknown; + let arr = temp as []; + if (!arr.find(x => x === item[JobManagementUtilities.convertColNameToField(column)])) { return false; } } @@ -705,9 +706,9 @@ export class JobsViewComponent extends JobManagementView implements OnInit, OnDe let filterValues = col.filterValues; if (filterValues && filterValues.length > 0) { if (item._parent) { - value = value && find(filterValues, x => x === item._parent[col.field]); + value = value && filterValues.find(x => x === item._parent[col.field]); } else { - value = value && find(filterValues, x => x === item[col.field]); + value = value && filterValues.find(x => x === item[col.field]); } } } diff --git a/src/sql/workbench/contrib/jobManagement/browser/notebooksView.component.ts b/src/sql/workbench/contrib/jobManagement/browser/notebooksView.component.ts index cc64fc9d8b..21f48a7043 100644 --- a/src/sql/workbench/contrib/jobManagement/browser/notebooksView.component.ts +++ b/src/sql/workbench/contrib/jobManagement/browser/notebooksView.component.ts @@ -33,7 +33,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; import { attachButtonStyler } from 'sql/platform/theme/common/styler'; import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar'; -import { find } from 'vs/base/common/arrays'; import { onUnexpectedError } from 'vs/base/common/errors'; import { IColorTheme } from 'vs/platform/theme/common/themeService'; @@ -319,7 +318,7 @@ export class NotebooksViewComponent extends JobManagementView implements OnInit, this._table.grid.removeCellCssStyles('notebook-error-row' + i.toString()); let item = this.dataView.getItemByIdx(i); // current filter - if (!!find(filterValues, x => x === item[args.column.field])) { + if (!!filterValues.find(x => x === item[args.column.field])) { // check all previous filters if (this.checkPreviousFilters(item)) { if (item.lastRunOutcome === 'Failed') { @@ -612,7 +611,9 @@ export class NotebooksViewComponent extends JobManagementView implements OnInit, private checkPreviousFilters(item): boolean { for (let column in this.filterValueMap) { if (column !== 'start' && this.filterValueMap[column][0].length > 0) { - if (!find(this.filterValueMap[column][0], x => x === item[JobManagementUtilities.convertColNameToField(column)])) { + let temp = this.filterValueMap[column][0] as unknown; + let arr = temp as []; + if (!arr.find(x => x === item[JobManagementUtilities.convertColNameToField(column)])) { return false; } } @@ -771,9 +772,9 @@ export class NotebooksViewComponent extends JobManagementView implements OnInit, let filterValues = col.filterValues; if (filterValues && filterValues.length > 0) { if (item._parent) { - value = value && find(filterValues, x => x === item._parent[col.field]); + value = value && filterValues.find(x => x === item._parent[col.field]); } else { - value = value && find(filterValues, x => x === item[col.field]); + value = value && filterValues.find(x => x === item[col.field]); } } } diff --git a/src/sql/workbench/contrib/notebook/browser/cellToolbarActions.ts b/src/sql/workbench/contrib/notebook/browser/cellToolbarActions.ts index 82373288cf..04b601c3d4 100644 --- a/src/sql/workbench/contrib/notebook/browser/cellToolbarActions.ts +++ b/src/sql/workbench/contrib/notebook/browser/cellToolbarActions.ts @@ -12,7 +12,6 @@ import { CellActionBase, CellContext } from 'sql/workbench/contrib/notebook/brow import { CellModel } from 'sql/workbench/services/notebook/browser/models/cell'; import { CellTypes, CellType } from 'sql/workbench/services/notebook/common/contracts'; import { ToggleableAction } from 'sql/workbench/contrib/notebook/browser/notebookActions'; -import { firstIndex } from 'vs/base/common/arrays'; import { getErrorMessage } from 'vs/base/common/errors'; import Severity from 'vs/base/common/severity'; import { INotebookService } from 'sql/workbench/services/notebook/browser/notebookService'; @@ -218,7 +217,7 @@ export class AddCellFromContextAction extends CellActionBase { doRun(context: CellContext): Promise { try { let model = context.model; - let index = firstIndex(model.cells, (cell) => cell.id === context.cell.id); + let index = model.cells.findIndex((cell) => cell.id === context.cell.id); if (index !== undefined && this.isAfter) { index += 1; } diff --git a/src/sql/workbench/contrib/notebook/browser/models/cellMagicMapper.ts b/src/sql/workbench/contrib/notebook/browser/models/cellMagicMapper.ts index 80c8fa23b6..b869d5361b 100644 --- a/src/sql/workbench/contrib/notebook/browser/models/cellMagicMapper.ts +++ b/src/sql/workbench/contrib/notebook/browser/models/cellMagicMapper.ts @@ -5,7 +5,6 @@ import { ICellMagicMapper } from 'sql/workbench/services/notebook/browser/models/modelInterfaces'; import { ILanguageMagic } from 'sql/workbench/services/notebook/browser/notebookService'; -import { find } from 'vs/base/common/arrays'; const defaultKernel = '*'; export class CellMagicMapper implements ICellMagicMapper { @@ -40,7 +39,7 @@ export class CellMagicMapper implements ICellMagicMapper { searchText = searchText.toLowerCase(); let kernelMagics = this.kernelToMagicMap.get(kernelId) || []; if (kernelMagics) { - return find(kernelMagics, m => m.magic.toLowerCase() === searchText); + return kernelMagics.find(m => m.magic.toLowerCase() === searchText); } return undefined; } diff --git a/src/sql/workbench/contrib/notebook/browser/notebook.component.ts b/src/sql/workbench/contrib/notebook/browser/notebook.component.ts index 34769e4457..e2e409e25e 100644 --- a/src/sql/workbench/contrib/notebook/browser/notebook.component.ts +++ b/src/sql/workbench/contrib/notebook/browser/notebook.component.ts @@ -50,7 +50,6 @@ import { Button } from 'sql/base/browser/ui/button/button'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { IBootstrapParams } from 'sql/workbench/services/bootstrap/common/bootstrapParams'; import { getErrorMessage, onUnexpectedError } from 'vs/base/common/errors'; -import { find, firstIndex } from 'vs/base/common/arrays'; import { CodeCellComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/codeCell.component'; import { TextCellComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/textCell.component'; import { NotebookInput } from 'sql/workbench/contrib/notebook/browser/models/notebookInput'; @@ -364,7 +363,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe if (DEFAULT_NOTEBOOK_PROVIDER === providerInfo.providerId) { let providers = notebookUtils.getProvidersForFileName(this._notebookParams.notebookUri.fsPath, this.notebookService); - let tsqlProvider = find(providers, provider => provider === SQL_NOTEBOOK_PROVIDER); + let tsqlProvider = providers.find(provider => provider === SQL_NOTEBOOK_PROVIDER); providerInfo.providerId = tsqlProvider ? SQL_NOTEBOOK_PROVIDER : providers[0]; } } @@ -411,7 +410,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe } findCellIndex(cellModel: ICellModel): number { - return firstIndex(this._model.cells, (cell) => cell.id === cellModel.id); + return this._model.cells.findIndex((cell) => cell.id === cellModel.id); } private setViewInErrorState(error: any): any { @@ -607,7 +606,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe private addPrimaryContributedActions(primary: IAction[]) { for (let action of primary) { // Need to ensure that we don't add the same action multiple times - let foundIndex = firstIndex(this._providerRelatedActions, act => act.id === action.id); + let foundIndex = this._providerRelatedActions.findIndex(act => act.id === action.id); if (foundIndex < 0) { this._actionBar.addAction(action); this._providerRelatedActions.push(action); @@ -658,7 +657,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe public async runCell(cell: ICellModel): Promise { await this.modelReady; let uriString = cell.cellUri.toString(); - if (firstIndex(this._model.cells, c => c.cellUri.toString() === uriString) > -1) { + if (this._model.cells.findIndex(c => c.cellUri.toString() === uriString) > -1) { this.selectCell(cell); return cell.runCell(this.notificationService, this.connectionManagementService); } else { @@ -674,10 +673,10 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe let startIndex = 0; let endIndex = codeCells.length; if (!isUndefinedOrNull(startCell)) { - startIndex = firstIndex(codeCells, c => c.id === startCell.id); + startIndex = codeCells.findIndex(c => c.id === startCell.id); } if (!isUndefinedOrNull(endCell)) { - endIndex = firstIndex(codeCells, c => c.id === endCell.id); + endIndex = codeCells.findIndex(c => c.id === endCell.id); } for (let i = startIndex; i < endIndex; i++) { let cellStatus = await this.runCell(codeCells[i]); @@ -693,7 +692,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe try { await this.modelReady; let uriString = cell.cellUri.toString(); - if (firstIndex(this._model.cells, c => c.cellUri.toString() === uriString) > -1) { + if (this._model.cells.findIndex(c => c.cellUri.toString() === uriString) > -1) { this.selectCell(cell); // Clear outputs of the requested cell if cell type is code cell. // If cell is markdown cell, clearOutputs() is a no-op @@ -767,7 +766,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe let elBody: HTMLElement = document.body; let tabBar = elBody.querySelector('.title.tabs') as HTMLElement; let actionBar = elBody.querySelector('.editor-toolbar.actionbar-container') as HTMLElement; - let section = find(this.getSectionElements(), s => s.relativeUri && s.relativeUri.toLowerCase() === id); + let section = this.getSectionElements().find(s => s.relativeUri && s.relativeUri.toLowerCase() === id); if (section) { // Scroll this section to the top of the header instead of just bringing header into view. if (tabBar && actionBar) { diff --git a/src/sql/workbench/contrib/notebook/browser/notebookActions.ts b/src/sql/workbench/contrib/notebook/browser/notebookActions.ts index 2c2ab8f465..9180f3c9ed 100644 --- a/src/sql/workbench/contrib/notebook/browser/notebookActions.ts +++ b/src/sql/workbench/contrib/notebook/browser/notebookActions.ts @@ -24,7 +24,6 @@ import { IFindNotebookController } from 'sql/workbench/contrib/notebook/browser/ import { INotebookModel } from 'sql/workbench/services/notebook/browser/models/modelInterfaces'; import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService'; import { TreeUpdateUtils } from 'sql/workbench/services/objectExplorer/browser/treeUpdateUtils'; -import { find, firstIndex } from 'vs/base/common/arrays'; import { INotebookService } from 'sql/workbench/services/notebook/browser/notebookService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { CellContext } from 'sql/workbench/contrib/notebook/browser/cellViews/codeActions'; @@ -323,10 +322,10 @@ export class KernelsDropdown extends SelectBox { if (kernels) { let index; if (standardKernel) { - index = firstIndex(kernels, kernel => kernel === standardKernel.displayName); + index = kernels.findIndex(kernel => kernel === standardKernel.displayName); } else { let kernelSpec = this.model.specs.kernels.find(k => k.name === kernel.name); - index = firstIndex(kernels, k => k === kernelSpec?.display_name); + index = kernels.findIndex(k => k === kernelSpec?.display_name); } if (nbKernelAlias) { index = kernels.indexOf(nbKernelAlias); @@ -407,7 +406,7 @@ export class AttachToDropdown extends SelectBox { let kernelDisplayName: string; if (this.model.clientSession && this.model.clientSession.kernel && this.model.clientSession.kernel.name) { let currentKernelName = this.model.clientSession.kernel.name.toLowerCase(); - let currentKernelSpec = find(this.model.specs.kernels, kernel => kernel.name && kernel.name.toLowerCase() === currentKernelName); + let currentKernelSpec = this.model.specs.kernels.find(kernel => kernel.name && kernel.name.toLowerCase() === currentKernelName); if (currentKernelSpec) { //KernelDisplayName should be Kusto when connecting to Kusto connection if ((this.model.context?.serverCapabilities.notebookKernelAlias && this.model.currentKernelAlias === this.model.context?.serverCapabilities.notebookKernelAlias) || (this.model.kernelAliases.includes(this.model.selectedKernelDisplayName) && this.model.selectedKernelDisplayName)) { @@ -427,7 +426,7 @@ export class AttachToDropdown extends SelectBox { this.setOptions([msgLocalHost]); } else { let connections: string[] = model.context && model.context.title && (connProviderIds.includes(this.model.context.providerName)) ? [model.context.title] : [msgSelectConnection]; - if (!find(connections, x => x === msgChangeConnection)) { + if (!connections.find(x => x === msgChangeConnection)) { connections.push(msgChangeConnection); } this.setOptions(connections, 0); @@ -497,7 +496,7 @@ export class AttachToDropdown extends SelectBox { //To ignore n/a after we have at least one valid connection attachToConnections = attachToConnections.filter(val => val !== msgSelectConnection); - let index = firstIndex(attachToConnections, connection => connection === connectedServer); + let index = attachToConnections.findIndex(connection => connection === connectedServer); this.setOptions([]); this.setOptions(attachToConnections); if (!index || index < 0 || index >= attachToConnections.length) { diff --git a/src/sql/workbench/contrib/profiler/browser/profilerEditor.ts b/src/sql/workbench/contrib/profiler/browser/profilerEditor.ts index bb3c5fd771..67074e9dfd 100644 --- a/src/sql/workbench/contrib/profiler/browser/profilerEditor.ts +++ b/src/sql/workbench/contrib/profiler/browser/profilerEditor.ts @@ -33,7 +33,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { CommonFindController, FindStartFocusAction } from 'vs/editor/contrib/find/findController'; import * as types from 'vs/base/common/types'; import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; -import { DARK, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IView, SplitView, Sizing } from 'vs/base/browser/ui/splitview/splitview'; @@ -50,7 +50,6 @@ import { IClipboardService } from 'sql/platform/clipboard/common/clipboardServic import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin'; import { handleCopyRequest } from 'sql/workbench/contrib/profiler/browser/profilerCopyHandler'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; -import { find } from 'vs/base/common/arrays'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { attachTabbedPanelStyler } from 'sql/workbench/common/styler'; import { UntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; @@ -239,7 +238,7 @@ export class ProfilerEditor extends EditorPane { this._viewTemplateSelector.setAriaLabel(nls.localize('profiler.viewSelectAccessibleName', "Select View")); this._register(this._viewTemplateSelector.onDidSelect(e => { if (this.input) { - this.input.setViewTemplate(find(this._viewTemplates, i => i.name === e.selected)); + this.input.setViewTemplate(this._viewTemplates.find(i => i.name === e.selected)); } })); let viewTemplateContainer = document.createElement('div'); @@ -301,16 +300,16 @@ export class ProfilerEditor extends EditorPane { profilerTableContainer.style.overflow = 'hidden'; profilerTableContainer.style.position = 'relative'; let theme = this.themeService.getColorTheme(); - if (theme.type === DARK) { + if (theme.type === ColorScheme.DARK) { DOM.addClass(profilerTableContainer, VS_DARK_THEME); - } else if (theme.type === HIGH_CONTRAST) { + } else if (theme.type === ColorScheme.HIGH_CONTRAST) { DOM.addClass(profilerTableContainer, VS_HC_THEME); } this.themeService.onDidColorThemeChange(e => { DOM.removeClasses(profilerTableContainer, VS_DARK_THEME, VS_HC_THEME); - if (e.type === DARK) { + if (e.type === ColorScheme.DARK) { DOM.addClass(profilerTableContainer, VS_DARK_THEME); - } else if (e.type === HIGH_CONTRAST) { + } else if (e.type === ColorScheme.HIGH_CONTRAST) { DOM.addClass(profilerTableContainer, VS_HC_THEME); } }); @@ -466,7 +465,7 @@ export class ProfilerEditor extends EditorPane { if (input.viewTemplate) { this._viewTemplateSelector.selectWithOptionName(input.viewTemplate.name); } else { - input.setViewTemplate(find(this._viewTemplates, i => i.name === 'Standard View')); + input.setViewTemplate(this._viewTemplates.find(i => i.name === 'Standard View')); } this._actionBar.context = input; diff --git a/src/sql/workbench/contrib/query/browser/gridPanel.ts b/src/sql/workbench/contrib/query/browser/gridPanel.ts index 313a23d8dc..5ca284788f 100644 --- a/src/sql/workbench/contrib/query/browser/gridPanel.ts +++ b/src/sql/workbench/contrib/query/browser/gridPanel.ts @@ -29,7 +29,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { Disposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; -import { range, find } from 'vs/base/common/arrays'; +import { range } from 'vs/base/common/arrays'; import { generateUuid } from 'vs/base/common/uuid'; import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { isInDOM, Dimension } from 'vs/base/browser/dom'; @@ -182,7 +182,7 @@ export class GridPanel extends Disposable { if (this.configurationService.getValue('queryEditor').results.streaming) { for (let set of resultsToUpdate) { - let table = find(this.tables, t => t.resultSet.batchId === set.batchId && t.resultSet.id === set.id); + let table = this.tables.find(t => t.resultSet.batchId === set.batchId && t.resultSet.id === set.id); if (table) { table.updateResult(set); } else { @@ -204,12 +204,12 @@ export class GridPanel extends Disposable { for (const set of resultSet) { // ensure we aren't adding a resultSet that is already visible - if (find(this.tables, t => t.resultSet.batchId === set.batchId && t.resultSet.id === set.id)) { + if (this.tables.find(t => t.resultSet.batchId === set.batchId && t.resultSet.id === set.id)) { continue; } let tableState: GridTableState; if (this.state) { - tableState = find(this.state.tableStates, e => e.batchId === set.batchId && e.resultId === set.id); + tableState = this.state.tableStates.find(e => e.batchId === set.batchId && e.resultId === set.id); } if (!tableState) { tableState = new GridTableState(set.id, set.batchId); @@ -263,7 +263,7 @@ export class GridPanel extends Disposable { } private maximizeTable(tableid: string): void { - if (!find(this.tables, t => t.id === tableid)) { + if (!this.tables.find(t => t.id === tableid)) { return; } @@ -292,7 +292,7 @@ export class GridPanel extends Disposable { this._state = val; if (this.state) { this.tables.map(t => { - let state = find(this.state.tableStates, s => s.batchId === t.resultSet.batchId && s.resultId === t.resultSet.id); + let state = this.state.tableStates.find(s => s.batchId === t.resultSet.batchId && s.resultId === t.resultSet.id); if (!state) { this.state.tableStates.push(t.state); } diff --git a/src/sql/workbench/contrib/query/browser/keyboardQueryActions.ts b/src/sql/workbench/contrib/query/browser/keyboardQueryActions.ts index fc33bbe53d..fd0fd3624f 100644 --- a/src/sql/workbench/contrib/query/browser/keyboardQueryActions.ts +++ b/src/sql/workbench/contrib/query/browser/keyboardQueryActions.ts @@ -21,7 +21,6 @@ import { EditDataEditor } from 'sql/workbench/contrib/editData/browser/editDataE import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { QueryEditorInput } from 'sql/workbench/common/editor/query/queryEditorInput'; -import { firstIndex } from 'vs/base/common/arrays'; const singleQuote = '\''; @@ -369,7 +368,7 @@ export class RunQueryShortcutAction extends Action { } private getColumnIndex(columnInfo: azdata.IDbColumn[], columnName: string): number { - return columnInfo ? firstIndex(columnInfo, c => c.columnName === columnName) : undefined; + return columnInfo ? columnInfo.findIndex(c => c.columnName === columnName) : undefined; } private canQueryProcMetadata(editor: QueryEditor): boolean { diff --git a/src/sql/workbench/contrib/views/browser/treeView.ts b/src/sql/workbench/contrib/views/browser/treeView.ts index 43b6557e3d..15221a5061 100644 --- a/src/sql/workbench/contrib/views/browser/treeView.ts +++ b/src/sql/workbench/contrib/views/browser/treeView.ts @@ -23,7 +23,7 @@ import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels'; import { ActionBar } 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, ThemeIcon, IThemeService } from 'vs/platform/theme/common/themeService'; +import { FileThemeIcon, FolderThemeIcon, registerThemingParticipant, ThemeIcon, IThemeService } from 'vs/platform/theme/common/themeService'; import { FileKind } from 'vs/platform/files/common/files'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { localize } from 'vs/nls'; @@ -37,12 +37,12 @@ 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 { 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/contrib/views/browser/nodeContext'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; class Root implements ITreeItem { label = { label: 'root' }; @@ -793,7 +793,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer p.concat(...c.filter(a => firstIndex(p, x => x.id === a.id) === -1)), [] as IAction[]); + return actions.reduce((p, c) => p.concat(...c.filter(a => p.findIndex(x => x.id === a.id) === -1)), [] as IAction[]); } private getActions(menuId: MenuId, context: { key: string, value?: string }): { primary: IAction[]; secondary: IAction[]; } { diff --git a/src/sql/workbench/contrib/welcome/page/browser/welcomePage.ts b/src/sql/workbench/contrib/welcome/page/browser/welcomePage.ts index e43d5fbc48..c6e3a16742 100644 --- a/src/sql/workbench/contrib/welcome/page/browser/welcomePage.ts +++ b/src/sql/workbench/contrib/welcome/page/browser/welcomePage.ts @@ -85,7 +85,7 @@ export class WelcomePageContribution implements IWorkbenchContribution { try { const folder = await this.fileService.resolve(folderUri); const files = folder.children ? folder.children.map(child => child.name) : []; - const file = arrays.find(files.sort(), file => strings.startsWith(file.toLowerCase(), 'readme')); + const file = files.sort().find(file => strings.startsWith(file.toLowerCase(), 'readme')); if (file) { return joinPath(folderUri, file); } diff --git a/src/sql/workbench/services/accountManagement/browser/accountManagementService.ts b/src/sql/workbench/services/accountManagement/browser/accountManagementService.ts index 915003990e..b86aa85fd8 100644 --- a/src/sql/workbench/services/accountManagement/browser/accountManagementService.ts +++ b/src/sql/workbench/services/accountManagement/browser/accountManagementService.ts @@ -20,7 +20,6 @@ import { Deferred } from 'sql/base/common/promise'; import { localize } from 'vs/nls'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { URI } from 'vs/base/common/uri'; -import { firstIndex } from 'vs/base/common/arrays'; import { values } from 'vs/base/common/collections'; import { onUnexpectedError } from 'vs/base/common/errors'; import { ILogService } from 'vs/platform/log/common/log'; @@ -185,7 +184,7 @@ export class AccountManagementService implements IAccountManagementService { } if (result.accountModified) { // Find the updated account and splice the updated on in - let indexToRemove: number = firstIndex(provider.accounts, account => { + let indexToRemove: number = provider.accounts.findIndex(account => { return account.key.accountId === result.changedAccount!.key.accountId; }); if (indexToRemove >= 0) { @@ -275,7 +274,7 @@ export class AccountManagementService implements IAccountManagementService { return result; } - let indexToRemove: number = firstIndex(provider.accounts, account => { + let indexToRemove: number = provider.accounts.findIndex(account => { return account.key.accountId === accountKey.accountId; }); @@ -473,7 +472,7 @@ export class AccountManagementService implements IAccountManagementService { private spliceModifiedAccount(provider: AccountProviderWithMetadata, modifiedAccount: azdata.Account) { // Find the updated account and splice the updated one in - let indexToRemove: number = firstIndex(provider.accounts, account => { + let indexToRemove: number = provider.accounts.findIndex(account => { return account.key.accountId === modifiedAccount.key.accountId; }); if (indexToRemove >= 0) { diff --git a/src/sql/workbench/services/accountManagement/browser/accountPickerImpl.ts b/src/sql/workbench/services/accountManagement/browser/accountPickerImpl.ts index b9ba92c31e..ec7600a493 100644 --- a/src/sql/workbench/services/accountManagement/browser/accountPickerImpl.ts +++ b/src/sql/workbench/services/accountManagement/browser/accountPickerImpl.ts @@ -24,7 +24,6 @@ import { attachDropdownStyler } from 'sql/platform/theme/common/styler'; import { AddAccountAction, RefreshAccountAction } from 'sql/platform/accounts/common/accountActions'; import { AccountPickerListRenderer, AccountListDelegate } from 'sql/workbench/services/accountManagement/browser/accountListRenderer'; import { AccountPickerViewModel } from 'sql/platform/accounts/common/accountPickerViewModel'; -import { firstIndex } from 'vs/base/common/arrays'; import { Tenant, TenantListDelegate, TenantPickerListRenderer } from 'sql/workbench/services/accountManagement/browser/tenantListRenderer'; export class AccountPicker extends Disposable { @@ -304,7 +303,7 @@ export class AccountPicker extends Disposable { // find selected index let selectedIndex: number | undefined; if (selectedElements.length > 0 && accounts.length > 0) { - selectedIndex = firstIndex(accounts, (account) => { + selectedIndex = accounts.findIndex(account => { return (account.key.accountId === selectedElements[0].key.accountId); }); } diff --git a/src/sql/workbench/services/connection/browser/connectionActions.ts b/src/sql/workbench/services/connection/browser/connectionActions.ts index a8a2fe332a..6fed9f5d07 100644 --- a/src/sql/workbench/services/connection/browser/connectionActions.ts +++ b/src/sql/workbench/services/connection/browser/connectionActions.ts @@ -17,7 +17,6 @@ import { EditDataInput } from 'sql/workbench/browser/editData/editDataInput'; import { DashboardInput } from 'sql/workbench/browser/editor/profiler/dashboardInput'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -import { find } from 'vs/base/common/arrays'; /** * Workbench action to clear the recent connnections list @@ -83,7 +82,7 @@ export class ClearRecentConnectionsAction extends Action { ]; self._quickInputService.pick(choices.map(x => x.key), { placeHolder: nls.localize('ClearRecentlyUsedLabel', "Clear List"), ignoreFocusLost: true }).then((choice) => { - let confirm = find(choices, x => x.key === choice); + let confirm = choices.find(x => x.key === choice); resolve(confirm && confirm.value); }); }); diff --git a/src/sql/workbench/services/connection/browser/connectionController.ts b/src/sql/workbench/services/connection/browser/connectionController.ts index e4f76d50e5..bd92aeca4f 100644 --- a/src/sql/workbench/services/connection/browser/connectionController.ts +++ b/src/sql/workbench/services/connection/browser/connectionController.ts @@ -18,7 +18,6 @@ import { IServerGroupController } from 'sql/platform/serverGroup/common/serverGr import { ILogService } from 'vs/platform/log/common/log'; import { ConnectionProviderProperties } from 'sql/platform/capabilities/common/capabilitiesService'; import { assign } from 'vs/base/common/objects'; -import { find } from 'vs/base/common/arrays'; export class ConnectionController implements IConnectionComponentController { private _advancedController: AdvancedPropertiesController; @@ -166,7 +165,7 @@ export class ConnectionController implements IConnectionComponentController { this._connectionWidget.updateServerGroup(this.getAllServerGroups(providers)); this._model = connectionInfo; this._model.providerName = this._providerName; - let appNameOption = find(this._providerOptions, option => option.specialValueType === ConnectionOptionSpecialType.appName); + let appNameOption = this._providerOptions.find(option => option.specialValueType === ConnectionOptionSpecialType.appName); if (appNameOption) { let appNameKey = appNameOption.name; this._model.options[appNameKey] = Constants.applicationName; diff --git a/src/sql/workbench/services/connection/browser/connectionDialogService.ts b/src/sql/workbench/services/connection/browser/connectionDialogService.ts index b3970c9a40..3aa51c390f 100644 --- a/src/sql/workbench/services/connection/browser/connectionDialogService.ts +++ b/src/sql/workbench/services/connection/browser/connectionDialogService.ts @@ -29,7 +29,6 @@ import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { CmsConnectionController } from 'sql/workbench/services/connection/browser/cmsConnectionController'; import { entries } from 'sql/base/common/collections'; -import { find } from 'vs/base/common/arrays'; import { onUnexpectedError } from 'vs/base/common/errors'; import { ILogService } from 'vs/platform/log/common/log'; @@ -316,7 +315,7 @@ export class ConnectionDialogService implements IConnectionDialogService { } if (!isProviderInParams) { let uniqueProvidersMap = this._connectionManagementService.getUniqueConnectionProvidersByNameMap(this._providerNameToDisplayNameMap); - this._currentProviderType = find(Object.keys(uniqueProvidersMap), (key) => uniqueProvidersMap[key] === input.selectedProviderDisplayName); + this._currentProviderType = Object.keys(uniqueProvidersMap).find((key) => uniqueProvidersMap[key] === input.selectedProviderDisplayName); } } this._model.providerName = this._currentProviderType; diff --git a/src/sql/workbench/services/connection/browser/connectionManagementService.ts b/src/sql/workbench/services/connection/browser/connectionManagementService.ts index 845d406024..94d4dc4626 100644 --- a/src/sql/workbench/services/connection/browser/connectionManagementService.ts +++ b/src/sql/workbench/services/connection/browser/connectionManagementService.ts @@ -45,7 +45,6 @@ import { Memento } from 'vs/workbench/common/memento'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { entries } from 'sql/base/common/collections'; -import { find } from 'vs/base/common/arrays'; import { values } from 'vs/base/common/collections'; import { assign } from 'vs/base/common/objects'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; @@ -795,7 +794,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti return AzureResource.Sql; } - let result = find(ConnectionManagementService._azureResources, r => AzureResource[r] === provider.properties.azureResource); + let result = ConnectionManagementService._azureResources.find(r => AzureResource[r] === provider.properties.azureResource); return result ? result : AzureResource.Sql; } @@ -814,7 +813,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti const azureAccounts = accounts.filter(a => a.key.providerId.startsWith('azure')); if (azureAccounts && azureAccounts.length > 0) { let accountId = (connection.authenticationType === Constants.azureMFA || connection.authenticationType === Constants.azureMFAAndUser) ? connection.azureAccount : connection.userName; - let account = find(azureAccounts, account => account.key.accountId === accountId); + let account = azureAccounts.find(account => account.key.accountId === accountId); if (account) { this._logService.debug(`Getting security token for Azure account ${account.key.accountId}`); if (account.isStale) { @@ -1094,7 +1093,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti ]; return this._quickInputService.pick(choices.map(x => x.key), { placeHolder: nls.localize('cancelConnectionConfirmation', "Are you sure you want to cancel this connection?"), ignoreFocusLost: true }).then((choice) => { - let confirm = find(choices, x => x.key === choice); + let confirm = choices.find(x => x.key === choice); return confirm && confirm.value; }); } @@ -1350,10 +1349,10 @@ export class ConnectionManagementService extends Disposable implements IConnecti } public async getConnectionCredentials(profileId: string): Promise<{ [name: string]: string }> { - let profile = find(this.getActiveConnections(), connectionProfile => connectionProfile.id === profileId); + let profile = this.getActiveConnections().find(connectionProfile => connectionProfile.id === profileId); if (!profile) { // Couldn't find an active profile so try all profiles now - fetching the password if we found one - profile = find(this.getConnections(), connectionProfile => connectionProfile.id === profileId); + profile = this.getConnections().find(connectionProfile => connectionProfile.id === profileId); if (!profile) { return undefined; } @@ -1361,7 +1360,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti } // Find the password option for the connection provider - let passwordOption = find(this._capabilitiesService.getCapabilities(profile.providerName).connection.connectionOptions, + let passwordOption = this._capabilitiesService.getCapabilities(profile.providerName).connection.connectionOptions.find( option => option.specialValueType === ConnectionOptionSpecialType.password); if (!passwordOption) { return undefined; @@ -1449,7 +1448,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti const connections = this.getActiveConnections(); const connectionExists: (conn: ConnectionProfile) => boolean = (conn) => { - return find(connections, existingConnection => existingConnection.id === conn.id) !== undefined; + return connections.find(existingConnection => existingConnection.id === conn.id) !== undefined; }; if (!activeConnectionsOnly) { diff --git a/src/sql/workbench/services/connection/browser/connectionWidget.ts b/src/sql/workbench/services/connection/browser/connectionWidget.ts index 005cd8005b..86eaa1ac78 100644 --- a/src/sql/workbench/services/connection/browser/connectionWidget.ts +++ b/src/sql/workbench/services/connection/browser/connectionWidget.ts @@ -35,7 +35,6 @@ import { endsWith, startsWith } from 'vs/base/common/strings'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; -import { find } from 'vs/base/common/arrays'; export enum AuthenticationType { SqlLogin = 'SqlLogin', @@ -136,7 +135,7 @@ export class ConnectionWidget extends lifecycle.Disposable { protected getAuthTypeDefault(option: azdata.ConnectionOption, os: OperatingSystem): string { // Check for OS-specific default value if (option.defaultValueOsOverrides) { - let result = find(option.defaultValueOsOverrides, d => ConnectionWidget._osByName[d.os] === os); + let result = option.defaultValueOsOverrides.find(d => ConnectionWidget._osByName[d.os] === os); if (result) { return result.defaultValueOverride; } @@ -413,7 +412,7 @@ export class ConnectionWidget extends lifecycle.Disposable { if (this._refreshCredentialsLink) { this._register(DOM.addDisposableListener(this._refreshCredentialsLink, DOM.EventType.CLICK, async () => { - let account = find(this._azureAccountList, account => account.key.accountId === this._azureAccountDropdown.value); + let account = this._azureAccountList.find(account => account.key.accountId === this._azureAccountDropdown.value); if (account) { await this._accountManagementService.refreshAccount(account); await this.fillInAzureAccountOptions(); @@ -537,7 +536,7 @@ export class ConnectionWidget extends lifecycle.Disposable { } private updateRefreshCredentialsLink(): void { - let chosenAccount = find(this._azureAccountList, account => account.key.accountId === this._azureAccountDropdown.value); + let chosenAccount = this._azureAccountList.find(account => account.key.accountId === this._azureAccountDropdown.value); if (chosenAccount && chosenAccount.isStale) { DOM.removeClass(this._tableContainer, 'hide-refresh-link'); } else { @@ -558,7 +557,7 @@ export class ConnectionWidget extends lifecycle.Disposable { await this.fillInAzureAccountOptions(); // If a new account was added find it and select it, otherwise select the first account - let newAccount = find(this._azureAccountList, option => !oldAccountIds.some(oldId => oldId === option.key.accountId)); + let newAccount = this._azureAccountList.find(option => !oldAccountIds.some(oldId => oldId === option.key.accountId)); if (newAccount) { this._azureAccountDropdown.selectWithOptionName(newAccount.key.accountId); } else { @@ -570,7 +569,7 @@ export class ConnectionWidget extends lifecycle.Disposable { // Display the tenant select box if needed const hideTenantsClassName = 'hide-azure-tenants'; - let selectedAccount = find(this._azureAccountList, account => account.key.accountId === this._azureAccountDropdown.value); + let selectedAccount = this._azureAccountList.find(account => account.key.accountId === this._azureAccountDropdown.value); if (selectedAccount && selectedAccount.properties.tenants && selectedAccount.properties.tenants.length > 1) { // There are multiple tenants available so let the user select one let options = selectedAccount.properties.tenants.map(tenant => tenant.displayName); @@ -589,7 +588,7 @@ export class ConnectionWidget extends lifecycle.Disposable { private onAzureTenantSelected(tenantIndex: number): void { this._azureTenantId = undefined; - let account = find(this._azureAccountList, account => account.key.accountId === this._azureAccountDropdown.value); + let account = this._azureAccountList.find(account => account.key.accountId === this._azureAccountDropdown.value); if (account && account.properties.tenants) { let tenant = account.properties.tenants[tenantIndex]; if (tenant) { @@ -708,7 +707,7 @@ export class ConnectionWidget extends lifecycle.Disposable { this._azureAccountDropdown.selectWithOptionName(this.getModelValue(accountName)); await this.onAzureAccountSelected(); let tenantId = connectionInfo.azureTenantId; - let account = find(this._azureAccountList, account => account.key.accountId === this._azureAccountDropdown.value); + let account = this._azureAccountList.find(account => account.key.accountId === this._azureAccountDropdown.value); if (account && account.properties.tenants.length > 1) { let tenant = account.properties.tenants.find(tenant => tenant.id === tenantId); if (tenant) { @@ -912,12 +911,12 @@ export class ConnectionWidget extends lifecycle.Disposable { private findGroupId(groupFullName: string): string { let group: IConnectionProfileGroup; if (ConnectionProfileGroup.isRoot(groupFullName)) { - group = find(this._serverGroupOptions, g => ConnectionProfileGroup.isRoot(g.name)); + group = this._serverGroupOptions.find(g => ConnectionProfileGroup.isRoot(g.name)); if (group === undefined) { - group = find(this._serverGroupOptions, g => g.name === this.DefaultServerGroup.name); + group = this._serverGroupOptions.find(g => g.name === this.DefaultServerGroup.name); } } else { - group = find(this._serverGroupOptions, g => g.name === groupFullName); + group = this._serverGroupOptions.find(g => g.name === groupFullName); } return group ? group.id : undefined; } @@ -926,7 +925,7 @@ export class ConnectionWidget extends lifecycle.Disposable { if (!displayName) { return undefined; } - return find(ConnectionWidget._authTypes, authType => this.getAuthTypeDisplayName(authType) === displayName); + return ConnectionWidget._authTypes.find(authType => this.getAuthTypeDisplayName(authType) === displayName); } public closeDatabaseDropdown(): void { diff --git a/src/sql/workbench/services/connection/test/browser/testTreeView.ts b/src/sql/workbench/services/connection/test/browser/testTreeView.ts index ed74d632fd..884a7d8b10 100644 --- a/src/sql/workbench/services/connection/test/browser/testTreeView.ts +++ b/src/sql/workbench/services/connection/test/browser/testTreeView.ts @@ -24,7 +24,8 @@ import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels'; import { ActionBar } 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, ThemeIcon, IThemeService } from 'vs/platform/theme/common/themeService'; +import { FileThemeIcon, FolderThemeIcon, registerThemingParticipant, ThemeIcon, IThemeService } from 'vs/platform/theme/common/themeService'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; import { FileKind } from 'vs/platform/files/common/files'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { localize } from 'vs/nls'; @@ -754,7 +755,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer { - let uiTab = find(tabList, i => i.tabConfig === tab); + let uiTab = tabList.find(i => i.tabConfig === tab); if (uiTab) { uiTab.isOpened = true; } diff --git a/src/sql/workbench/services/insights/test/electron-browser/insightsUtils.test.ts b/src/sql/workbench/services/insights/test/electron-browser/insightsUtils.test.ts index 25305cbbb1..4086f8a93f 100644 --- a/src/sql/workbench/services/insights/test/electron-browser/insightsUtils.test.ts +++ b/src/sql/workbench/services/insights/test/electron-browser/insightsUtils.test.ts @@ -22,13 +22,13 @@ import * as pfs from 'vs/base/node/pfs'; import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { IProcessEnvironment } from 'vs/base/common/platform'; import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; -import { TestWindowConfiguration } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import { isEqual } from 'vs/base/common/resources'; +import { TestWorkbenchConfiguration } from 'vs/workbench/test/electron-browser/workbenchTestServices'; class MockWorkbenchEnvironmentService extends NativeWorkbenchEnvironmentService { constructor(public userEnv: IProcessEnvironment) { - super({ ...TestWindowConfiguration, userEnv }, TestWindowConfiguration.execPath); + super({ ...TestWorkbenchConfiguration, userEnv }); } } diff --git a/src/sql/workbench/services/jobManagement/browser/jobManagementUtilities.ts b/src/sql/workbench/services/jobManagement/browser/jobManagementUtilities.ts index 4c0c9e9d60..621d4322e5 100644 --- a/src/sql/workbench/services/jobManagement/browser/jobManagementUtilities.ts +++ b/src/sql/workbench/services/jobManagement/browser/jobManagementUtilities.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { find } from 'vs/base/common/arrays'; export class JobManagementUtilities { @@ -42,7 +41,9 @@ export class JobManagementUtilities { } public static convertToNextRun(date: string) { - if (find(date, x => x === '1/1/0001')) { + let newDate = date as unknown; + let dateArr = newDate as []; + if (dateArr.find(x => x === '1/1/0001')) { return nls.localize('agentUtilities.notScheduled', "Not Scheduled"); } else { return date; @@ -50,7 +51,9 @@ export class JobManagementUtilities { } public static convertToLastRun(date: string) { - if (find(date, x => x === '1/1/0001')) { + let newDate = date as unknown; + let dateArr = newDate as []; + if (dateArr.find(x => x === '1/1/0001')) { return nls.localize('agentUtilities.neverRun', "Never Run"); } else { return date; @@ -58,7 +61,9 @@ export class JobManagementUtilities { } public static setRunnable(icon: HTMLElement, index: number) { - if (find(icon.className, x => x === 'non-runnable')) { + let temp = icon.className as unknown; + let classNameArr = temp as []; + if (classNameArr.find(x => x === 'non-runnable')) { icon.className = icon.className.slice(0, index); } } diff --git a/src/sql/workbench/services/notebook/browser/models/cell.ts b/src/sql/workbench/services/notebook/browser/models/cell.ts index 708bb8f63b..3097e202be 100644 --- a/src/sql/workbench/services/notebook/browser/models/cell.ts +++ b/src/sql/workbench/services/notebook/browser/models/cell.ts @@ -22,7 +22,6 @@ import { optional } from 'vs/platform/instantiation/common/instantiation'; import { getErrorMessage, onUnexpectedError } from 'vs/base/common/errors'; import { generateUuid } from 'vs/base/common/uuid'; import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents'; -import { firstIndex, find } from 'vs/base/common/arrays'; import { HideInputTag } from 'sql/platform/notebooks/common/outputRegistry'; import { FutureInternal, notebookConstants } from 'sql/workbench/services/notebook/browser/interfaces'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -136,7 +135,7 @@ export class CellModel extends Disposable implements ICellModel { let tagIndex = -1; if (this._metadata.tags) { - tagIndex = firstIndex(this._metadata.tags, tag => tag === HideInputTag); + tagIndex = this._metadata.tags.findIndex(tag => tag === HideInputTag); } if (this._isCollapsed) { @@ -786,7 +785,7 @@ export class CellModel extends Disposable implements ICellModel { if (serverInfo) { let endpoints: notebookUtils.IEndpoint[] = notebookUtils.getClusterEndpoints(serverInfo); if (endpoints && endpoints.length > 0) { - endpoint = find(endpoints, ep => ep.serviceName.toLowerCase() === notebookUtils.hadoopEndpointNameGateway); + endpoint = endpoints.find(ep => ep.serviceName.toLowerCase() === notebookUtils.hadoopEndpointNameGateway); } } } diff --git a/src/sql/workbench/services/notebook/browser/models/notebookModel.ts b/src/sql/workbench/services/notebook/browser/models/notebookModel.ts index a46e9eab5b..1f1d713840 100644 --- a/src/sql/workbench/services/notebook/browser/models/notebookModel.ts +++ b/src/sql/workbench/services/notebook/browser/models/notebookModel.ts @@ -24,7 +24,6 @@ import { ConnectionProfile } from 'sql/platform/connection/common/connectionProf import { uriPrefixes } from 'sql/platform/connection/common/utils'; import { ILogService } from 'vs/platform/log/common/log'; import { getErrorMessage } from 'vs/base/common/errors'; -import { find, firstIndex } from 'vs/base/common/arrays'; import { startsWith } from 'vs/base/common/strings'; import { notebookConstants } from 'sql/workbench/services/notebook/browser/interfaces'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; @@ -119,18 +118,18 @@ export class NotebookModel extends Disposable implements INotebookModel { } public get notebookManager(): INotebookManager { - let manager = find(this.notebookManagers, manager => manager.providerId === this._providerId); + let manager = this.notebookManagers.find(manager => manager.providerId === this._providerId); if (!manager) { // Note: this seems like a less than ideal scenario. We should ideally pass in the "correct" provider ID and allow there to be a default, // instead of assuming in the NotebookModel constructor that the option is either SQL or Jupyter - manager = find(this.notebookManagers, manager => manager.providerId === DEFAULT_NOTEBOOK_PROVIDER); + manager = this.notebookManagers.find(manager => manager.providerId === DEFAULT_NOTEBOOK_PROVIDER); } return manager; } public getNotebookManager(providerId: string): INotebookManager { if (providerId) { - return find(this.notebookManagers, manager => manager.providerId === providerId); + return this.notebookManagers.find(manager => manager.providerId === providerId); } return undefined; } @@ -397,7 +396,7 @@ export class NotebookModel extends Disposable implements INotebookModel { } public findCellIndex(cellModel: ICellModel): number { - return firstIndex(this._cells, (cell) => cell.equals(cellModel)); + return this._cells.findIndex(cell => cell.equals(cellModel)); } public addCell(cellType: CellType, index?: number): ICellModel { @@ -498,7 +497,7 @@ export class NotebookModel extends Disposable implements INotebookModel { if (this.inErrorState || !this._cells) { return; } - let index = firstIndex(this._cells, (cell) => cell.equals(cellModel)); + let index = this._cells.findIndex(cell => cell.equals(cellModel)); if (index > -1) { this._cells.splice(index, 1); if (this._activeCell === cellModel) { @@ -548,7 +547,7 @@ export class NotebookModel extends Disposable implements INotebookModel { public async startSession(manager: INotebookManager, displayName?: string, setErrorStateOnFail?: boolean, kernelAlias?: string): Promise { if (displayName && this._standardKernels) { - let standardKernel = find(this._standardKernels, kernel => kernel.displayName === displayName); + let standardKernel = this._standardKernels.find(kernel => kernel.displayName === displayName); if (standardKernel) { this._defaultKernel = { name: standardKernel.name, display_name: standardKernel.displayName }; } @@ -653,7 +652,7 @@ export class NotebookModel extends Disposable implements INotebookModel { private isValidConnection(profile: IConnectionProfile | connection.Connection) { if (this._standardKernels) { - let standardKernels = find(this._standardKernels, kernel => this._defaultKernel && kernel.displayName === this._defaultKernel.display_name); + let standardKernels = this._standardKernels.find(kernel => this._defaultKernel && kernel.displayName === this._defaultKernel.display_name); let connectionProviderIds = standardKernels ? standardKernels.connectionProviderIds : undefined; let providerFeatures = this._capabilitiesService.getCapabilities(profile.providerName); if (connectionProviderIds?.length) { @@ -666,14 +665,14 @@ export class NotebookModel extends Disposable implements INotebookModel { this._kernelDisplayNameToConnectionProviderIds.set(this._currentKernelAlias, [profile.providerName]); } } - return this._currentKernelAlias || profile && connectionProviderIds && find(connectionProviderIds, provider => provider === profile.providerName) !== undefined; + return this._currentKernelAlias || profile && connectionProviderIds && connectionProviderIds.find(provider => provider === profile.providerName) !== undefined; } return false; } public getStandardKernelFromName(name: string): notebookUtils.IStandardKernelWithProvider { if (name && this._standardKernels) { - let kernel = find(this._standardKernels, kernel => kernel.name.toLowerCase() === name.toLowerCase()); + let kernel = this._standardKernels.find(kernel => kernel.name.toLowerCase() === name.toLowerCase()); return kernel; } return undefined; @@ -681,7 +680,7 @@ export class NotebookModel extends Disposable implements INotebookModel { public getStandardKernelFromDisplayName(displayName: string): notebookUtils.IStandardKernelWithProvider { if (displayName && this._standardKernels) { - let kernel = find(this._standardKernels, kernel => kernel.displayName.toLowerCase() === displayName.toLowerCase()); + let kernel = this._standardKernels.find(kernel => kernel.displayName.toLowerCase() === displayName.toLowerCase()); return kernel; } return undefined; @@ -827,8 +826,8 @@ export class NotebookModel extends Disposable implements INotebookModel { if (spec) { // Ensure that the kernel we try to switch to is a valid kernel; if not, use the default let kernelSpecs = this.getKernelSpecs(); - if (kernelSpecs && kernelSpecs.length > 0 && firstIndex(kernelSpecs, k => k.display_name === spec.display_name) < 0) { - spec = find(kernelSpecs, spec => spec.name === this.notebookManager.sessionManager.specs.defaultKernel); + if (kernelSpecs && kernelSpecs.length > 0 && kernelSpecs.findIndex(k => k.display_name === spec.display_name) < 0) { + spec = kernelSpecs.find(spec => spec.name === this.notebookManager.sessionManager.specs.defaultKernel); } } else { @@ -889,7 +888,7 @@ export class NotebookModel extends Disposable implements INotebookModel { } private getKernelSpecFromDisplayName(displayName: string): nb.IKernelSpec { - let kernel: nb.IKernelSpec = find(this.specs.kernels, k => k.display_name.toLowerCase() === displayName.toLowerCase()); + let kernel: nb.IKernelSpec = this.specs.kernels.find(k => k.display_name.toLowerCase() === displayName.toLowerCase()); if (!kernel) { return undefined; // undefined is handled gracefully in the session to default to the default kernel } else if (!kernel.name) { @@ -906,7 +905,7 @@ export class NotebookModel extends Disposable implements INotebookModel { this._savedKernelInfo.display_name = displayName; } if (this._standardKernels) { - let standardKernel = find(this._standardKernels, kernel => kernel.displayName === displayName || startsWith(displayName, kernel.displayName)); + let standardKernel = this._standardKernels.find(kernel => kernel.displayName === displayName || startsWith(displayName, kernel.displayName)); if (standardKernel && this._savedKernelInfo.name && this._savedKernelInfo.name !== standardKernel.name) { this._savedKernelInfo.name = standardKernel.name; this._savedKernelInfo.display_name = standardKernel.displayName; @@ -920,7 +919,7 @@ export class NotebookModel extends Disposable implements INotebookModel { if (!specs || !specs.kernels) { return kernel.name; } - let newKernel = find(this.notebookManager.sessionManager.specs.kernels, k => k.name === kernel.name); + let newKernel = this.notebookManager.sessionManager.specs.kernels.find(k => k.name === kernel.name); let newKernelDisplayName; if (newKernel) { newKernelDisplayName = newKernel.display_name; diff --git a/src/sql/workbench/services/notebook/browser/sql/sqlSessionManager.ts b/src/sql/workbench/services/notebook/browser/sql/sqlSessionManager.ts index aaf9a86414..ea28d98ce9 100644 --- a/src/sql/workbench/services/notebook/browser/sql/sqlSessionManager.ts +++ b/src/sql/workbench/services/notebook/browser/sql/sqlSessionManager.ts @@ -24,7 +24,6 @@ import { ILanguageMagic } from 'sql/workbench/services/notebook/browser/notebook import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { URI } from 'vs/base/common/uri'; import { getUriPrefix, uriPrefixes } from 'sql/platform/connection/common/utils'; -import { firstIndex } from 'vs/base/common/arrays'; import { startsWith } from 'vs/base/common/strings'; import { onUnexpectedError } from 'vs/base/common/errors'; import { FutureInternal, notebookConstants } from 'sql/workbench/services/notebook/browser/interfaces'; @@ -75,7 +74,7 @@ export class SqlSessionManager implements nb.SessionManager { startNew(options: nb.ISessionOptions): Thenable { let sqlSession = new SqlSession(options, this._instantiationService); - let index = firstIndex(SqlSessionManager._sessions, session => session.path === options.path); + let index = SqlSessionManager._sessions.findIndex(session => session.path === options.path); if (index > -1) { SqlSessionManager._sessions.splice(index); } @@ -84,7 +83,7 @@ export class SqlSessionManager implements nb.SessionManager { } shutdown(id: string): Thenable { - let index = firstIndex(SqlSessionManager._sessions, session => session.id === id); + let index = SqlSessionManager._sessions.findIndex(session => session.id === id); if (index > -1) { let sessionManager = SqlSessionManager._sessions[index]; SqlSessionManager._sessions.splice(index); diff --git a/src/sql/workbench/services/objectExplorer/browser/serverTreeActionProvider.ts b/src/sql/workbench/services/objectExplorer/browser/serverTreeActionProvider.ts index 1439b6f1b1..d009bd189f 100644 --- a/src/sql/workbench/services/objectExplorer/browser/serverTreeActionProvider.ts +++ b/src/sql/workbench/services/objectExplorer/browser/serverTreeActionProvider.ts @@ -24,7 +24,6 @@ import { TreeNodeContextKey } from 'sql/workbench/services/objectExplorer/common import { IQueryManagementService } from 'sql/workbench/services/query/common/queryManagement'; import { ServerInfoContextKey } from 'sql/workbench/services/connection/common/serverInfoContextKey'; import { fillInActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { firstIndex, find } from 'vs/base/common/arrays'; import { AsyncServerTree, ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree'; /** @@ -87,7 +86,7 @@ export class ServerTreeActionProvider { const options = { arg: undefined, shouldForwardArgs: true }; const groups = menu.getActions(options); let insertIndex: number | undefined = 0; - const queryIndex = firstIndex(groups, v => { + const queryIndex = groups.findIndex(v => { if (v[0] === '0_query') { return true; } else { @@ -193,7 +192,7 @@ export class ServerTreeActionProvider { private isScriptableObject(context: ObjectExplorerContext): boolean { if (context.treeNode) { - if (find(NodeType.SCRIPTABLE_OBJECTS, x => x === context?.treeNode?.nodeTypeId)) { + if (NodeType.SCRIPTABLE_OBJECTS.find(x => x === context?.treeNode?.nodeTypeId)) { return true; } } diff --git a/src/sql/workbench/services/objectExplorer/test/browser/objectExplorerService.test.ts b/src/sql/workbench/services/objectExplorer/test/browser/objectExplorerService.test.ts index b5d1da749e..6bdc2b7450 100644 --- a/src/sql/workbench/services/objectExplorer/test/browser/objectExplorerService.test.ts +++ b/src/sql/workbench/services/objectExplorer/test/browser/objectExplorerService.test.ts @@ -18,7 +18,6 @@ import { NullLogService } from 'vs/platform/log/common/log'; import { TestObjectExplorerProvider } from 'sql/workbench/services/objectExplorer/test/common/testObjectExplorerProvider'; import { TestConnectionManagementService } from 'sql/platform/connection/test/common/testConnectionManagementService'; import { TestCapabilitiesService } from 'sql/platform/capabilities/test/common/testCapabilitiesService'; -import { find } from 'vs/base/common/arrays'; import { NullAdsTelemetryService } from 'sql/platform/telemetry/common/adsTelemetryService'; import { ConnectionOptionSpecialType, ServiceOptionType } from 'sql/platform/connection/common/interfaces'; @@ -495,7 +494,7 @@ suite('SQL Object Explorer Service tests', () => { sqlOEProvider.setup(x => x.expandNode(TypeMoq.It.isAny())).callback(() => { objectExplorerService.onNodeExpanded(tableExpandInfo); }).returns(() => Promise.resolve(true)); - const tableNode = find(childNodes, node => node.nodePath === table1NodePath); + const tableNode = childNodes.find(node => node.nodePath === table1NodePath); await objectExplorerService.resolveTreeNodeChildren(objectExplorerSession, tableNode); const isExpanded = await tableNode.isExpanded(); assert.equal(isExpanded, true, 'Table node was not expanded'); @@ -511,7 +510,7 @@ suite('SQL Object Explorer Service tests', () => { objectExplorerService.onSessionCreated(1, objectExplorerSession); const childNodes = await objectExplorerService.resolveTreeNodeChildren(objectExplorerSession, objectExplorerService.getObjectExplorerNode(connection)); // If I check whether the table is expanded, the answer should be no because only its parent node is expanded - const tableNode = find(childNodes, node => node.nodePath === table1NodePath); + const tableNode = childNodes.find(node => node.nodePath === table1NodePath); const isExpanded = await tableNode.isExpanded(); assert.equal(isExpanded, false); }); @@ -535,9 +534,9 @@ suite('SQL Object Explorer Service tests', () => { sqlOEProvider.setup(x => x.expandNode(TypeMoq.It.isAny())).callback(() => { objectExplorerService.onNodeExpanded(tableExpandInfo); }).returns(() => Promise.resolve(true)); - await objectExplorerService.resolveTreeNodeChildren(objectExplorerSession, find(childNodes, node => node.nodePath === table1NodePath)); + await objectExplorerService.resolveTreeNodeChildren(objectExplorerSession, childNodes.find(node => node.nodePath === table1NodePath)); // If I check whether the table is expanded, the answer should be yes - const tableNode = find(childNodes, node => node.nodePath === table1NodePath); + const tableNode = childNodes.find(node => node.nodePath === table1NodePath); const isExpanded = await tableNode.isExpanded(); assert.equal(isExpanded, false); }); diff --git a/src/sql/workbench/services/profiler/browser/profilerFilterDialog.ts b/src/sql/workbench/services/profiler/browser/profilerFilterDialog.ts index b71cd07de8..3cfa15af16 100644 --- a/src/sql/workbench/services/profiler/browser/profilerFilterDialog.ts +++ b/src/sql/workbench/services/profiler/browser/profilerFilterDialog.ts @@ -24,7 +24,6 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ProfilerFilter, ProfilerFilterClause, ProfilerFilterClauseOperator, IProfilerService } from 'sql/workbench/services/profiler/browser/interfaces'; import { ILogService } from 'vs/platform/log/common/log'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; -import { find, firstIndex } from 'vs/base/common/arrays'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { onUnexpectedError } from 'vs/base/common/errors'; import { attachModalDialogStyler } from 'sql/workbench/common/styler'; @@ -223,7 +222,7 @@ export class ProfilerFilterDialog extends Modal { private addClauseRow(setInitialValue: boolean, field?: string, operator?: string, value?: string): void { const columns = this._input!.columns.map(column => column.name); - if (field && !find(columns, x => x === field)) { + if (field && !columns.find(x => x === field)) { return; } @@ -272,7 +271,7 @@ export class ProfilerFilterDialog extends Modal { } private removeRow(clauseId: string) { - const idx = firstIndex(this._clauseRows, (entry) => { return entry.id === clauseId; }); + const idx = this._clauseRows.findIndex(entry => { return entry.id === clauseId; }); if (idx !== -1) { this._clauseRows[idx].row.remove(); this._clauseRows.splice(idx, 1); diff --git a/src/sql/workbench/services/query/common/queryRunner.ts b/src/sql/workbench/services/query/common/queryRunner.ts index 435a2c9f3e..b4555ba0bb 100644 --- a/src/sql/workbench/services/query/common/queryRunner.ts +++ b/src/sql/workbench/services/query/common/queryRunner.ts @@ -23,7 +23,6 @@ import { mssqlProviderName } from 'sql/platform/connection/common/constants'; import { IGridDataProvider, getResultsString } from 'sql/workbench/services/query/common/gridDataProvider'; import { getErrorMessage } from 'vs/base/common/errors'; import { ILogService } from 'vs/platform/log/common/log'; -import { find } from 'vs/base/common/arrays'; import { IRange, Range } from 'vs/editor/common/core/range'; import { BatchSummary, IQueryMessage, ResultSetSummary, QueryExecuteSubsetParams, CompleteBatchSummary, IResultMessage, ResultSetSubset, BatchStartSummary } from './query'; import { IQueryEditorConfiguration } from 'sql/platform/query/common/query'; @@ -323,7 +322,7 @@ export default class QueryRunner extends Disposable { } // handle getting queryPlanxml if we need too // check if this result has show plan, this needs work, it won't work for any other provider - let hasShowPlan = !!find(resultSet.columnInfo, e => e.columnName === 'Microsoft SQL Server 2005 XML Showplan'); + let hasShowPlan = !!resultSet.columnInfo.find(e => e.columnName === 'Microsoft SQL Server 2005 XML Showplan'); if (hasShowPlan && resultSet.rowCount > 0) { this._isQueryPlan = true; diff --git a/src/sql/workbench/services/queryHistory/common/queryHistoryServiceImpl.ts b/src/sql/workbench/services/queryHistory/common/queryHistoryServiceImpl.ts index a077ca94bc..86aa0a0df9 100644 --- a/src/sql/workbench/services/queryHistory/common/queryHistoryServiceImpl.ts +++ b/src/sql/workbench/services/queryHistory/common/queryHistoryServiceImpl.ts @@ -14,7 +14,6 @@ import { IConnectionManagementService } from 'sql/platform/connection/common/con import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; -import { find } from 'vs/base/common/arrays'; /** * Service that collects the results of executed queries @@ -42,7 +41,7 @@ export class QueryHistoryService extends Disposable implements IQueryHistoryServ this._captureEnabled = !!this._configurationService.getValue('queryHistory.captureEnabled'); this._register(this._configurationService.onDidChangeConfiguration((e: IConfigurationChangeEvent) => { - if (find(e.affectedKeys, x => x === 'queryHistory.captureEnabled')) { + if (e.affectedKeys.find(x => x === 'queryHistory.captureEnabled')) { this.updateCaptureEnabled(); } })); diff --git a/src/sql/workbench/services/tasks/common/tasksService.ts b/src/sql/workbench/services/tasks/common/tasksService.ts index 6e2535c497..352ef82ec7 100644 --- a/src/sql/workbench/services/tasks/common/tasksService.ts +++ b/src/sql/workbench/services/tasks/common/tasksService.ts @@ -13,7 +13,6 @@ import { localize } from 'vs/nls'; import Severity from 'vs/base/common/severity'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement'; -import { find } from 'vs/base/common/arrays'; export const SERVICE_ID = 'taskHistoryService'; export const ITaskService = createDecorator(SERVICE_ID); @@ -227,7 +226,7 @@ export class TaskService implements ITaskService { private getTaskInQueue(taskId: string): TaskNode | undefined { if (this._taskQueue.hasChildren) { - return find(this._taskQueue.children!, x => x.id === taskId); + return this._taskQueue.children!.find(x => x.id === taskId); } return undefined; } diff --git a/src/sql/workbench/test/electron-browser/api/extHostObjectExplorer.test.ts b/src/sql/workbench/test/electron-browser/api/extHostObjectExplorer.test.ts index 1d77496520..07fbb4ab72 100644 --- a/src/sql/workbench/test/electron-browser/api/extHostObjectExplorer.test.ts +++ b/src/sql/workbench/test/electron-browser/api/extHostObjectExplorer.test.ts @@ -9,7 +9,6 @@ import * as TypeMoq from 'typemoq'; import { MainThreadObjectExplorerShape } from 'sql/workbench/api/common/sqlExtHost.protocol'; import { ExtHostObjectExplorerNode } from 'sql/workbench/api/common/extHostObjectExplorer'; -import { find } from 'vs/base/common/arrays'; const nodes: { [nodeName: string]: azdata.NodeInfo } = { @@ -75,7 +74,7 @@ suite('ExtHostObjectExplorer Tests', () => { mockProxy.setup(p => p.$getNode(TypeMoq.It.isAny(), TypeMoq.It.isAny())) .returns((connectionId, nodePath) => { - return Promise.resolve(nodes[find(Object.keys(nodes), key => + return Promise.resolve(nodes[Object.keys(nodes).find(key => nodes[key].nodePath === nodePath)]); }); }); diff --git a/src/tsconfig.vscode.json b/src/tsconfig.vscode.json index 5fa2e6f5cd..23a84f276a 100644 --- a/src/tsconfig.vscode.json +++ b/src/tsconfig.vscode.json @@ -6,7 +6,7 @@ "experimentalDecorators": true, "noImplicitReturns": true, "noUnusedLocals": true, - "strict": true, + "strict": false, "forceConsistentCasingInFileNames": true, "baseUrl": ".", "removeComments": false, diff --git a/src/typings/trustedTypes.d.ts b/src/typings/trustedTypes.d.ts new file mode 100644 index 0000000000..8f28ee344c --- /dev/null +++ b/src/typings/trustedTypes.d.ts @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// see https://w3c.github.io/webappsec-trusted-types/dist/spec/ +// this isn't complete nor 100% correct + +type TrustedHTML = string & object; +type TrustedScript = string; +type TrustedScriptURL = string; + +interface TrustedTypePolicyOptions { + createHTML?: (value: string) => string + createScript?: (value: string) => string + createScriptURL?: (value: string) => string +} + +interface TrustedTypePolicy { + readonly name: string; + createHTML(input: string, ...more: any[]): TrustedHTML + createScript(input: string, ...more: any[]): TrustedScript + createScriptURL(input: string, ...more: any[]): TrustedScriptURL +} + +interface TrustedTypePolicyFactory { + createPolicy(policyName: string, object: TrustedTypePolicyOptions): TrustedTypePolicy; +} + +interface Window { + trustedTypes: TrustedTypePolicyFactory | undefined; +} + +interface WorkerGlobalScope { + trustedTypes: TrustedTypePolicyFactory | undefined; +} diff --git a/src/vs/base/browser/codicons.ts b/src/vs/base/browser/codicons.ts index a418e52b19..d569f7c7ac 100644 --- a/src/vs/base/browser/codicons.ts +++ b/src/vs/base/browser/codicons.ts @@ -4,9 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import { renderCodiconsRegex } from 'vs/base/common/codicons'; -export function renderCodiconsAsElement(text: string): Array { +const renderCodiconsRegex = /(\\)?\$\((([a-z0-9\-]+?)(?:~([a-z0-9\-]*?))?)\)/gi; + +export function renderCodicons(text: string): Array { const elements = new Array(); let match: RegExpMatchArray | null; @@ -24,4 +25,4 @@ export function renderCodiconsAsElement(text: string): Array boolean = _classList.hasClass.bind(_classList); +export function hasClass(node: HTMLElement | SVGElement, className: string): boolean { return _classList.hasClass(node, className); } /** @deprecated ES6 - use classList*/ -export const addClass: (node: HTMLElement | SVGElement, className: string) => void = _classList.addClass.bind(_classList); +export function addClass(node: HTMLElement | SVGElement, className: string): void { return _classList.addClass(node, className); } /** @deprecated ES6 - use classList*/ -export const addClasses: (node: HTMLElement | SVGElement, ...classNames: string[]) => void = _classList.addClasses.bind(_classList); +export function addClasses(node: HTMLElement | SVGElement, ...classNames: string[]): void { return _classList.addClasses(node, ...classNames); } /** @deprecated ES6 - use classList*/ -export const removeClass: (node: HTMLElement | SVGElement, className: string) => void = _classList.removeClass.bind(_classList); +export function removeClass(node: HTMLElement | SVGElement, className: string): void { return _classList.removeClass(node, className); } /** @deprecated ES6 - use classList*/ -export const removeClasses: (node: HTMLElement | SVGElement, ...classNames: string[]) => void = _classList.removeClasses.bind(_classList); +export function removeClasses(node: HTMLElement | SVGElement, ...classNames: string[]): void { return _classList.removeClasses(node, ...classNames); } /** @deprecated ES6 - use classList*/ -export const toggleClass: (node: HTMLElement | SVGElement, className: string, shouldHaveIt?: boolean) => void = _classList.toggleClass.bind(_classList); +export function toggleClass(node: HTMLElement | SVGElement, className: string, shouldHaveIt?: boolean): void { return _classList.toggleClass(node, className, shouldHaveIt); } class DomListener implements IDisposable { @@ -411,9 +410,9 @@ export function getClientArea(element: HTMLElement): Dimension { } // If visual view port exits and it's on mobile, it should be used instead of window innerWidth / innerHeight, or document.body.clientWidth / document.body.clientHeight - if (platform.isIOS && (window).visualViewport) { - const width = (window).visualViewport.width; - const height = (window).visualViewport.height - ( + if (platform.isIOS && window.visualViewport) { + const width = window.visualViewport.width; + const height = window.visualViewport.height - ( browser.isStandalone // in PWA mode, the visual viewport always includes the safe-area-inset-bottom (which is for the home indicator) // even when you are using the onscreen monitor, the visual viewport will include the area between system statusbar and the onscreen keyboard @@ -785,11 +784,11 @@ function getSharedStyleSheet(): HTMLStyleElement { } function getDynamicStyleSheetRules(style: any) { - if (style && style.sheet && style.sheet.rules) { + if (style?.sheet?.rules) { // Chrome, IE return style.sheet.rules; } - if (style && style.sheet && style.sheet.cssRules) { + if (style?.sheet?.cssRules) { // FF return style.sheet.cssRules; } @@ -1012,20 +1011,23 @@ export function prepend(parent: HTMLElement, child: T): T { return child; } -const SELECTOR_REGEX = /([\w\-]+)?(#([\w\-]+))?((\.([\w\-]+))*)/; -export function reset(parent: HTMLElement, ...children: Array) { +/** + * Removes all children from `parent` and appends `children` + */ +export function reset(parent: HTMLElement, ...children: Array) { parent.innerText = ''; - coalesce(children) - .forEach(child => { - if (child instanceof Node) { - parent.appendChild(child); - } else { - parent.appendChild(document.createTextNode(child as string)); - } - }); + for (const child of children) { + if (child instanceof Node) { + parent.appendChild(child); + } else if (typeof child === 'string') { + parent.appendChild(document.createTextNode(child)); + } + } } +const SELECTOR_REGEX = /([\w\-]+)?(#([\w\-]+))?((\.([\w\-]+))*)/; + export enum Namespace { HTML = 'http://www.w3.org/1999/xhtml', SVG = 'http://www.w3.org/2000/svg' @@ -1075,14 +1077,13 @@ function _$(namespace: Namespace, description: string, attrs? } }); - coalesce(children) - .forEach(child => { - if (child instanceof Node) { - result.appendChild(child); - } else { - result.appendChild(document.createTextNode(child as string)); - } - }); + for (const child of children) { + if (child instanceof Node) { + result.appendChild(child); + } else if (typeof child === 'string') { + result.appendChild(document.createTextNode(child)); + } + } return result as T; } @@ -1276,3 +1277,66 @@ export function triggerDownload(dataOrUri: Uint8Array | URI, name: string): void // Ensure to remove the element from DOM eventually setTimeout(() => document.body.removeChild(anchor)); } + +export enum DetectedFullscreenMode { + + /** + * The document is fullscreen, e.g. because an element + * in the document requested to be fullscreen. + */ + DOCUMENT = 1, + + /** + * The browser is fullsreen, e.g. because the user enabled + * native window fullscreen for it. + */ + BROWSER +} + +export interface IDetectedFullscreen { + + /** + * Figure out if the document is fullscreen or the browser. + */ + mode: DetectedFullscreenMode; + + /** + * Wether we know for sure that we are in fullscreen mode or + * it is a guess. + */ + guess: boolean; +} + +export function detectFullscreen(): IDetectedFullscreen | null { + + // Browser fullscreen: use DOM APIs to detect + if (document.fullscreenElement || (document).webkitFullscreenElement || (document).webkitIsFullScreen) { + return { mode: DetectedFullscreenMode.DOCUMENT, guess: false }; + } + + // There is no standard way to figure out if the browser + // is using native fullscreen. Via checking on screen + // height and comparing that to window height, we can guess + // it though. + + if (window.innerHeight === screen.height) { + // if the height of the window matches the screen height, we can + // safely assume that the browser is fullscreen because no browser + // chrome is taking height away (e.g. like toolbars). + return { mode: DetectedFullscreenMode.BROWSER, guess: false }; + } + + if (platform.isMacintosh || platform.isLinux) { + // macOS and Linux do not properly report `innerHeight`, only Windows does + if (window.outerHeight === screen.height && window.outerWidth === screen.width) { + // if the height of the browser matches the screen height, we can + // only guess that we are in fullscreen. It is also possible that + // the user has turned off taskbars in the OS and the browser is + // simply able to span the entire size of the screen. + return { mode: DetectedFullscreenMode.BROWSER, guess: true }; + } + } + + // Not in fullscreen + return null; +} diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts index 255bc67187..d967357163 100644 --- a/src/vs/base/browser/markdownRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -15,9 +15,10 @@ import { cloneAndChange } from 'vs/base/common/objects'; import { escape } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; -import { renderCodicons, markdownEscapeEscapedCodicons } from 'vs/base/common/codicons'; +import { markdownEscapeEscapedCodicons } from 'vs/base/common/codicons'; import { resolvePath } from 'vs/base/common/resources'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; +import { renderCodicons } from 'vs/base/browser/codicons'; export interface MarkedOptions extends marked.MarkedOptions { baseUrl?: never; @@ -143,7 +144,11 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende } }; renderer.paragraph = (text): string => { - return `

${markdown.supportThemeIcons ? renderCodicons(text) : text}

`; + if (markdown.supportThemeIcons) { + const elements = renderCodicons(text); + text = elements.map(e => typeof e === 'string' ? e : e.outerHTML).join(''); + } + return `

${text}

`; }; if (options.codeBlockRenderer) { diff --git a/src/vs/base/browser/ui/actionbar/actionViewItems.ts b/src/vs/base/browser/ui/actionbar/actionViewItems.ts index c1a7429181..de95c381fd 100644 --- a/src/vs/base/browser/ui/actionbar/actionViewItems.ts +++ b/src/vs/base/browser/ui/actionbar/actionViewItems.ts @@ -9,12 +9,12 @@ import * as nls from 'vs/nls'; import { Disposable } from 'vs/base/common/lifecycle'; import { SelectBox, ISelectOptionItem, ISelectBoxOptions } from 'vs/base/browser/ui/selectBox/selectBox'; import { IAction, IActionRunner, Action, IActionChangeEvent, ActionRunner, Separator, IActionViewItem } from 'vs/base/common/actions'; -import * as DOM from 'vs/base/browser/dom'; import * as types from 'vs/base/common/types'; -import { EventType, Gesture } from 'vs/base/browser/touch'; +import { EventType as TouchEventType, Gesture } from 'vs/base/browser/touch'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; import { DataTransfers } from 'vs/base/browser/dnd'; import { isFirefox } from 'vs/base/browser/browser'; +import { $, addClasses, addDisposableListener, append, EventHelper, EventLike, EventType, removeClasses, removeTabIndexAndUpdateFocus } from 'vs/base/browser/dom'; export interface IBaseActionViewItemOptions { draggable?: boolean; @@ -112,19 +112,19 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { if (isFirefox) { // Firefox: requires to set a text data transfer to get going - this._register(DOM.addDisposableListener(container, DOM.EventType.DRAG_START, e => e.dataTransfer?.setData(DataTransfers.TEXT, this._action.label))); + this._register(addDisposableListener(container, EventType.DRAG_START, e => e.dataTransfer?.setData(DataTransfers.TEXT, this._action.label))); } } - this._register(DOM.addDisposableListener(element, EventType.Tap, e => this.onClick(e))); + this._register(addDisposableListener(element, TouchEventType.Tap, e => this.onClick(e))); - this._register(DOM.addDisposableListener(element, DOM.EventType.MOUSE_DOWN, e => { + this._register(addDisposableListener(element, EventType.MOUSE_DOWN, e => { if (!enableDragging) { - DOM.EventHelper.stop(e, true); // do not run when dragging is on because that would disable it + EventHelper.stop(e, true); // do not run when dragging is on because that would disable it } if (this._action.enabled && e.button === 0) { - DOM.addClass(element, 'active'); + element.classList.add('active'); } })); @@ -133,15 +133,15 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { // main mouse button. This is for scenarios where e.g. some interaction forces // the Ctrl+key to be pressed and hold but the user still wants to interact // with the actions (for example quick access in quick navigation mode). - this._register(DOM.addDisposableListener(element, DOM.EventType.CONTEXT_MENU, e => { + this._register(addDisposableListener(element, EventType.CONTEXT_MENU, e => { if (e.button === 0 && e.ctrlKey === true) { this.onClick(e); } })); } - this._register(DOM.addDisposableListener(element, DOM.EventType.CLICK, e => { - DOM.EventHelper.stop(e, true); + this._register(addDisposableListener(element, EventType.CLICK, e => { + EventHelper.stop(e, true); // menus do not use the click event if (!(this.options && this.options.isMenu)) { @@ -149,20 +149,20 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { } })); - this._register(DOM.addDisposableListener(element, DOM.EventType.DBLCLICK, e => { - DOM.EventHelper.stop(e, true); + this._register(addDisposableListener(element, EventType.DBLCLICK, e => { + EventHelper.stop(e, true); })); - [DOM.EventType.MOUSE_UP, DOM.EventType.MOUSE_OUT].forEach(event => { - this._register(DOM.addDisposableListener(element, event, e => { - DOM.EventHelper.stop(e); - DOM.removeClass(element, 'active'); + [EventType.MOUSE_UP, EventType.MOUSE_OUT].forEach(event => { + this._register(addDisposableListener(element, event, e => { + EventHelper.stop(e); + element.classList.remove('active'); })); }); } - onClick(event: DOM.EventLike): void { - DOM.EventHelper.stop(event, true); + onClick(event: EventLike): void { + EventHelper.stop(event, true); const context = types.isUndefinedOrNull(this._context) ? this.options?.useEventAsContext ? event : undefined : this._context; this.actionRunner.run(this._action, context); @@ -171,14 +171,14 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { focus(): void { if (this.element) { this.element.focus(); - DOM.addClass(this.element, 'focused'); + this.element.classList.add('focused'); } } blur(): void { if (this.element) { this.element.blur(); - DOM.removeClass(this.element, 'focused'); + this.element.classList.remove('focused'); } } @@ -209,7 +209,7 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { dispose(): void { if (this.element) { - DOM.removeNode(this.element); + this.element.remove(); this.element = undefined; } @@ -243,7 +243,7 @@ export class ActionViewItem extends BaseActionViewItem { super.render(container); if (this.element) { - this.label = DOM.append(this.element, DOM.$('a.action-label')); + this.label = append(this.element, $('a.action-label')); } if (this.label) { @@ -259,7 +259,7 @@ export class ActionViewItem extends BaseActionViewItem { } if (this.options.label && this.options.keybinding && this.element) { - DOM.append(this.element, DOM.$('span.keybinding')).textContent = this.options.keybinding; + append(this.element, $('span.keybinding')).textContent = this.options.keybinding; } this.updateClass(); @@ -304,23 +304,23 @@ export class ActionViewItem extends BaseActionViewItem { updateClass(): void { if (this.cssClass && this.label) { - DOM.removeClasses(this.label, this.cssClass); + removeClasses(this.label, this.cssClass); } if (this.options.icon) { this.cssClass = this.getAction().class; if (this.label) { - DOM.addClass(this.label, 'codicon'); + this.label.classList.add('codicon'); if (this.cssClass) { - DOM.addClasses(this.label, this.cssClass); + addClasses(this.label, this.cssClass); } } this.updateEnabled(); } else { if (this.label) { - DOM.removeClass(this.label, 'codicon'); + this.label.classList.remove('codicon'); } } } @@ -329,22 +329,22 @@ export class ActionViewItem extends BaseActionViewItem { if (this.getAction().enabled) { if (this.label) { this.label.removeAttribute('aria-disabled'); - DOM.removeClass(this.label, 'disabled'); + this.label.classList.remove('disabled'); this.label.tabIndex = 0; } if (this.element) { - DOM.removeClass(this.element, 'disabled'); + this.element.classList.remove('disabled'); } } else { if (this.label) { this.label.setAttribute('aria-disabled', 'true'); - DOM.addClass(this.label, 'disabled'); - DOM.removeTabIndexAndUpdateFocus(this.label); + this.label.classList.add('disabled'); + removeTabIndexAndUpdateFocus(this.label); } if (this.element) { - DOM.addClass(this.element, 'disabled'); + this.element.classList.add('disabled'); } } } @@ -352,9 +352,9 @@ export class ActionViewItem extends BaseActionViewItem { updateChecked(): void { if (this.label) { if (this.getAction().checked) { - DOM.addClass(this.label, 'checked'); + this.label.classList.add('checked'); } else { - DOM.removeClass(this.label, 'checked'); + this.label.classList.remove('checked'); } } } diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 13ec2ad808..f6c5166ac8 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -10,7 +10,7 @@ import * as DOM from 'vs/base/browser/dom'; import * as types from 'vs/base/common/types'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { Event, Emitter } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import { IActionViewItemOptions, ActionViewItem, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; export const enum ActionsOrientation { @@ -47,8 +47,9 @@ export class ActionBar extends Disposable implements IActionRunner { private _actionRunner: IActionRunner; private _context: unknown; - private _orientation: ActionsOrientation; - private _triggerKeys: ActionTrigger; + private readonly _orientation: ActionsOrientation; + private readonly _triggerKeys: ActionTrigger; + private _actionIds: string[]; // View Items viewItems: IActionViewItem[]; @@ -60,16 +61,16 @@ export class ActionBar extends Disposable implements IActionRunner { protected actionsList: HTMLElement; private _onDidBlur = this._register(new Emitter()); - readonly onDidBlur: Event = this._onDidBlur.event; + readonly onDidBlur = this._onDidBlur.event; private _onDidCancel = this._register(new Emitter()); - readonly onDidCancel: Event = this._onDidCancel.event; + readonly onDidCancel = this._onDidCancel.event; private _onDidRun = this._register(new Emitter()); - readonly onDidRun: Event = this._onDidRun.event; + readonly onDidRun = this._onDidRun.event; private _onDidBeforeRun = this._register(new Emitter()); - readonly onDidBeforeRun: Event = this._onDidBeforeRun.event; + readonly onDidBeforeRun = this._onDidBeforeRun.event; constructor(container: HTMLElement, options: IActionBarOptions = {}) { super(); @@ -92,6 +93,7 @@ export class ActionBar extends Disposable implements IActionRunner { this._register(this._actionRunner.onDidRun(e => this._onDidRun.fire(e))); this._register(this._actionRunner.onDidBeforeRun(e => this._onDidBeforeRun.fire(e))); + this._actionIds = []; this.viewItems = []; this.focusedItem = undefined; @@ -245,6 +247,10 @@ export class ActionBar extends Disposable implements IActionRunner { return this.domNode; } + hasAction(action: IAction): boolean { + return this._actionIds.includes(action.id); + } + push(arg: IAction | ReadonlyArray, options: IActionOptions = {}): void { const actions: ReadonlyArray = Array.isArray(arg) ? arg : [arg]; @@ -279,9 +285,11 @@ export class ActionBar extends Disposable implements IActionRunner { if (index === null || index < 0 || index >= this.actionsList.children.length) { this.actionsList.appendChild(actionViewItemElement); this.viewItems.push(item); + this._actionIds.push(action.id); } else { this.actionsList.insertBefore(actionViewItemElement, this.actionsList.children[index]); this.viewItems.splice(index, 0, item); + this._actionIds.splice(index, 0, action.id); index++; } }); @@ -317,12 +325,14 @@ export class ActionBar extends Disposable implements IActionRunner { if (index >= 0 && index < this.viewItems.length) { this.actionsList.removeChild(this.actionsList.childNodes[index]); dispose(this.viewItems.splice(index, 1)); + this._actionIds.splice(index, 1); } } clear(): void { dispose(this.viewItems); this.viewItems = []; + this._actionIds = []; DOM.clearNode(this.actionsList); } @@ -463,7 +473,9 @@ export class ActionBar extends Disposable implements IActionRunner { dispose(this.viewItems); this.viewItems = []; - DOM.removeNode(this.getContainer()); + this._actionIds = []; + + this.getContainer().remove(); super.dispose(); } diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts index 90f01d31db..4a0a3f2f67 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts @@ -118,7 +118,7 @@ export class BreadcrumbsWidget { dispose(): void { this._disposables.dispose(); - dispose(this._pendingLayout); + this._pendingLayout?.dispose(); this._onDidSelectItem.dispose(); this._onDidFocusItem.dispose(); this._onDidChangeFocus.dispose(); @@ -131,9 +131,7 @@ export class BreadcrumbsWidget { if (dim && dom.Dimension.equals(dim, this._dimension)) { return; } - if (this._pendingLayout) { - this._pendingLayout.dispose(); - } + this._pendingLayout?.dispose(); if (dim) { // only measure this._pendingLayout = this._updateDimensions(dim); @@ -180,8 +178,8 @@ export class BreadcrumbsWidget { if (style.breadcrumbsHoverForeground) { content += `.monaco-breadcrumbs .monaco-breadcrumb-item:hover:not(.focused):not(.selected) { color: ${style.breadcrumbsHoverForeground}}\n`; } - if (this._styleElement.innerHTML !== content) { - this._styleElement.innerHTML = content; + if (this._styleElement.innerText !== content) { + this._styleElement.innerText = content; } } diff --git a/src/vs/base/browser/ui/button/button.ts b/src/vs/base/browser/ui/button/button.ts index 2976a3c145..2c310a1794 100644 --- a/src/vs/base/browser/ui/button/button.ts +++ b/src/vs/base/browser/ui/button/button.ts @@ -4,15 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./button'; -import * as DOM from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Color } from 'vs/base/common/color'; 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 { renderCodiconsAsElement } from 'vs/base/browser/codicons'; +import { Gesture, EventType as TouchEventType } from 'vs/base/browser/touch'; +import { renderCodicons } from 'vs/base/browser/codicons'; +import { addDisposableListener, IFocusTracker, EventType, EventHelper, trackFocus, reset, removeTabIndexAndUpdateFocus } from 'vs/base/browser/dom'; export interface IButtonOptions extends IButtonStyles { readonly title?: boolean | string; @@ -52,7 +52,7 @@ export class Button extends Disposable { private _onDidClick = this._register(new Emitter()); get onDidClick(): BaseEvent { return this._onDidClick.event; } - private focusTracker: DOM.IFocusTracker; + private focusTracker: IFocusTracker; constructor(container: HTMLElement, options?: IButtonOptions) { super(); @@ -71,7 +71,7 @@ export class Button extends Disposable { this.buttonBorder = this.options.buttonBorder; this._element = document.createElement('a'); - DOM.addClass(this._element, 'monaco-button'); + this._element.classList.add('monaco-button'); this._element.tabIndex = 0; this._element.setAttribute('role', 'button'); @@ -79,10 +79,10 @@ export class Button extends Disposable { this._register(Gesture.addTarget(this._element)); - [DOM.EventType.CLICK, EventType.Tap].forEach(eventType => { - this._register(DOM.addDisposableListener(this._element, eventType, e => { + [EventType.CLICK, TouchEventType.Tap].forEach(eventType => { + this._register(addDisposableListener(this._element, eventType, e => { if (!this.enabled) { - DOM.EventHelper.stop(e); + EventHelper.stop(e); return; } @@ -90,7 +90,7 @@ export class Button extends Disposable { })); }); - this._register(DOM.addDisposableListener(this._element, DOM.EventType.KEY_DOWN, e => { + this._register(addDisposableListener(this._element, EventType.KEY_DOWN, e => { const event = new StandardKeyboardEvent(e); let eventHandled = false; if (this.enabled && (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space))) { @@ -102,22 +102,22 @@ export class Button extends Disposable { } if (eventHandled) { - DOM.EventHelper.stop(event, true); + EventHelper.stop(event, true); } })); - this._register(DOM.addDisposableListener(this._element, DOM.EventType.MOUSE_OVER, e => { - if (!DOM.hasClass(this._element, 'disabled')) { + this._register(addDisposableListener(this._element, EventType.MOUSE_OVER, e => { + if (!this._element.classList.contains('disabled')) { this.setHoverBackground(); } })); - this._register(DOM.addDisposableListener(this._element, DOM.EventType.MOUSE_OUT, e => { + this._register(addDisposableListener(this._element, EventType.MOUSE_OUT, e => { this.applyStyles(); // restore standard styles })); // Also set hover background when button is focused for feedback - this.focusTracker = this._register(DOM.trackFocus(this._element)); + this.focusTracker = this._register(trackFocus(this._element)); this._register(this.focusTracker.onDidFocus(() => this.setHoverBackground())); this._register(this.focusTracker.onDidBlur(() => this.applyStyles())); // restore standard styles @@ -176,11 +176,9 @@ export class Button extends Disposable { } set label(value: string) { - if (!DOM.hasClass(this._element, 'monaco-text-button')) { - DOM.addClass(this._element, 'monaco-text-button'); - } + this._element.classList.add('monaco-text-button'); if (this.options.supportCodicons) { - DOM.reset(this._element, ...renderCodiconsAsElement(value)); + reset(this._element, ...renderCodicons(value)); } else { this._element.textContent = value; } @@ -192,25 +190,25 @@ export class Button extends Disposable { } } - // {{SQL CARBON EDIT}} from addClass to addClasses to support multiple classes @todo anthonydresser 4/12/19 invesitgate a better way to do this. + // {{SQL CARBON EDIT}} set icon(iconClassName: string) { - DOM.addClasses(this._element, ...iconClassName.split(' ')); + this._element.classList.add(...iconClassName.split(' ')); } set enabled(value: boolean) { if (value) { - DOM.removeClass(this._element, 'disabled'); + this._element.classList.remove('disabled'); this._element.setAttribute('aria-disabled', String(false)); this._element.tabIndex = 0; } else { - DOM.addClass(this._element, 'disabled'); + this._element.classList.add('disabled'); this._element.setAttribute('aria-disabled', String(true)); - DOM.removeTabIndexAndUpdateFocus(this._element); + removeTabIndexAndUpdateFocus(this._element); } } get enabled() { - return !DOM.hasClass(this._element, 'disabled'); + return !this._element.classList.contains('disabled'); } focus(): void { @@ -238,7 +236,7 @@ export class ButtonGroup extends Disposable { // Implement keyboard access in buttons if there are multiple if (count > 1) { - this._register(DOM.addDisposableListener(button.element, DOM.EventType.KEY_DOWN, e => { + this._register(addDisposableListener(button.element, EventType.KEY_DOWN, e => { const event = new StandardKeyboardEvent(e); let eventHandled = true; @@ -254,7 +252,7 @@ export class ButtonGroup extends Disposable { if (eventHandled && typeof buttonIndexToFocus === 'number') { this._buttons[buttonIndexToFocus].focus(); - DOM.EventHelper.stop(e, true); + EventHelper.stop(e, true); } })); diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 82acc8995b8d7ee0338bc65547844f5f99b953ce..bb7ce5a58291a55c67f7f02fdc5a6d66c1459b88 100644 GIT binary patch delta 4686 zcmZA53s_WT8VB(II|Ga&iik4EO<;uaG9V1YRUA-JQSp{Yco!9<5=ApZQ%CKxW;c_p zwyRyY*m7$rGbM9(+p>0R%i3y-%*<}*D>ExA725yt-RIeTw#VOj=ZwsBX3qQl-w%&< zIy;X!cX&eU0J#xB&61|NW_QvrJpfl25W9Za@^uY?-tT?@B=-jn#W&X1Eluciu9`o4 zj0zh0gzLRqPqXkIV3^;Z+)wMJ`mf&UneLR0C zbwyoMecIy*qqhJb@l70&%_}coQ{Oq@S>WR$ASV6R4o_Jn#C}7-x$x_`3!_VaL@(!; zd*k;>K-d0O_gzbQk)cRHe4e~yUFi@flTaE8Ho z;gOFu5(tY#nB#rEuFr+{h_JRv0t}U?W1{aSW0B1 zjFO;?mN7C`DrB6DmkD?jzr*8bLpesH3R5r@6_Sd{n2w1UiGfJKUM$0M?2vqRMKs3Y z0sIxG#Dl+K8-BshI4nKm@OS(RB~pqUJcmZygs0eK5v>w=zO`?pD->n-U39a3w3`pG z7AdwEtU85`?zVw=ILS71|G4T`M?Ynj5Utc{B82Cl3~QBQU&C6h@K)=oUha%Lcy@(ix5HYi zki~kXlEJOMC{L)2XICk%1y~ytR|Tw%it7W`CdHKkE2Ow)U~N`-k#&pWI)b%TaYezp zT5)Z`x<=u8R(^&3?1K#9$#qI%T8~A!L+L!*u7pe9xBYK3NdoJw zO43;EOCU*NZB<;%ux?YZ-+PymzO1(c5%xh++BfvR+sP#j>kGxj4a@Eb;xdQzmEvNC zWhpLwSVl1fV12C^2e7_T3y%<_!1@jd*#{;Ftka5F0_%IlRDtz_ zV!puoQ88&?ol(pjSU)MI53HXRa|qTi3if?GtC&r&&MAJd5Ule&AqEz#F2(4AWj7=- z#9;lZ7-x(T#bARG#fXD(D25%3Q!(~n!W07#CR{NJVIpk2N~R)=OEDi|dMG9(Oi#tk zgkh`Ln4U0f2^(`1CQ30;VR|cOD@-57l!b{_%v%_?fsM%v6N=%17{W096n3-5Dh4x5 zf5nJ~VQ<+O)-Z93u?@q1wK2e9;uWJDhCOX#sKc;#ZH#xAL=|Gv!z3wYK1{M=`op9s z&HBc#fb%zqi}@x-{dNeF&Mw%K!eFs z9BnWG#UTfiuQ={t3KRz)Orhckgeg)ShA_p7V-coAaX`Y1q!16Bl`y3W$5=-x&QF-2 z;v|JBQ=F+V<%-i4X0+m*g&CtbabdzkF>O~1;%|2&A@^{bWGU4OX}yX)5|u{-?=C3dH; zRbqGgl}hYRZ&6}*`Z^_Sa@K!%6N%l$o0ZsI zyhVxK#9I|NdoWikZu?-aQQQE+T&uVhgt^Yfra@cljqx)jwe?v1%-p?va+l(M5axEp zogvH}3OiWuRFdDibl`~413bG|an}fQpTa5D`xSSRFuN7^lrZ*dh`URe2Nn03Fb^s2 zIAI=EIL!KplAf&g$0BY=Vfe99>;reEFpnwNU%verNlIID65QVBc=m+i&K2fK#XT&{ z9%b)lTPqXZ4%NorH_$O~`oKd8sR;`b_9glgw8Yd>$q~u7CGSh_Ozuvp zPPsYdr9r_#U#FI*ZcmF#+mk*g{XS2P=hWc68I>6~XFQm3IBr7hfJZn$ZYgtw(`>yQ0*+)mjjo6wK z$k~zea_->V>A7ojcjvz2uk&QD3@C6nGZVPngN9Es@f4bmIVL{>Q z!sA6%MNbu-F7_ASRlKM8L`inZijwUm`%8|FTs-p8(vs3gNA(@GY1HYUKR742KiC%J;qgzyM5fDaX*hQ7{7b` zi3!6dbX58(+bYjY+%WOrBurX9Y2W0c$@3;}s@hTYMwK-sZc59Pd!`(jnl$yu)brDV z)2^GgXWH@Uk<%+@^qEmLW9y7JF4}QX$IPmk+p8Vb+11OdPtD4n)iUc~O+-y?&DM&V zBQ<9)_FY_gaa(OuGxj&XSkV>~g1|MNID#+_Ffg>~OO zS~8rec{8HV%`aG$)ctvU4^O<)A3IPI6LX#Xw+3F!12=U}lgjo@A8mI;I3_qM9TVGo z99tLeil}dCUb%Wr`2b>wb$o6sNz?uwc=ORTp;i5#%eNO}B<6vuExt{$vdhH5>@qx@hLA#-JJa8^0dOC&ewE zxoB?hy-`Cp0H2QK6G9u8EML(OUN#u`tQP3vx$98;;0lQQfxx6L&ooc(RQv;zTtga1i8=hgWn?+{+{x@>l*Sqg zc)jojz0Yszc=ZPao6TmcdDFaQtL#4i$dIhLkKn)f61U((>BE2EH@qkw43I(i9{2CT%5ScTPCgPX7xP4HnopR)lQaWgid8Monf zY{egN2kye%@MAmf#eLX;`|$uC#7;bfUD%CB@fe=OUbJ8zp2o9y4*T&uUcevm5?;oi za1gKh@EZP%!}u#+$D4Q?f5$uc2j0Um{1flv102T*e2h=(faFQO6iAT_loA;%r7}dyWT=$maXf*ium`0W zf=Y}=70M+Rqc9dDFc3Wug#hN`dTf(yzW1F_froJfXC)3taVvhr4|qd@JL4^Ugd!EKqDMn1u>2veqlM8_e|z16UgXpS!RJVHPQNBg|sOzJyt#*r70uioFW6RIzJe zmMQix%yPv}hFPK5(=azEb~g;;!a+LP!`#RllHUH3u=ozeyt_$Zg@18agzt0SH7O1P zj8AbSVAd;_9cF{#n80jQ92l6J6-Nigy^@5@tecc1vNkIY7R;>*?iJ_reC{H>c(X-G z2+qn5(+%^r;+(^rR-AYk#+SpnhB1mM0Am&N0OpKhGQgZw%m|osifIA!tzvG#oL5W` zm<#R&;e}xWb5Su?V7^lf7#P<=#HfM!K{0e-epHMfn4c7b2!@-&VI;x)tQbx(mlf8t zy7nXfV?G$ym&ELX;i_?%VlY<}^9<%U#bjfaC}te2D5f23kYet^ayT3&Agot03t`(R zrXp;x!@rCH3ClKc7?rSmyB&rmY&*sHgbh^;QrPy2kqR587_P7#6k`^a{pT=nVLK^C zFKlPU5Qg=2;f0vTu8sSeA&beQk3 zQHn_q8?Af{eb^Yq_=k;ETm-OeaEB`amP6xkIl#s#t_j%QiVFj_kK*cpjaOVEunCIm z1U6CORn{cF|Ge-l!JA~oWdoa{aF{h!aRI^hRa`}|{S=oHY?|VFf=yRkRInL}D+{*2 z;_`ycRCtf?zs*wIXt3FeyA3u+am&HxD(*Yj0g9UsHcxQ}!saV(L)Zevy$D;VxFKPS z6n7=;K=SdzwFz6SxIkeCDXvo35`|XQ!HVk@wp4M^!VXbfxv*u5%NKU2;u?l6S6s-j z6^g4FK0Ayz#4Qbbt#UJFhbwMw*b$059CoDQHisRhxYuDT6*oNWXvJL*Tcx=5VaF=& zf7o$X@%6vN2jdkF1+Wtoj|Z?56%PuqlN7G7Rx6G_>~%_bV{4SSESRjs%Q{7goBydw z+OgKU_rI2D9LhRPNqg2hC1I@7mADz6p`-6@A9CLD)@-r$X3f$IA}duxeeSRk1r`566Dl zv!Lg)o_l)r>2)kFA#QQp<=%JqDe1E@z9{~9!UKtEiOUk36OSccN-9a(lVp-(lIxR? zr39zcr5sG{m|B~9XX@#`Eqz=2jqi6j&6_qattIVvdRcm1dQ(P5#z@Y|eZ-^P{ZLtfs6TS>I@#u3#FmNYClyTEGU-6Iw>qu5 zuDE(l^~vjsue(^2TQjVtrl!7TZOy648IzkQADq&8%8n^!YTVR0Q(v#`Tsy0FbM4c$ zXQstXdvx0Cbvx_MPcI2{i(Q==cw_X=sDLco-90ePm+A>h$_i}WxH8P=+3smUSH!br zLb`_}1&4HxkevL8oDxaS&&f~j6x35f`b%EK;Gh8!LGueg%4+EM!j;R@Pwea;9~SxW zZISz<1Ktj8L)INB&hLGp<13z)h~@_(!uq5?bLE#4J5m>BzLy``!Nf%s#jJZREZX~Q w)bN+!h6$dBJkKE-flYTDA0rikDW7i%3Yafew+YmLy;K6HPM1p{%k&NY9Vj%6j{pDw diff --git a/src/vs/base/browser/ui/codicons/codiconLabel.ts b/src/vs/base/browser/ui/codicons/codiconLabel.ts index 3da4558e5d..8141a44925 100644 --- a/src/vs/base/browser/ui/codicons/codiconLabel.ts +++ b/src/vs/base/browser/ui/codicons/codiconLabel.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { reset } from 'vs/base/browser/dom'; -import { renderCodiconsAsElement } from 'vs/base/browser/codicons'; +import { renderCodicons } from 'vs/base/browser/codicons'; export class CodiconLabel { @@ -13,7 +13,7 @@ export class CodiconLabel { ) { } set text(text: string) { - reset(this._container, ...renderCodiconsAsElement(text ?? '')); + reset(this._container, ...renderCodicons(text ?? '')); } set title(title: string) { diff --git a/src/vs/base/browser/ui/codicons/codiconStyles.ts b/src/vs/base/browser/ui/codicons/codiconStyles.ts index 274e9de74c..3ef34818ba 100644 --- a/src/vs/base/browser/ui/codicons/codiconStyles.ts +++ b/src/vs/base/browser/ui/codicons/codiconStyles.ts @@ -20,7 +20,7 @@ function initialize() { for (let c of iconRegistry.all) { rules.push(formatRule(c)); } - codiconStyleSheet.innerHTML = rules.join('\n'); + codiconStyleSheet.textContent = rules.join('\n'); } const delayer = new RunOnceScheduler(updateAll, 0); diff --git a/src/vs/base/browser/ui/contextview/contextview.ts b/src/vs/base/browser/ui/contextview/contextview.ts index b8f31da856..d80fb4b9a5 100644 --- a/src/vs/base/browser/ui/contextview/contextview.ts +++ b/src/vs/base/browser/ui/contextview/contextview.ts @@ -139,7 +139,7 @@ export class ContextView extends Disposable { if (this.shadowRoot) { this.shadowRoot.removeChild(this.view); this.shadowRoot = null; - DOM.removeNode(this.shadowRootHostElement!); + this.shadowRootHostElement?.remove(); this.shadowRootHostElement = null; } else { this.container.removeChild(this.view); @@ -364,6 +364,10 @@ let SHADOW_ROOT_CSS = /* css */ ` -ms-user-select: none; } + :host { + font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", system-ui, "Ubuntu", "Droid Sans", sans-serif; + } + :host-context(.mac) { font-family: -apple-system, BlinkMacSystemFont, sans-serif; } :host-context(.mac:lang(zh-Hans)) { font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", sans-serif; } :host-context(.mac:lang(zh-Hant)) { font-family: -apple-system, BlinkMacSystemFont, "PingFang TC", sans-serif; } diff --git a/src/vs/base/browser/ui/dropdown/dropdown.ts b/src/vs/base/browser/ui/dropdown/dropdown.ts index 9a06c87cac..0acb019a5a 100644 --- a/src/vs/base/browser/ui/dropdown/dropdown.ts +++ b/src/vs/base/browser/ui/dropdown/dropdown.ts @@ -10,7 +10,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { IContextViewProvider, IAnchor, AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { IMenuOptions } from 'vs/base/browser/ui/menu/menu'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { EventHelper, EventType, removeClass, addClass, append, $, addDisposableListener, DOMEvent } from 'vs/base/browser/dom'; +import { EventHelper, EventType, append, $, addDisposableListener, DOMEvent } from 'vs/base/browser/dom'; import { IContextMenuProvider } from 'vs/base/browser/contextmenu'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Emitter } from 'vs/base/common/event'; @@ -162,7 +162,7 @@ export class Dropdown extends BaseDropdown { show(): void { super.show(); - addClass(this.element, 'active'); + this.element.classList.add('active'); this.contextViewProvider.showContextView({ getAnchor: () => this.getAnchor(), @@ -184,7 +184,7 @@ export class Dropdown extends BaseDropdown { } protected onHide(): void { - removeClass(this.element, 'active'); + this.element.classList.remove('active'); } hide(): void { @@ -206,8 +206,8 @@ export interface IActionProvider { export interface IDropdownMenuOptions extends IBaseDropdownOptions { contextMenuProvider: IContextMenuProvider; - actions?: IAction[]; - actionProvider?: IActionProvider; + readonly actions?: IAction[]; + readonly actionProvider?: IActionProvider; menuClassName?: string; menuAsChild?: boolean; // scope down for #99448 } @@ -253,7 +253,7 @@ export class DropdownMenu extends BaseDropdown { show(): void { super.show(); - addClass(this.element, 'active'); + this.element.classList.add('active'); this._contextMenuProvider.showContextMenu({ getAnchor: () => this.element, @@ -275,6 +275,6 @@ export class DropdownMenu extends BaseDropdown { private onHide(): void { this.hide(); - removeClass(this.element, 'active'); + this.element.classList.remove('active'); } } diff --git a/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts index c6ffcc87c8..054813f782 100644 --- a/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts +++ b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts @@ -8,12 +8,11 @@ import { IAction, IActionRunner, IActionViewItemProvider } from 'vs/base/common/ import { IDisposable } from 'vs/base/common/lifecycle'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; -import { append, $, addClasses } from 'vs/base/browser/dom'; +import { append, $ } from 'vs/base/browser/dom'; import { Emitter } from 'vs/base/common/event'; import { BaseActionViewItem, IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { IActionProvider, DropdownMenu, IDropdownMenuOptions, ILabelRenderer } from 'vs/base/browser/ui/dropdown/dropdown'; import { IContextMenuProvider } from 'vs/base/browser/contextmenu'; -import { asArray } from 'vs/base/common/arrays'; export interface IKeybindingProvider { (action: IAction): ResolvedKeybinding | undefined; @@ -60,14 +59,20 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { const labelRenderer: ILabelRenderer = (el: HTMLElement): IDisposable | null => { this.element = append(el, $('a.action-label')); - const classNames = this.options.classNames ? asArray(this.options.classNames) : []; + let classNames: string[] = []; + + if (typeof this.options.classNames === 'string') { + classNames = this.options.classNames.split(/\W+/g).filter(s => !!s); + } else if (this.options.classNames) { + classNames = this.options.classNames; + } // todo@aeschli: remove codicon, should come through `this.options.classNames` if (!classNames.find(c => c === 'icon')) { classNames.push('codicon'); } - addClasses(this.element, ...classNames); + this.element.classList.add(...classNames); this.element.tabIndex = 0; this.element.setAttribute('role', 'button'); @@ -78,19 +83,15 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { return null; }; + const isActionsArray = Array.isArray(this.menuActionsOrProvider); const options: IDropdownMenuOptions = { contextMenuProvider: this.contextMenuProvider, labelRenderer: labelRenderer, - menuAsChild: this.options.menuAsChild + menuAsChild: this.options.menuAsChild, + actions: isActionsArray ? this.menuActionsOrProvider as IAction[] : undefined, + actionProvider: isActionsArray ? undefined : this.menuActionsOrProvider as IActionProvider }; - // Render the DropdownMenu around a simple action to toggle it - if (Array.isArray(this.menuActionsOrProvider)) { - options.actions = this.menuActionsOrProvider; - } else { - options.actionProvider = this.menuActionsOrProvider as IActionProvider; - } - this.dropdownMenu = this._register(new DropdownMenu(container, options)); this._register(this.dropdownMenu.onDidChangeVisibility(visible => { this.element?.setAttribute('aria-expanded', `${visible}`); diff --git a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts index 1d582c6959..f7618c6ba1 100644 --- a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts +++ b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as objects from 'vs/base/common/objects'; -import { renderCodicons } from 'vs/base/common/codicons'; -import { escape } from 'vs/base/common/strings'; +import * as dom from 'vs/base/browser/dom'; +import { renderCodicons } from 'vs/base/browser/codicons'; export interface IHighlight { start: number; @@ -15,7 +15,7 @@ export interface IHighlight { export class HighlightedLabel { - private domNode: HTMLElement; + private readonly domNode: HTMLElement; private text: string = ''; private title: string = ''; private highlights: IHighlight[] = []; @@ -44,10 +44,6 @@ export class HighlightedLabel { return; } - if (!Array.isArray(highlights)) { - highlights = []; - } - this.text = text; this.title = title; this.highlights = highlights; @@ -56,7 +52,7 @@ export class HighlightedLabel { private render(): void { - let htmlContent = ''; + const children: HTMLSpanElement[] = []; let pos = 0; for (const highlight of this.highlights) { @@ -64,31 +60,26 @@ export class HighlightedLabel { continue; } if (pos < highlight.start) { - htmlContent += ''; const substring = this.text.substring(pos, highlight.start); - htmlContent += this.supportCodicons ? renderCodicons(escape(substring)) : escape(substring); - htmlContent += ''; + children.push(dom.$('span', undefined, ...this.supportCodicons ? renderCodicons(substring) : [substring])); pos = highlight.end; } - if (highlight.extraClasses) { - htmlContent += ``; - } else { - htmlContent += ``; - } + const substring = this.text.substring(highlight.start, highlight.end); - htmlContent += this.supportCodicons ? renderCodicons(escape(substring)) : escape(substring); - htmlContent += ''; + const element = dom.$('span.highlight', undefined, ...this.supportCodicons ? renderCodicons(substring) : [substring]); + if (highlight.extraClasses) { + element.classList.add(highlight.extraClasses); + } + children.push(element); pos = highlight.end; } if (pos < this.text.length) { - htmlContent += ''; - const substring = this.text.substring(pos); - htmlContent += this.supportCodicons ? renderCodicons(escape(substring)) : escape(substring); - htmlContent += ''; + const substring = this.text.substring(pos,); + children.push(dom.$('span', undefined, ...this.supportCodicons ? renderCodicons(substring) : [substring])); } - this.domNode.innerHTML = htmlContent; + dom.reset(this.domNode, ...children); if (this.title) { this.domNode.title = this.title; } else { diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index e61b8b8816..561bbfdcb8 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -6,7 +6,6 @@ import { getOrDefault } from 'vs/base/common/objects'; import { IDisposable, dispose, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Gesture, EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch'; -import * as DOM from 'vs/base/browser/dom'; import { Event, Emitter } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; import { SmoothScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; @@ -22,6 +21,7 @@ import { DataTransfers, StaticDND, IDragAndDropData } from 'vs/base/browser/dnd' import { disposableTimeout, Delayer } from 'vs/base/common/async'; import { isFirefox } from 'vs/base/browser/browser'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; +import { $, animate, getContentHeight, getContentWidth, getTopLeftOffset, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; interface IItem { readonly id: string; @@ -260,7 +260,7 @@ export class ListView implements ISpliceable, IDisposable { } this._horizontalScrolling = value; - DOM.toggleClass(this.domNode, 'horizontal-scrolling', this._horizontalScrolling); + this.domNode.classList.toggle('horizontal-scrolling', this._horizontalScrolling); if (this._horizontalScrolling) { for (const item of this.items) { @@ -268,7 +268,7 @@ export class ListView implements ISpliceable, IDisposable { } this.updateScrollWidth(); - this.scrollableElement.setScrollDimensions({ width: DOM.getContentWidth(this.domNode) }); + this.scrollableElement.setScrollDimensions({ width: getContentWidth(this.domNode) }); this.rowsContainer.style.width = `${Math.max(this.scrollWidth || 0, this.renderWidth)}px`; } else { this.scrollableElementWidthDelayer.cancel(); @@ -303,13 +303,13 @@ export class ListView implements ISpliceable, IDisposable { this.domNode = document.createElement('div'); this.domNode.className = 'monaco-list'; - DOM.addClass(this.domNode, this.domId); + this.domNode.classList.add(this.domId); this.domNode.tabIndex = 0; - DOM.toggleClass(this.domNode, 'mouse-support', typeof options.mouseSupport === 'boolean' ? options.mouseSupport : true); + this.domNode.classList.toggle('mouse-support', typeof options.mouseSupport === 'boolean' ? options.mouseSupport : true); this._horizontalScrolling = getOrDefault(options, o => o.horizontalScrolling, DefaultOptions.horizontalScrolling); - DOM.toggleClass(this.domNode, 'horizontal-scrolling', this._horizontalScrolling); + this.domNode.classList.toggle('horizontal-scrolling', this._horizontalScrolling); this.additionalScrollHeight = typeof options.additionalScrollHeight === 'undefined' ? 0 : options.additionalScrollHeight; @@ -325,7 +325,7 @@ export class ListView implements ISpliceable, IDisposable { this.disposables.add(Gesture.addTarget(this.rowsContainer)); - this.scrollable = new Scrollable(getOrDefault(options, o => o.smoothScrolling, false) ? 125 : 0, cb => DOM.scheduleAtNextAnimationFrame(cb)); + this.scrollable = new Scrollable(getOrDefault(options, o => o.smoothScrolling, false) ? 125 : 0, cb => scheduleAtNextAnimationFrame(cb)); this.scrollableElement = this.disposables.add(new SmoothScrollableElement(this.rowsContainer, { alwaysConsumeMouseWheel: true, horizontal: ScrollbarVisibility.Auto, @@ -510,7 +510,7 @@ export class ListView implements ISpliceable, IDisposable { this.rowsContainer.style.height = `${this._scrollHeight}px`; if (!this.scrollableElementUpdateDisposable) { - this.scrollableElementUpdateDisposable = DOM.scheduleAtNextAnimationFrame(() => { + this.scrollableElementUpdateDisposable = scheduleAtNextAnimationFrame(() => { this.scrollableElement.setScrollDimensions({ scrollHeight: this.scrollHeight }); this.updateScrollWidth(); this.scrollableElementUpdateDisposable = null; @@ -629,7 +629,7 @@ export class ListView implements ISpliceable, IDisposable { layout(height?: number, width?: number): void { let scrollDimensions: INewScrollDimensions = { - height: typeof height === 'number' ? height : DOM.getContentHeight(this.domNode) + height: typeof height === 'number' ? height : getContentHeight(this.domNode) }; if (this.scrollableElementUpdateDisposable) { @@ -649,7 +649,7 @@ export class ListView implements ISpliceable, IDisposable { if (this.horizontalScrolling) { this.scrollableElement.setScrollDimensions({ - width: typeof width === 'number' ? width : DOM.getContentWidth(this.domNode) + width: typeof width === 'number' ? width : getContentWidth(this.domNode) }); } } @@ -754,7 +754,7 @@ export class ListView implements ISpliceable, IDisposable { } item.row.domNode.style.width = isFirefox ? '-moz-fit-content' : 'fit-content'; - item.width = DOM.getContentWidth(item.row.domNode); + item.width = getContentWidth(item.row.domNode); const style = window.getComputedStyle(item.row.domNode); if (style.paddingLeft) { @@ -785,7 +785,7 @@ export class ListView implements ISpliceable, IDisposable { item.row!.domNode!.setAttribute('aria-posinset', String(this.accessibilityProvider.getPosInSet(item.element, index))); item.row!.domNode!.setAttribute('id', this.getElementDomId(index)); - DOM.toggleClass(item.row!.domNode!, 'drop-target', item.dropTarget); + item.row!.domNode!.classList.toggle('drop-target', item.dropTarget); } private removeItemFromDOM(index: number): void { @@ -896,7 +896,9 @@ export class ListView implements ISpliceable, IDisposable { this.render(previousRenderRange, e.scrollTop, e.height, e.scrollLeft, e.scrollWidth); if (this.supportDynamicHeights) { - this._rerender(e.scrollTop, e.height); + // Don't update scrollTop from within an scroll event + // so we don't break smooth scrolling. #104144 + this._rerender(e.scrollTop, e.height, false); } } catch (err) { console.error('Got bad scroll event:', e); @@ -934,7 +936,7 @@ export class ListView implements ISpliceable, IDisposable { label = String(elements.length); } - const dragImage = DOM.$('.monaco-drag-image'); + const dragImage = $('.monaco-drag-image'); dragImage.textContent = label; document.body.appendChild(dragImage); event.dataTransfer.setDragImage(dragImage, -10, -10); @@ -1015,11 +1017,11 @@ export class ListView implements ISpliceable, IDisposable { this.currentDragFeedbackDisposable.dispose(); if (feedback[0] === -1) { // entire list feedback - DOM.addClass(this.domNode, 'drop-target'); - DOM.addClass(this.rowsContainer, 'drop-target'); + this.domNode.classList.add('drop-target'); + this.rowsContainer.classList.add('drop-target'); this.currentDragFeedbackDisposable = toDisposable(() => { - DOM.removeClass(this.domNode, 'drop-target'); - DOM.removeClass(this.rowsContainer, 'drop-target'); + this.domNode.classList.remove('drop-target'); + this.rowsContainer.classList.remove('drop-target'); }); } else { for (const index of feedback) { @@ -1027,7 +1029,7 @@ export class ListView implements ISpliceable, IDisposable { item.dropTarget = true; if (item.row && item.row.domNode) { - DOM.addClass(item.row.domNode, 'drop-target'); + item.row.domNode.classList.add('drop-target'); } } @@ -1037,7 +1039,7 @@ export class ListView implements ISpliceable, IDisposable { item.dropTarget = false; if (item.row && item.row.domNode) { - DOM.removeClass(item.row.domNode, 'drop-target'); + item.row.domNode.classList.remove('drop-target'); } } }); @@ -1093,8 +1095,8 @@ export class ListView implements ISpliceable, IDisposable { private setupDragAndDropScrollTopAnimation(event: DragEvent): void { if (!this.dragOverAnimationDisposable) { - const viewTop = DOM.getTopLeftOffset(this.domNode).top; - this.dragOverAnimationDisposable = DOM.animate(this.animateDragAndDropScrollTop.bind(this, viewTop)); + const viewTop = getTopLeftOffset(this.domNode).top; + this.dragOverAnimationDisposable = animate(this.animateDragAndDropScrollTop.bind(this, viewTop)); } this.dragOverAnimationStopDisposable.dispose(); @@ -1166,7 +1168,7 @@ export class ListView implements ISpliceable, IDisposable { * Given a stable rendered state, checks every rendered element whether it needs * to be probed for dynamic height. Adjusts scroll height and top if necessary. */ - private _rerender(renderTop: number, renderHeight: number): void { + private _rerender(renderTop: number, renderHeight: number, updateScrollTop: boolean = true): void { const previousRenderRange = this.getRenderRange(renderTop, renderHeight); // Let's remember the second element's position, this helps in scrolling up @@ -1232,7 +1234,7 @@ export class ListView implements ISpliceable, IDisposable { } } - if (typeof anchorElementIndex === 'number') { + if (updateScrollTop && typeof anchorElementIndex === 'number') { this.scrollTop = this.elementTop(anchorElementIndex) - anchorElementTopDelta!; } diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index edd404f847..ea36ebd916 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -6,9 +6,8 @@ import 'vs/css!./list'; import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; import { isNumber } from 'vs/base/common/types'; -import { range, firstIndex, binarySearch } from 'vs/base/common/arrays'; +import { range, binarySearch } from 'vs/base/common/arrays'; import { memoize } from 'vs/base/common/decorators'; -import * as DOM from 'vs/base/browser/dom'; import * as platform from 'vs/base/common/platform'; import { Gesture } from 'vs/base/browser/touch'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -27,6 +26,7 @@ import { matchesPrefix } from 'vs/base/common/filters'; import { IDragAndDropData } from 'vs/base/browser/dnd'; import { alert } from 'vs/base/browser/ui/aria/aria'; import { IThemable } from 'vs/base/common/styler'; +import { createStyleSheet } from 'vs/base/browser/dom'; interface ITraitChangeEvent { indexes: number[]; @@ -55,7 +55,7 @@ class TraitRenderer implements IListRenderer } renderElement(element: T, index: number, templateData: ITraitTemplateData): void { - const renderedElementIndex = firstIndex(this.renderedElements, el => el.templateData === templateData); + const renderedElementIndex = this.renderedElements.findIndex(el => el.templateData === templateData); if (renderedElementIndex >= 0) { const rendered = this.renderedElements[renderedElementIndex]; @@ -96,7 +96,7 @@ class TraitRenderer implements IListRenderer } disposeTemplate(templateData: ITraitTemplateData): void { - const index = firstIndex(this.renderedElements, el => el.templateData === templateData); + const index = this.renderedElements.findIndex(el => el.templateData === templateData); if (index < 0) { return; @@ -137,11 +137,11 @@ class Trait implements ISpliceable, IDisposable { } renderIndex(index: number, container: HTMLElement): void { - DOM.toggleClass(container, this._trait, this.contains(index)); + container.classList.toggle(this._trait, this.contains(index)); } unrender(container: HTMLElement): void { - DOM.removeClass(container, this._trait); + container.classList.remove(this._trait); } /** @@ -230,11 +230,11 @@ export function isInputElement(e: HTMLElement): boolean { } export function isMonacoEditor(e: HTMLElement): boolean { - if (DOM.hasClass(e, 'monaco-editor')) { + if (e.classList.contains('monaco-editor')) { return true; } - if (DOM.hasClass(e, 'monaco-list')) { + if (e.classList.contains('monaco-list')) { return false; } @@ -824,10 +824,7 @@ export class DefaultStyleController implements IStyleController { content.push(`.monaco-list-type-filter { box-shadow: 1px 1px 1px ${styles.listMatchesShadow}; }`); } - const newStyles = content.join('\n'); - if (newStyles !== this.styleElement.innerHTML) { - this.styleElement.innerHTML = newStyles; - } + this.styleElement.textContent = content.join('\n'); } } @@ -1228,7 +1225,7 @@ export class List implements ISpliceable, IThemable, IDisposable { if (_options.styleController) { this.styleController = _options.styleController(this.view.domId); } else { - const styleElement = DOM.createStyleSheet(this.view.domNode); + const styleElement = createStyleSheet(this.view.domNode); this.styleController = new DefaultStyleController(styleElement, this.view.domId); } @@ -1636,7 +1633,7 @@ export class List implements ISpliceable, IThemable, IDisposable { private _onFocusChange(): void { const focus = this.focus.get(); - DOM.toggleClass(this.view.domNode, 'element-focused', focus.length > 0); + this.view.domNode.classList.toggle('element-focused', focus.length > 0); this.onDidChangeActiveDescendant(); } @@ -1659,9 +1656,9 @@ export class List implements ISpliceable, IThemable, IDisposable { private _onSelectionChange(): void { const selection = this.selection.get(); - DOM.toggleClass(this.view.domNode, 'selection-none', selection.length === 0); - DOM.toggleClass(this.view.domNode, 'selection-single', selection.length === 1); - DOM.toggleClass(this.view.domNode, 'selection-multiple', selection.length > 1); + this.view.domNode.classList.toggle('selection-none', selection.length === 0); + this.view.domNode.classList.toggle('selection-single', selection.length === 1); + this.view.domNode.classList.toggle('selection-multiple', selection.length > 1); } dispose(): void { diff --git a/src/vs/base/browser/ui/list/rowCache.ts b/src/vs/base/browser/ui/list/rowCache.ts index 0b4283cc74..b69cba6dfa 100644 --- a/src/vs/base/browser/ui/list/rowCache.ts +++ b/src/vs/base/browser/ui/list/rowCache.ts @@ -5,7 +5,7 @@ import { IListRenderer } from './list'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { $, removeClass } from 'vs/base/browser/dom'; +import { $ } from 'vs/base/browser/dom'; export interface IRow { domNode: HTMLElement | null; @@ -60,7 +60,7 @@ export class RowCache implements IDisposable { private releaseRow(row: IRow): void { const { domNode, templateId } = row; if (domNode) { - removeClass(domNode, 'scrolling'); + domNode.classList.remove('scrolling'); removeFromParent(domNode); } diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index cfacdec0f8..93460faa45 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -229,11 +229,11 @@ export class Menu extends ActionBar { private initializeStyleSheet(container: HTMLElement): void { if (isInShadowDOM(container)) { this.styleSheet = createStyleSheet(container); - this.styleSheet.innerHTML = MENU_WIDGET_CSS; + this.styleSheet.textContent = MENU_WIDGET_CSS; } else { if (!Menu.globalStyleSheet) { Menu.globalStyleSheet = createStyleSheet(); - Menu.globalStyleSheet.innerHTML = MENU_WIDGET_CSS; + Menu.globalStyleSheet.textContent = MENU_WIDGET_CSS; } this.styleSheet = Menu.globalStyleSheet; diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index 417434ef39..dae4ccdd10 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -532,25 +532,31 @@ export class MenuBar extends Disposable { // Update the button label to reflect mnemonics if (this.options.enableMnemonics) { - let innerHtml = strings.escape(label); + let cleanLabel = strings.escape(label); // This is global so reset it MENU_ESCAPED_MNEMONIC_REGEX.lastIndex = 0; - let escMatch = MENU_ESCAPED_MNEMONIC_REGEX.exec(innerHtml); + let escMatch = MENU_ESCAPED_MNEMONIC_REGEX.exec(cleanLabel); // We can't use negative lookbehind so we match our negative and skip while (escMatch && escMatch[1]) { - escMatch = MENU_ESCAPED_MNEMONIC_REGEX.exec(innerHtml); + escMatch = MENU_ESCAPED_MNEMONIC_REGEX.exec(cleanLabel); } + const replaceDoubleEscapes = (str: string) => str.replace(/&&/g, '&'); + if (escMatch) { - innerHtml = `${innerHtml.substr(0, escMatch.index)}${innerHtml.substr(escMatch.index + escMatch[0].length)}`; + titleElement.innerText = ''; + titleElement.append( + strings.ltrim(replaceDoubleEscapes(cleanLabel.substr(0, escMatch.index)), ' '), + $('mnemonic', { 'aria-hidden': 'true' }, escMatch[3]), + strings.rtrim(replaceDoubleEscapes(cleanLabel.substr(escMatch.index + escMatch[0].length)), ' ') + ); + } else { + titleElement.innerText = replaceDoubleEscapes(cleanLabel).trim(); } - - innerHtml = innerHtml.replace(/&&/g, '&'); - titleElement.innerHTML = innerHtml; } else { - titleElement.innerHTML = cleanMenuLabel.replace(/&&/g, '&'); + titleElement.innerText = cleanMenuLabel.replace(/&&/g, '&'); } let mnemonicMatches = MENU_MNEMONIC_REGEX.exec(label); diff --git a/src/vs/base/browser/ui/sash/sash.ts b/src/vs/base/browser/ui/sash/sash.ts index 853185876e..47c3b3451b 100644 --- a/src/vs/base/browser/ui/sash/sash.ts +++ b/src/vs/base/browser/ui/sash/sash.ts @@ -10,7 +10,7 @@ import * as types from 'vs/base/common/types'; import { EventType, GestureEvent, Gesture } from 'vs/base/browser/touch'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { Event, Emitter } from 'vs/base/common/event'; -import { getElementsByTagName, EventHelper, createStyleSheet, addDisposableListener, append, $, addClass, removeClass, toggleClass } from 'vs/base/browser/dom'; +import { getElementsByTagName, EventHelper, createStyleSheet, addDisposableListener, append, $ } from 'vs/base/browser/dom'; import { domEvent } from 'vs/base/browser/event'; const DEBUG = false; @@ -86,9 +86,9 @@ export class Sash extends Disposable { return; } - toggleClass(this.el, 'disabled', state === SashState.Disabled); - toggleClass(this.el, 'minimum', state === SashState.Minimum); - toggleClass(this.el, 'maximum', state === SashState.Maximum); + this.el.classList.toggle('disabled', state === SashState.Disabled); + this.el.classList.toggle('minimum', state === SashState.Minimum); + this.el.classList.toggle('maximum', state === SashState.Maximum); this._state = state; this._onDidEnablementChange.fire(state); @@ -151,7 +151,7 @@ export class Sash extends Disposable { this.el = append(container, $('.monaco-sash')); if (isMacintosh) { - addClass(this.el, 'mac'); + this.el.classList.add('mac'); } this._register(domEvent(this.el, 'mousedown')(this.onMouseDown, this)); @@ -185,14 +185,14 @@ export class Sash extends Disposable { this.orientation = options.orientation || Orientation.VERTICAL; if (this.orientation === Orientation.HORIZONTAL) { - addClass(this.el, 'horizontal'); - removeClass(this.el, 'vertical'); + this.el.classList.add('horizontal'); + this.el.classList.remove('vertical'); } else { - removeClass(this.el, 'horizontal'); - addClass(this.el, 'vertical'); + this.el.classList.remove('horizontal'); + this.el.classList.add('vertical'); } - toggleClass(this.el, 'debug', DEBUG); + this.el.classList.toggle('debug', DEBUG); this.layout(); } @@ -238,7 +238,7 @@ export class Sash extends Disposable { const altKey = mouseDownEvent.altKey; const startEvent: ISashEvent = { startX, currentX: startX, startY, currentY: startY, altKey }; - addClass(this.el, 'active'); + this.el.classList.add('active'); this._onDidStart.fire(startEvent); // fix https://github.com/Microsoft/vscode/issues/21675 @@ -266,7 +266,7 @@ export class Sash extends Disposable { } } - style.innerHTML = `* { cursor: ${cursor} !important; }`; + style.textContent = `* { cursor: ${cursor} !important; }`; }; const disposables = new DisposableStore(); @@ -290,7 +290,7 @@ export class Sash extends Disposable { this.el.removeChild(style); - removeClass(this.el, 'active'); + this.el.classList.remove('active'); this._onDidEnd.fire(); disposables.dispose(); @@ -396,11 +396,11 @@ export class Sash extends Disposable { } private onOrthogonalStartSashEnablementChange(state: SashState): void { - toggleClass(this.el, 'orthogonal-start', state !== SashState.Disabled); + this.el.classList.toggle('orthogonal-start', state !== SashState.Disabled); } private onOrthogonalEndSashEnablementChange(state: SashState): void { - toggleClass(this.el, 'orthogonal-end', state !== SashState.Disabled); + this.el.classList.toggle('orthogonal-end', state !== SashState.Disabled); } private getOrthogonalSash(e: MouseEvent): Sash | undefined { diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index 7d92e7310f..a77e0c3195 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -45,7 +45,7 @@ class SelectListRenderer implements IListRenderer .select-box-dropdown-list-container .monaco-list .monaco-list-row.option-disabled:hover { outline: none !important; }`); } - this.styleElement.innerHTML = content.join('\n'); + this.styleElement.textContent = content.join('\n'); this.applyStyles(); } @@ -451,8 +451,8 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi this.layoutSelectDropDown(); }, onHide: () => { - dom.toggleClass(this.selectDropDownContainer, 'visible', false); - dom.toggleClass(this.selectElement, 'synthetic-focus', false); + this.selectDropDownContainer.classList.remove('visible'); + this.selectElement.classList.remove('synthetic-focus'); }, anchorPosition: this._dropDownPosition }, this.selectBoxOptions.optionsAsChildren ? this.container : undefined); @@ -466,8 +466,8 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi render: (container: HTMLElement) => this.renderSelectDropDown(container), layout: () => this.layoutSelectDropDown(), onHide: () => { - dom.toggleClass(this.selectDropDownContainer, 'visible', false); - dom.toggleClass(this.selectElement, 'synthetic-focus', false); + this.selectDropDownContainer.classList.remove('visible'); + this.selectElement.classList.remove('synthetic-focus'); }, anchorPosition: this._dropDownPosition }, this.selectBoxOptions.optionsAsChildren ? this.container : undefined); @@ -514,42 +514,15 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi // Iterate over detailed descriptions, find max height private measureMaxDetailsHeight(): number { - let maxDetailsPaneHeight = 0; - this.options.forEach((option, index) => { - - this.selectionDetailsPane.innerText = ''; - - if (option.description) { - if (option.descriptionIsMarkdown) { - this.selectionDetailsPane.appendChild(this.renderDescriptionMarkdown(option.description)); - } else { - this.selectionDetailsPane.innerText = option.description; - } - this.selectionDetailsPane.style.display = 'block'; - } else { - this.selectionDetailsPane.style.display = 'none'; - } + this.options.forEach((_option, index) => { + this.updateDetail(index); if (this.selectionDetailsPane.offsetHeight > maxDetailsPaneHeight) { maxDetailsPaneHeight = this.selectionDetailsPane.offsetHeight; } }); - // Reset description to selected - - this.selectionDetailsPane.innerText = ''; - const description = this.options[this.selected].description || null; - const descriptionIsMarkdown = this.options[this.selected].descriptionIsMarkdown || null; - - if (description) { - if (descriptionIsMarkdown) { - this.selectionDetailsPane.appendChild(this.renderDescriptionMarkdown(description)); - } else { - this.selectionDetailsPane.innerText = description; - } - this.selectionDetailsPane.style.display = 'block'; - } return maxDetailsPaneHeight; } @@ -567,7 +540,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi if (this.selectList) { // Make visible to enable measurements - dom.toggleClass(this.selectDropDownContainer, 'visible', true); + this.selectDropDownContainer.classList.add('visible'); const selectPosition = dom.getDomNodePagePosition(this.selectElement); const styles = getComputedStyle(this.selectElement); @@ -622,8 +595,8 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi this.selectDropDownContainer.appendChild(this.selectionDetailsPane); this.selectDropDownContainer.appendChild(this.selectDropDownListContainer); - dom.removeClass(this.selectionDetailsPane, 'border-top'); - dom.addClass(this.selectionDetailsPane, 'border-bottom'); + this.selectionDetailsPane.classList.remove('border-top'); + this.selectionDetailsPane.classList.add('border-bottom'); } else { this._dropDownPosition = AnchorPosition.BELOW; @@ -632,8 +605,8 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi this.selectDropDownContainer.appendChild(this.selectDropDownListContainer); this.selectDropDownContainer.appendChild(this.selectionDetailsPane); - dom.removeClass(this.selectionDetailsPane, 'border-bottom'); - dom.addClass(this.selectionDetailsPane, 'border-top'); + this.selectionDetailsPane.classList.remove('border-bottom'); + this.selectionDetailsPane.classList.add('border-top'); } // Do full layout on showSelectDropDown only return true; @@ -687,12 +660,14 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi this.selectDropDownContainer.style.height = (listHeight + verticalPadding) + 'px'; } + this.updateDetail(this.selected); + this.selectDropDownContainer.style.width = selectOptimalWidth; // Maintain focus outline on parent select as well as list container - tabindex for focus this.selectDropDownListContainer.setAttribute('tabindex', '0'); - dom.toggleClass(this.selectElement, 'synthetic-focus', true); - dom.toggleClass(this.selectDropDownContainer, 'synthetic-focus', true); + this.selectElement.classList.add('synthetic-focus'); + this.selectDropDownContainer.classList.add('synthetic-focus'); return true; } else { @@ -717,7 +692,6 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi container.innerHTML = this.options[longest]?.text + (!!this.options[longest]?.decoratorRight ? (this.options[longest].decoratorRight + ' ') : ''); // {{ SQL CARBON EDIT }} Don't error if no option found (empty list) - elementWidth = dom.getTotalWidth(container); } @@ -883,8 +857,11 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi return; } + this.updateDetail(e.indexes[0]); + } + + private updateDetail(selectedIndex: number): void { this.selectionDetailsPane.innerText = ''; - const selectedIndex = e.indexes[0]; const description = this.options[selectedIndex].description; const descriptionIsMarkdown = this.options[selectedIndex].descriptionIsMarkdown; diff --git a/src/vs/base/browser/ui/selectBox/selectBoxNative.ts b/src/vs/base/browser/ui/selectBox/selectBoxNative.ts index 02853ba2f2..350353c556 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxNative.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxNative.ts @@ -144,7 +144,7 @@ export class SelectBoxNative extends Disposable implements ISelectBoxDelegate { } public render(container: HTMLElement): void { - dom.addClass(container, 'select-container'); + container.classList.add('select-container'); container.appendChild(this.selectElement); this.setOptions(this.options, this.selected); this.applyStyles(); diff --git a/src/vs/base/browser/ui/splitview/paneview.ts b/src/vs/base/browser/ui/splitview/paneview.ts index 4df8c5bcd1..87b210da36 100644 --- a/src/vs/base/browser/ui/splitview/paneview.ts +++ b/src/vs/base/browser/ui/splitview/paneview.ts @@ -9,8 +9,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { $, append, addClass, removeClass, toggleClass, trackFocus, EventHelper, clearNode } from 'vs/base/browser/dom'; -import { firstIndex } from 'vs/base/common/arrays'; +import { $, append, trackFocus, EventHelper, clearNode } from 'vs/base/browser/dom'; import { Color, RGBA } from 'vs/base/common/color'; import { SplitView, IView } from './splitview'; import { isFirefox } from 'vs/base/browser/browser'; @@ -143,7 +142,7 @@ export abstract class Pane extends Disposable implements IView { } if (this.element) { - toggleClass(this.element, 'expanded', expanded); + this.element.classList.toggle('expanded', expanded); } this._expanded = !!expanded; @@ -191,8 +190,8 @@ export abstract class Pane extends Disposable implements IView { this._orientation = orientation; if (this.element) { - toggleClass(this.element, 'horizontal', this.orientation === Orientation.HORIZONTAL); - toggleClass(this.element, 'vertical', this.orientation === Orientation.VERTICAL); + this.element.classList.toggle('horizontal', this.orientation === Orientation.HORIZONTAL); + this.element.classList.toggle('vertical', this.orientation === Orientation.VERTICAL); } if (this.header) { @@ -201,9 +200,9 @@ export abstract class Pane extends Disposable implements IView { } render(): void { - toggleClass(this.element, 'expanded', this.isExpanded()); - toggleClass(this.element, 'horizontal', this.orientation === Orientation.HORIZONTAL); - toggleClass(this.element, 'vertical', this.orientation === Orientation.VERTICAL); + this.element.classList.toggle('expanded', this.isExpanded()); + this.element.classList.toggle('horizontal', this.orientation === Orientation.HORIZONTAL); + this.element.classList.toggle('vertical', this.orientation === Orientation.VERTICAL); this.header = $('.pane-header'); append(this.element, this.header); @@ -215,8 +214,8 @@ export abstract class Pane extends Disposable implements IView { const focusTracker = trackFocus(this.header); this._register(focusTracker); - this._register(focusTracker.onDidFocus(() => addClass(this.header, 'focused'), null)); - this._register(focusTracker.onDidBlur(() => removeClass(this.header, 'focused'), null)); + this._register(focusTracker.onDidFocus(() => this.header.classList.add('focused'), null)); + this._register(focusTracker.onDidBlur(() => this.header.classList.remove('focused'), null)); this.updateHeader(); @@ -255,7 +254,7 @@ export abstract class Pane extends Disposable implements IView { const height = this._orientation === Orientation.VERTICAL ? size - headerSize : this.orthogonalSize - headerSize; if (this.isExpanded()) { - toggleClass(this.body, 'wide', width >= 600); + this.body.classList.toggle('wide', width >= 600); this.layoutBody(height, width); this.expandedSize = size; } @@ -275,8 +274,8 @@ export abstract class Pane extends Disposable implements IView { const expanded = !this.headerVisible || this.isExpanded(); this.header.style.lineHeight = `${this.headerSize}px`; - toggleClass(this.header, 'hidden', !this.headerVisible); - toggleClass(this.header, 'expanded', expanded); + this.header.classList.toggle('hidden', !this.headerVisible); + this.header.classList.toggle('expanded', expanded); this.header.setAttribute('aria-expanded', String(expanded)); this.header.style.color = this.styles.headerForeground ? this.styles.headerForeground.toString() : ''; @@ -474,7 +473,7 @@ export class PaneView extends Disposable { } removePane(pane: Pane): void { - const index = firstIndex(this.paneItems, item => item.pane === pane); + const index = this.paneItems.findIndex(item => item.pane === pane); if (index === -1) { return; @@ -486,8 +485,8 @@ export class PaneView extends Disposable { } movePane(from: Pane, to: Pane): void { - const fromIndex = firstIndex(this.paneItems, item => item.pane === from); - const toIndex = firstIndex(this.paneItems, item => item.pane === to); + const fromIndex = this.paneItems.findIndex(item => item.pane === from); + const toIndex = this.paneItems.findIndex(item => item.pane === to); if (fromIndex === -1 || toIndex === -1) { return; @@ -500,7 +499,7 @@ export class PaneView extends Disposable { } resizePane(pane: Pane, size: number): void { - const index = firstIndex(this.paneItems, item => item.pane === pane); + const index = this.paneItems.findIndex(item => item.pane === pane); if (index === -1) { return; @@ -510,7 +509,7 @@ export class PaneView extends Disposable { } getPaneSize(pane: Pane): number { - const index = firstIndex(this.paneItems, item => item.pane === pane); + const index = this.paneItems.findIndex(item => item.pane === pane); if (index === -1) { return -1; @@ -561,11 +560,11 @@ export class PaneView extends Disposable { window.clearTimeout(this.animationTimer); } - addClass(this.el, 'animated'); + this.el.classList.add('animated'); this.animationTimer = window.setTimeout(() => { this.animationTimer = undefined; - removeClass(this.el, 'animated'); + this.el.classList.remove('animated'); }, 200); } diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index 33d90e3490..d55c5a6dcf 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -7,12 +7,12 @@ import 'vs/css!./splitview'; import { IDisposable, toDisposable, Disposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import * as types from 'vs/base/common/types'; -import * as dom from 'vs/base/browser/dom'; import { clamp } from 'vs/base/common/numbers'; -import { range, firstIndex, pushToStart, pushToEnd } from 'vs/base/common/arrays'; +import { range, pushToStart, pushToEnd } from 'vs/base/common/arrays'; import { Sash, Orientation, ISashEvent as IBaseSashEvent, SashState } from 'vs/base/browser/ui/sash/sash'; import { Color } from 'vs/base/common/color'; import { domEvent } from 'vs/base/browser/event'; +import { $, append } from 'vs/base/browser/dom'; export { Orientation } from 'vs/base/browser/ui/sash/sash'; export interface ISplitViewStyles { @@ -93,7 +93,7 @@ abstract class ViewItem { this.size = 0; } - dom.toggleClass(this.container, 'visible', visible); + this.container.classList.toggle('visible', visible); if (this.view.setVisible) { this.view.setVisible(visible); @@ -122,7 +122,7 @@ abstract class ViewItem { if (typeof size === 'number') { this._size = size; this._cachedVisibleSize = undefined; - dom.addClass(container, 'visible'); + container.classList.add('visible'); } else { this._size = 0; this._cachedVisibleSize = size.cachedVisibleSize; @@ -296,12 +296,12 @@ export class SplitView extends Disposable { this.proportionalLayout = types.isUndefined(options.proportionalLayout) ? true : !!options.proportionalLayout; this.el = document.createElement('div'); - dom.addClass(this.el, 'monaco-split-view2'); - dom.addClass(this.el, this.orientation === Orientation.VERTICAL ? 'vertical' : 'horizontal'); + this.el.classList.add('monaco-split-view2'); + this.el.classList.add(this.orientation === Orientation.VERTICAL ? 'vertical' : 'horizontal'); container.appendChild(this.el); - this.sashContainer = dom.append(this.el, dom.$('.sash-container')); - this.viewContainer = dom.append(this.el, dom.$('.split-view-container')); + this.sashContainer = append(this.el, $('.sash-container')); + this.viewContainer = append(this.el, $('.split-view-container')); this.style(options.styles || defaultStyles); @@ -323,10 +323,10 @@ export class SplitView extends Disposable { style(styles: ISplitViewStyles): void { if (styles.separatorBorder.isTransparent()) { - dom.removeClass(this.el, 'separator-border'); + this.el.classList.remove('separator-border'); this.el.style.removeProperty('--separator-border'); } else { - dom.addClass(this.el, 'separator-border'); + this.el.classList.add('separator-border'); this.el.style.setProperty('--separator-border', styles.separatorBorder.toString()); } } @@ -460,7 +460,7 @@ export class SplitView extends Disposable { item.enabled = false; } - const index = firstIndex(this.sashItems, item => item.sash === sash); + const index = this.sashItems.findIndex(item => item.sash === sash); // This way, we can press Alt while we resize a sash, macOS style! const disposable = combinedDisposable( @@ -657,7 +657,7 @@ export class SplitView extends Disposable { this.state = State.Busy; // Add view - const container = dom.$('.split-view-view'); + const container = $('.split-view-view'); if (index === this.viewItems.length) { this.viewContainer.appendChild(container); @@ -709,11 +709,11 @@ export class SplitView extends Disposable { const onStartDisposable = onStart(this.onSashStart, this); const onChange = Event.map(sash.onDidChange, sashEventMapper); const onChangeDisposable = onChange(this.onSashChange, this); - const onEnd = Event.map(sash.onDidEnd, () => firstIndex(this.sashItems, item => item.sash === sash)); + const onEnd = Event.map(sash.onDidEnd, () => this.sashItems.findIndex(item => item.sash === sash)); const onEndDisposable = onEnd(this.onSashEnd, this); const onDidResetDisposable = sash.onDidReset(() => { - const index = firstIndex(this.sashItems, item => item.sash === sash); + const index = this.sashItems.findIndex(item => item.sash === sash); const upIndexes = range(index, -1); const downIndexes = range(index + 1, this.viewItems.length); const snapBeforeIndex = this.findFirstSnapIndex(upIndexes); diff --git a/src/vs/base/browser/ui/toolbar/toolbar.ts b/src/vs/base/browser/ui/toolbar/toolbar.ts index 0ee927ee44..50494b8627 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.ts +++ b/src/vs/base/browser/ui/toolbar/toolbar.ts @@ -72,7 +72,7 @@ export class ToolBar extends Disposable { actionViewItemProvider: this.options.actionViewItemProvider, actionRunner: this.actionRunner, keybindingProvider: this.options.getKeyBinding, - classNames: toolBarMoreIcon.classNames, + classNames: toolBarMoreIcon.classNamesArray, anchorAlignmentProvider: this.options.anchorAlignmentProvider, menuAsChild: !!this.options.renderDropdownAsChildElement } diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index f1aeb4809b..24a3232291 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/tree'; import { IDisposable, dispose, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IListOptions, List, IListStyles, MouseController, DefaultKeyboardNavigationDelegate, isInputElement, isMonacoEditor } from 'vs/base/browser/ui/list/listWidget'; import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IKeyboardNavigationLabelProvider, IIdentityProvider, IKeyboardNavigationDelegate } from 'vs/base/browser/ui/list/list'; -import { append, $, toggleClass, getDomNodePagePosition, removeClass, addClass, hasClass, hasParentWithClass, createStyleSheet, clearNode, addClasses, removeClasses } from 'vs/base/browser/dom'; +import { append, $, getDomNodePagePosition, hasParentWithClass, createStyleSheet, clearNode } from 'vs/base/browser/dom'; import { Event, Relay, Emitter, EventBufferer } from 'vs/base/common/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -405,10 +405,10 @@ class TreeRenderer implements IListRenderer } if (node.collapsible && (!this.hideTwistiesOfChildlessElements || node.visibleChildrenCount > 0)) { - addClasses(templateData.twistie, treeItemExpandedIcon.classNames, 'collapsible'); - toggleClass(templateData.twistie, 'collapsed', node.collapsed); + templateData.twistie.classList.add(...treeItemExpandedIcon.classNamesArray, 'collapsible'); + templateData.twistie.classList.toggle('collapsed', node.collapsed); } else { - removeClasses(templateData.twistie, treeItemExpandedIcon.classNames, 'collapsible', 'collapsed'); + templateData.twistie.classList.remove(...treeItemExpandedIcon.classNamesArray, 'collapsible', 'collapsed'); } if (node.collapsible) { @@ -443,7 +443,7 @@ class TreeRenderer implements IListRenderer const guide = $('.indent-guide', { style: `width: ${this.indent}px` }); if (this.activeIndentNodes.has(parent)) { - addClass(guide, 'active'); + guide.classList.add('active'); } if (templateData.indent.childElementCount === 0) { @@ -486,13 +486,13 @@ class TreeRenderer implements IListRenderer this.activeIndentNodes.forEach(node => { if (!set.has(node)) { - this.renderedIndentGuides.forEach(node, line => removeClass(line, 'active')); + this.renderedIndentGuides.forEach(node, line => line.classList.remove('active')); } }); set.forEach(node => { if (!this.activeIndentNodes.has(node)) { - this.renderedIndentGuides.forEach(node, line => addClass(line, 'active')); + this.renderedIndentGuides.forEach(node, line => line.classList.add('active')); } }); @@ -833,10 +833,10 @@ class TypeFilterController implements IDisposable { }; updatePosition(); - removeClass(this.domNode, positionClassName); + this.domNode.classList.remove(positionClassName); - addClass(this.domNode, 'dragging'); - disposables.add(toDisposable(() => removeClass(this.domNode, 'dragging'))); + this.domNode.classList.add('dragging'); + disposables.add(toDisposable(() => this.domNode.classList.remove('dragging'))); domEvent(document, 'dragover')(onDragOver, null, disposables); domEvent(this.domNode, 'dragend')(onDragEnd, null, disposables); @@ -864,12 +864,12 @@ class TypeFilterController implements IDisposable { private updateFilterOnTypeTitleAndIcon(): void { if (this.filterOnType) { - removeClasses(this.filterOnTypeDomNode, treeFilterOnTypeOffIcon.classNames); - addClasses(this.filterOnTypeDomNode, treeFilterOnTypeOnIcon.classNames); + this.filterOnTypeDomNode.classList.remove(...treeFilterOnTypeOffIcon.classNamesArray); + this.filterOnTypeDomNode.classList.add(...treeFilterOnTypeOnIcon.classNamesArray); this.filterOnTypeDomNode.title = localize('disable filter on type', "Disable Filter on Type"); } else { - removeClasses(this.filterOnTypeDomNode, treeFilterOnTypeOnIcon.classNames); - addClasses(this.filterOnTypeDomNode, treeFilterOnTypeOffIcon.classNames); + this.filterOnTypeDomNode.classList.remove(...treeFilterOnTypeOnIcon.classNamesArray); + this.filterOnTypeDomNode.classList.add(...treeFilterOnTypeOffIcon.classNamesArray); this.filterOnTypeDomNode.title = localize('enable filter on type', "Enable Filter on Type"); } } @@ -885,7 +885,7 @@ class TypeFilterController implements IDisposable { this._empty = false; } - toggleClass(this.domNode, 'no-matches', noMatches); + this.domNode.classList.toggle('no-matches', noMatches); this.domNode.title = localize('found', "Matched {0} out of {1} elements", this.filter.matchCount, this.filter.totalCount); this.labelDomNode.textContent = this.pattern.length > 16 ? '…' + this.pattern.substr(this.pattern.length - 16) : this.pattern; @@ -1096,8 +1096,8 @@ class TreeNodeListMouseController extends MouseController< } const target = e.browserEvent.target as HTMLElement; - const onTwistie = hasClass(target, 'monaco-tl-twistie') - || (hasClass(target, 'monaco-icon-label') && hasClass(target, 'folder-icon') && e.browserEvent.offsetX < 16); + const onTwistie = target.classList.contains('monaco-tl-twistie') + || (target.classList.contains('monaco-icon-label') && target.classList.contains('folder-icon') && e.browserEvent.offsetX < 16); let expandOnlyOnTwistieClick = false; @@ -1130,7 +1130,7 @@ class TreeNodeListMouseController extends MouseController< } protected onDoubleClick(e: IListMouseEvent>): void { - const onTwistie = hasClass(e.browserEvent.target as HTMLElement, 'monaco-tl-twistie'); + const onTwistie = (e.browserEvent.target as HTMLElement).classList.contains('monaco-tl-twistie'); if (onTwistie) { return; @@ -1340,7 +1340,7 @@ export abstract class AbstractTree implements IDisposable } this.styleElement = createStyleSheet(this.view.getHTMLElement()); - toggleClass(this.getHTMLElement(), 'always', this._options.renderIndentGuides === RenderIndentGuides.Always); + this.getHTMLElement().classList.toggle('always', this._options.renderIndentGuides === RenderIndentGuides.Always); } updateOptions(optionsUpdate: IAbstractTreeOptionsUpdate = {}): void { @@ -1363,7 +1363,7 @@ export abstract class AbstractTree implements IDisposable this._onDidUpdateOptions.fire(this._options); - toggleClass(this.getHTMLElement(), 'always', this._options.renderIndentGuides === RenderIndentGuides.Always); + this.getHTMLElement().classList.toggle('always', this._options.renderIndentGuides === RenderIndentGuides.Always); } get options(): IAbstractTreeOptions { @@ -1474,11 +1474,7 @@ export abstract class AbstractTree implements IDisposable content.push(`.monaco-list${suffix} .monaco-tl-indent > .indent-guide.active { border-color: ${styles.treeIndentGuidesStroke}; }`); } - const newStyles = content.join('\n'); - if (newStyles !== this.styleElement.innerHTML) { - this.styleElement.innerHTML = newStyles; - } - + this.styleElement.textContent = content.join('\n'); this.view.style(styles); } diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 1c1512bac4..8db25c427a 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -15,7 +15,6 @@ import { Iterable } from 'vs/base/common/iterator'; import { IDragAndDropData } from 'vs/base/browser/dnd'; import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { isPromiseCanceledError, onUnexpectedError } from 'vs/base/common/errors'; -import { removeClasses, addClasses } from 'vs/base/browser/dom'; import { ScrollEvent } from 'vs/base/common/scrollable'; import { ICompressedTreeNode, ICompressedTreeElement } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; import { IThemable } from 'vs/base/common/styler'; @@ -110,9 +109,9 @@ class AsyncDataTreeRenderer implements IT renderTwistie(element: IAsyncDataTreeNode, twistieElement: HTMLElement): boolean { if (element.slow) { - addClasses(twistieElement, treeItemLoadingIcon.classNames); + twistieElement.classList.add(...treeItemLoadingIcon.classNamesArray); } else { - removeClasses(twistieElement, treeItemLoadingIcon.classNames); + twistieElement.classList.remove(...treeItemLoadingIcon.classNamesArray); } return false; } @@ -1053,9 +1052,9 @@ class CompressibleAsyncDataTreeRenderer i renderTwistie(element: IAsyncDataTreeNode, twistieElement: HTMLElement): boolean { if (element.slow) { - addClasses(twistieElement, treeItemLoadingIcon.classNames); + twistieElement.classList.add(...treeItemLoadingIcon.classNamesArray); } else { - removeClasses(twistieElement, treeItemLoadingIcon.classNames); + twistieElement.classList.remove(...treeItemLoadingIcon.classNamesArray); } return false; } diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index b987fec219..4e01498dd6 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -399,29 +399,13 @@ export function lastIndex(array: ReadonlyArray, fn: (item: T) => boolean): return -1; } -/** - * @deprecated ES6: use `Array.findIndex` - */ -export function firstIndex(array: ReadonlyArray, fn: (item: T) => boolean): number { - for (let i = 0; i < array.length; i++) { - const element = array[i]; - - if (fn(element)) { - return i; - } - } - - return -1; -} - - /** * @deprecated ES6: use `Array.find` */ export function first(array: ReadonlyArray, fn: (item: T) => boolean, notFoundValue: T): T; export function first(array: ReadonlyArray, fn: (item: T) => boolean): T | undefined; export function first(array: ReadonlyArray, fn: (item: T) => boolean, notFoundValue: T | undefined = undefined): T | undefined { - const index = firstIndex(array, fn); + const index = array.findIndex(fn); return index < 0 ? notFoundValue : array[index]; } @@ -565,21 +549,6 @@ export function pushToEnd(arr: T[], value: T): void { } } - -/** - * @deprecated ES6: use `Array.find` - */ -export function find(arr: ArrayLike, predicate: (value: T, index: number, arr: ArrayLike) => any): T | undefined { - for (let i = 0; i < arr.length; i++) { - const element = arr[i]; - if (predicate(element, i, arr)) { - return element; - } - } - - return undefined; -} - export function mapArrayOrNot(items: T | T[], fn: (_: T) => U): U | U[] { return Array.isArray(items) ? items.map(fn) : diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index 96e8cf8844..25640560ab 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -52,21 +52,21 @@ export function createCancelablePromise(callback: (token: CancellationToken) export function raceCancellation(promise: Promise, token: CancellationToken): Promise; export function raceCancellation(promise: Promise, token: CancellationToken, defaultValue: T): Promise; -export function raceCancellation(promise: Promise, token: CancellationToken, defaultValue?: T): Promise { - return Promise.race([promise, new Promise(resolve => token.onCancellationRequested(() => resolve(defaultValue)))]); +export function raceCancellation(promise: Promise, token: CancellationToken, defaultValue?: T): Promise { + return Promise.race([promise, new Promise(resolve => token.onCancellationRequested(() => resolve(defaultValue)))]); } -export function raceTimeout(promise: Promise, timeout: number, onTimeout?: () => void): Promise { - let promiseResolve: (() => void) | undefined = undefined; +export function raceTimeout(promise: Promise, timeout: number, onTimeout?: () => void): Promise { + let promiseResolve: ((value: T | undefined) => void) | undefined = undefined; const timer = setTimeout(() => { - promiseResolve?.(); + promiseResolve?.(undefined); onTimeout?.(); }, timeout); return Promise.race([ promise.finally(() => clearTimeout(timer)), - new Promise(resolve => promiseResolve = resolve) + new Promise(resolve => promiseResolve = resolve) ]); } @@ -432,7 +432,7 @@ export function first(promiseFactories: ITask>[], shouldStop: (t: interface ILimitedTaskFactory { factory: ITask>; - c: (value?: T | Promise) => void; + c: (value: T | Promise) => void; e: (error?: any) => void; } @@ -618,10 +618,10 @@ export class RunOnceScheduler { private timeout: number; private timeoutHandler: () => void; - constructor(runner: (...args: any[]) => void, timeout: number) { + constructor(runner: (...args: any[]) => void, delay: number) { this.timeoutToken = -1; this.runner = runner; - this.timeout = timeout; + this.timeout = delay; this.timeoutHandler = this.onTimeout.bind(this); } @@ -651,6 +651,14 @@ export class RunOnceScheduler { this.timeoutToken = setTimeout(this.timeoutHandler, delay); } + get delay(): number { + return this.timeout; + } + + set delay(value: number) { + this.timeout = value; + } + /** * Returns true if scheduled. */ diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts index 1c793daa9b..4df9a9177b 100644 --- a/src/vs/base/common/codicons.ts +++ b/src/vs/base/common/codicons.ts @@ -478,6 +478,9 @@ export namespace Codicon { export const vmConnect = new Codicon('vm-connect', { character: '\\eba9' }); export const cloud = new Codicon('cloud', { character: '\\ebaa' }); export const merge = new Codicon('merge', { character: '\\ebab' }); + export const exportIcon = new Codicon('export', { character: '\\ebac' }); + export const graphLeft = new Codicon('graph-left', { character: '\\ebad' }); + export const magnet = new Codicon('magnet', { character: '\\ebae' }); } @@ -499,20 +502,6 @@ export function markdownUnescapeCodicons(text: string): string { return text.replace(markdownUnescapeCodiconsRegex, (match, escaped, codicon) => escaped ? match : `$(${codicon})`); } -export const renderCodiconsRegex = /(\\)?\$\((([a-z0-9\-]+?)(?:~([a-z0-9\-]*?))?)\)/gi; - -/** - * @deprecated Use `renderCodiconsAsElement` instead - */ -export function renderCodicons(text: string): string { - return text.replace(renderCodiconsRegex, (_, escaped, codicon, name, animation) => { - // If the class for codicons is changed, it should also be updated in src\vs\base\browser\markdownRenderer.ts - return escaped - ? `$(${codicon})` - : ``; - }); -} - const stripCodiconsRegex = /(\s)?(\\)?\$\([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?\)(\s)?/gi; export function stripCodicons(text: string): string { if (text.indexOf(codiconStartMarker) === -1) { diff --git a/src/vs/base/common/objects.ts b/src/vs/base/common/objects.ts index 92865c673d..9f5aaf360f 100644 --- a/src/vs/base/common/objects.ts +++ b/src/vs/base/common/objects.ts @@ -179,18 +179,18 @@ export function equals(one: any, other: any): boolean { } /** - * Calls JSON.Stringify with a replacer to break apart any circular references. - * This prevents JSON.stringify from throwing the exception + * Calls `JSON.Stringify` with a replacer to break apart any circular references. + * This prevents `JSON`.stringify` from throwing the exception * "Uncaught TypeError: Converting circular structure to JSON" */ export function safeStringify(obj: any): string { - const seen: any[] = []; + const seen = new Set(); return JSON.stringify(obj, (key, value) => { if (isObject(value) || Array.isArray(value)) { - if (seen.indexOf(value) !== -1) { + if (seen.has(value)) { return '[Circular]'; } else { - seen.push(value); + seen.add(value); } } return value; diff --git a/src/vs/base/common/resources.ts b/src/vs/base/common/resources.ts index 90a07f2e10..aaa1abc07a 100644 --- a/src/vs/base/common/resources.ts +++ b/src/vs/base/common/resources.ts @@ -454,15 +454,15 @@ export class ResourceGlobMatcher { } } -export function toLocalResource(resource: URI, authority: string | undefined): URI { +export function toLocalResource(resource: URI, authority: string | undefined, localScheme: string): URI { if (authority) { let path = resource.path; if (path && path[0] !== paths.posix.sep) { path = paths.posix.sep + path; } - return resource.with({ scheme: Schemas.vscodeRemote, authority, path }); + return resource.with({ scheme: localScheme, authority, path }); } - return resource.with({ scheme: Schemas.file }); + return resource.with({ scheme: localScheme }); } diff --git a/src/vs/base/common/skipList.ts b/src/vs/base/common/skipList.ts index b72cb49ec3..11cc8e3790 100644 --- a/src/vs/base/common/skipList.ts +++ b/src/vs/base/common/skipList.ts @@ -22,7 +22,7 @@ export class SkipList implements Map { readonly [Symbol.toStringTag] = 'SkipList'; private _maxLevel: number; - private _level: number = 1; + private _level: number = 0; private _header: Node; private _size: number = 0; @@ -122,7 +122,7 @@ export class SkipList implements Map { private static _search(list: SkipList, searchKey: K, comparator: Comparator) { let x = list._header; - for (let i = list._level; i >= 0; i--) { + for (let i = list._level - 1; i >= 0; i--) { while (x.forward[i] && comparator(x.forward[i].key, searchKey) < 0) { x = x.forward[i]; } @@ -137,7 +137,7 @@ export class SkipList implements Map { private static _insert(list: SkipList, searchKey: K, value: V, comparator: Comparator) { let update: Node[] = []; let x = list._header; - for (let i = list._level; i >= 0; i--) { + for (let i = list._level - 1; i >= 0; i--) { while (x.forward[i] && comparator(x.forward[i].key, searchKey) < 0) { x = x.forward[i]; } @@ -152,13 +152,13 @@ export class SkipList implements Map { // insert let lvl = SkipList._randomLevel(list); if (lvl > list._level) { - for (let i = list._level + 1; i <= lvl; i++) { + for (let i = list._level; i < lvl; i++) { update[i] = list._header; } list._level = lvl; } x = new Node(lvl, searchKey, value); - for (let i = 0; i <= lvl; i++) { + for (let i = 0; i < lvl; i++) { x.forward[i] = update[i].forward[i]; update[i].forward[i] = x; } @@ -177,7 +177,7 @@ export class SkipList implements Map { private static _delete(list: SkipList, searchKey: K, comparator: Comparator) { let update: Node[] = []; let x = list._header; - for (let i = list._level; i >= 0; i--) { + for (let i = list._level - 1; i >= 0; i--) { while (x.forward[i] && comparator(x.forward[i].key, searchKey) < 0) { x = x.forward[i]; } @@ -194,7 +194,7 @@ export class SkipList implements Map { } update[i].forward[i] = x.forward[i]; } - while (list._level >= 1 && list._header.forward[list._level] === NIL) { + while (list._level > 0 && list._header.forward[list._level - 1] === NIL) { list._level -= 1; } return true; diff --git a/src/vs/base/node/processes.ts b/src/vs/base/node/processes.ts index 16e3721f0c..97ffd601ff 100644 --- a/src/vs/base/node/processes.ts +++ b/src/vs/base/node/processes.ts @@ -18,7 +18,7 @@ import { CommandOptions, ForkOptions, SuccessData, Source, TerminateResponse, Te import { getPathFromAmdModule } from 'vs/base/common/amd'; export { CommandOptions, ForkOptions, SuccessData, Source, TerminateResponse, TerminateResponseCode }; -export type ValueCallback = (value?: T | Promise) => void; +export type ValueCallback = (value: T | Promise) => void; export type ErrorCallback = (error?: any) => void; export type ProgressCallback = (progress: T) => void; @@ -98,7 +98,7 @@ export abstract class AbstractProcess { private childProcess: cp.ChildProcess | null; protected childProcessPromise: Promise | null; - private pidResolve?: ValueCallback; + private pidResolve: ValueCallback | undefined; protected terminateRequested: boolean; private static WellKnowCommands: IStringDictionary = { diff --git a/src/vs/base/node/ps.ts b/src/vs/base/node/ps.ts index 2dda40bf3d..cb16ea0337 100644 --- a/src/vs/base/node/ps.ts +++ b/src/vs/base/node/ps.ts @@ -193,6 +193,11 @@ export function listProcesses(rootPid: number): Promise { processInfo.load = parseFloat(cpuUsage[i]); } + if (!rootItem) { + reject(new Error(`Root process ${rootPid} not found`)); + return; + } + resolve(rootItem); } }); @@ -228,7 +233,11 @@ export function listProcesses(rootPid: number): Promise { if (process.platform === 'linux') { calculateLinuxCpuUsage(); } else { - resolve(rootItem); + if (!rootItem) { + reject(new Error(`Root process ${rootPid} not found`)); + } else { + resolve(rootItem); + } } } }); diff --git a/src/vs/base/node/terminalEncoding.ts b/src/vs/base/node/terminalEncoding.ts index 983ddf78f2..3d42b24e2f 100644 --- a/src/vs/base/node/terminalEncoding.ts +++ b/src/vs/base/node/terminalEncoding.ts @@ -40,7 +40,7 @@ const JSCHARDET_TO_ICONV_ENCODINGS: { [name: string]: string } = { const UTF8 = 'utf8'; export async function resolveTerminalEncoding(verbose?: boolean): Promise { - let rawEncodingPromise: Promise; + let rawEncodingPromise: Promise; // Support a global environment variable to win over other mechanics const cliEncodingEnv = process.env['VSCODE_CLI_ENCODING']; @@ -54,7 +54,7 @@ export async function resolveTerminalEncoding(verbose?: boolean): Promise(resolve => { + rawEncodingPromise = new Promise(resolve => { if (verbose) { console.log('Running "chcp" to detect terminal encoding...'); } diff --git a/src/vs/base/node/zip.ts b/src/vs/base/node/zip.ts index c9b993643f..b2c7b851d7 100644 --- a/src/vs/base/node/zip.ts +++ b/src/vs/base/node/zip.ts @@ -12,6 +12,7 @@ import { mkdirp, rimraf } from 'vs/base/node/pfs'; import { open as _openZip, Entry, ZipFile } from 'yauzl'; import * as yazl from 'yazl'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { assertIsDefined } from 'vs/base/common/types'; export interface IExtractOptions { overwrite?: boolean; @@ -161,24 +162,24 @@ function extractZip(zipfile: ZipFile, targetPath: string, options: IOptions, tok } function openZip(zipFile: string, lazy: boolean = false): Promise { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { _openZip(zipFile, lazy ? { lazyEntries: true } : undefined!, (error?: Error, zipfile?: ZipFile) => { if (error) { reject(toExtractError(error)); } else { - resolve(zipfile); + resolve(assertIsDefined(zipfile)); } }); }); } function openZipStream(zipFile: ZipFile, entry: Entry): Promise { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { zipFile.openReadStream(entry, (error?: Error, stream?: Readable) => { if (error) { reject(toExtractError(error)); } else { - resolve(stream); + resolve(assertIsDefined(stream)); } }); }); diff --git a/src/vs/base/parts/ipc/node/ipc.cp.ts b/src/vs/base/parts/ipc/node/ipc.cp.ts index 813fae737f..8258d4fc6a 100644 --- a/src/vs/base/parts/ipc/node/ipc.cp.ts +++ b/src/vs/base/parts/ipc/node/ipc.cp.ts @@ -6,7 +6,7 @@ import { ChildProcess, fork, ForkOptions } from 'child_process'; import { IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle'; import { Delayer, createCancelablePromise } from 'vs/base/common/async'; -import { deepClone, assign } from 'vs/base/common/objects'; +import { deepClone } from 'vs/base/common/objects'; import { Emitter, Event } from 'vs/base/common/event'; import { createQueuedSender } from 'vs/base/node/processes'; import { IChannel, ChannelServer as IPCServer, ChannelClient as IPCClient, IChannelClient } from 'vs/base/parts/ipc/common/ipc'; @@ -14,6 +14,7 @@ import { isRemoteConsoleLog, log } from 'vs/base/common/console'; import { CancellationToken } from 'vs/base/common/cancellation'; import * as errors from 'vs/base/common/errors'; import { VSBuffer } from 'vs/base/common/buffer'; +import { isMacintosh } from 'vs/base/common/platform'; /** * This implementation doesn't perform well since it uses base64 encoding for buffers. @@ -179,10 +180,10 @@ export class Client implements IChannelClient, IDisposable { const args = this.options && this.options.args ? this.options.args : []; const forkOpts: ForkOptions = Object.create(null); - forkOpts.env = assign(deepClone(process.env), { 'VSCODE_PARENT_PID': String(process.pid) }); + forkOpts.env = { ...deepClone(process.env), 'VSCODE_PARENT_PID': String(process.pid) }; if (this.options && this.options.env) { - forkOpts.env = assign(forkOpts.env, this.options.env); + forkOpts.env = { ...forkOpts.env, ...this.options.env }; } if (this.options && this.options.freshExecArgv) { @@ -197,6 +198,12 @@ export class Client implements IChannelClient, IDisposable { forkOpts.execArgv = ['--nolazy', '--inspect-brk=' + this.options.debugBrk]; } + if (isMacintosh && forkOpts.env) { + // Unset `DYLD_LIBRARY_PATH`, as it leads to process crashes + // See https://github.com/microsoft/vscode/issues/105848 + delete forkOpts.env['DYLD_LIBRARY_PATH']; + } + this.child = fork(this.modulePath, args, forkOpts); const onMessageEmitter = new Emitter(); diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts index f3a79caa3e..53b84a7594 100644 --- a/src/vs/base/parts/quickinput/browser/quickInput.ts +++ b/src/vs/base/parts/quickinput/browser/quickInput.ts @@ -1416,7 +1416,7 @@ export class QuickInputController extends Disposable { } input(options: IInputOptions = {}, token: CancellationToken = CancellationToken.None): Promise { - return new Promise((resolve, reject) => { + return new Promise((resolve) => { if (token.isCancellationRequested) { resolve(undefined); return; @@ -1692,8 +1692,8 @@ export class QuickInputController extends Disposable { content.push(`.quick-input-list .quick-input-list-separator { color: ${this.styles.list.pickerGroupForeground}; }`); } const newStyles = content.join('\n'); - if (newStyles !== this.ui.styleSheet.innerHTML) { - this.ui.styleSheet.innerHTML = newStyles; + if (newStyles !== this.ui.styleSheet.textContent) { + this.ui.styleSheet.textContent = newStyles; } } } diff --git a/src/vs/base/parts/request/browser/request.ts b/src/vs/base/parts/request/browser/request.ts index 7d2df75e2d..3eae966f7c 100644 --- a/src/vs/base/parts/request/browser/request.ts +++ b/src/vs/base/parts/request/browser/request.ts @@ -5,13 +5,15 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { canceled } from 'vs/base/common/errors'; -import { assign } from 'vs/base/common/objects'; import { VSBuffer, bufferToStream } from 'vs/base/common/buffer'; import { IRequestOptions, IRequestContext } from 'vs/base/parts/request/common/request'; export function request(options: IRequestOptions, token: CancellationToken): Promise { if (options.proxyAuthorization) { - options.headers = assign(options.headers || {}, { 'Proxy-Authorization': options.proxyAuthorization }); + options.headers = { + ...(options.headers || {}), + 'Proxy-Authorization': options.proxyAuthorization + }; } const xhr = new XMLHttpRequest(); diff --git a/src/vs/base/parts/sandbox/electron-browser/preload.js b/src/vs/base/parts/sandbox/electron-browser/preload.js index e9a88ca2fe..f4bcc5273b 100644 --- a/src/vs/base/parts/sandbox/electron-browser/preload.js +++ b/src/vs/base/parts/sandbox/electron-browser/preload.js @@ -93,6 +93,7 @@ process: { platform: process.platform, env: process.env, + versions: process.versions, _whenEnvResolved: undefined, get whenEnvResolved() { if (!this._whenEnvResolved) { diff --git a/src/vs/base/parts/sandbox/electron-sandbox/globals.ts b/src/vs/base/parts/sandbox/electron-sandbox/globals.ts index 5577f7d37d..2142404a6d 100644 --- a/src/vs/base/parts/sandbox/electron-sandbox/globals.ts +++ b/src/vs/base/parts/sandbox/electron-sandbox/globals.ts @@ -94,6 +94,11 @@ export const process = (window as any).vscode.process as { * A listener on the process. Only a small subset of listener types are allowed. */ on: (type: string, callback: Function) => void; + + /** + * A list of versions for the current node.js/electron configuration. + */ + versions: { [key: string]: string | undefined }; }; export const context = (window as any).vscode.context as { diff --git a/src/vs/base/test/browser/actionbar.test.ts b/src/vs/base/test/browser/actionbar.test.ts index c83aef6d9f..7805b86b05 100644 --- a/src/vs/base/test/browser/actionbar.test.ts +++ b/src/vs/base/test/browser/actionbar.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionBar, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action, Separator } from 'vs/base/common/actions'; suite('Actionbar', () => { @@ -24,4 +24,37 @@ suite('Actionbar', () => { assert(actions[1] === a5); assert(actions[2] === a6); }); + + test('hasAction()', function () { + const container = document.createElement('div'); + const actionbar = new ActionBar(container); + + let a1 = new Action('a1'); + let a2 = new Action('a2'); + + actionbar.push(a1); + assert.equal(actionbar.hasAction(a1), true); + assert.equal(actionbar.hasAction(a2), false); + + actionbar.pull(0); + assert.equal(actionbar.hasAction(a1), false); + + actionbar.push(a1, { index: 1 }); + actionbar.push(a2, { index: 0 }); + assert.equal(actionbar.hasAction(a1), true); + assert.equal(actionbar.hasAction(a2), true); + + actionbar.pull(0); + assert.equal(actionbar.hasAction(a1), true); + assert.equal(actionbar.hasAction(a2), false); + + actionbar.pull(0); + assert.equal(actionbar.hasAction(a1), false); + assert.equal(actionbar.hasAction(a2), false); + + actionbar.push(a1); + assert.equal(actionbar.hasAction(a1), true); + actionbar.clear(); + assert.equal(actionbar.hasAction(a1), false); + }); }); diff --git a/src/vs/base/test/browser/codicons.test.ts b/src/vs/base/test/browser/codicons.test.ts index 6ef429afd8..beeb5be101 100644 --- a/src/vs/base/test/browser/codicons.test.ts +++ b/src/vs/base/test/browser/codicons.test.ts @@ -3,43 +3,43 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { renderCodiconsAsElement } from 'vs/base/browser/codicons'; +import { renderCodicons } from 'vs/base/browser/codicons'; import * as assert from 'assert'; suite('renderCodicons', () => { test('no codicons', () => { - const result = renderCodiconsAsElement(' hello World .'); + const result = renderCodicons(' hello World .'); assert.equal(elementsToString(result), ' hello World .'); }); test('codicon only', () => { - const result = renderCodiconsAsElement('$(alert)'); + const result = renderCodicons('$(alert)'); assert.equal(elementsToString(result), ''); }); test('codicon and non-codicon strings', () => { - const result = renderCodiconsAsElement(` $(alert) Unresponsive`); + const result = renderCodicons(` $(alert) Unresponsive`); assert.equal(elementsToString(result), ' Unresponsive'); }); test('multiple codicons', () => { - const result = renderCodiconsAsElement('$(check)$(error)'); + const result = renderCodicons('$(check)$(error)'); assert.equal(elementsToString(result), ''); }); test('escaped codicon', () => { - const result = renderCodiconsAsElement('\\$(escaped)'); + const result = renderCodicons('\\$(escaped)'); assert.equal(elementsToString(result), '$(escaped)'); }); test('codicon with animation', () => { - const result = renderCodiconsAsElement('$(zip~anim)'); + const result = renderCodicons('$(zip~anim)'); assert.equal(elementsToString(result), ''); }); diff --git a/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts b/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts index 4ed0de0f1a..1a6e04e2ac 100644 --- a/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts +++ b/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts @@ -7,7 +7,6 @@ import * as assert from 'assert'; import { ITreeNode, ITreeRenderer, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list'; -import { hasClass } from 'vs/base/browser/dom'; import { timeout } from 'vs/base/common/async'; interface Element { @@ -103,8 +102,8 @@ suite('AsyncDataTree', function () { await tree.setInput(model.root); assert.equal(container.querySelectorAll('.monaco-list-row').length, 1); let twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement; - assert(!hasClass(twistie, 'collapsible')); - assert(!hasClass(twistie, 'collapsed')); + assert(!twistie.classList.contains('collapsible')); + assert(!twistie.classList.contains('collapsed')); model.get('a').children = [ { id: 'aa' }, @@ -151,8 +150,8 @@ suite('AsyncDataTree', function () { assert.deepStrictEqual(getChildrenCalls, ['root']); let twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement; - assert(!hasClass(twistie, 'collapsible')); - assert(!hasClass(twistie, 'collapsed')); + assert(!twistie.classList.contains('collapsible')); + assert(!twistie.classList.contains('collapsed')); assert(tree.getNode().children[0].collapsed); model.get('a').children = [{ id: 'aa' }, { id: 'ab' }, { id: 'ac' }]; @@ -160,8 +159,8 @@ suite('AsyncDataTree', function () { assert.deepStrictEqual(getChildrenCalls, ['root', 'root']); twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement; - assert(hasClass(twistie, 'collapsible')); - assert(hasClass(twistie, 'collapsed')); + assert(twistie.classList.contains('collapsible')); + assert(twistie.classList.contains('collapsed')); assert(tree.getNode().children[0].collapsed); model.get('a').children = []; @@ -169,8 +168,8 @@ suite('AsyncDataTree', function () { assert.deepStrictEqual(getChildrenCalls, ['root', 'root', 'root']); twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement; - assert(!hasClass(twistie, 'collapsible')); - assert(!hasClass(twistie, 'collapsed')); + assert(!twistie.classList.contains('collapsible')); + assert(!twistie.classList.contains('collapsed')); assert(tree.getNode().children[0].collapsed); model.get('a').children = [{ id: 'aa' }, { id: 'ab' }, { id: 'ac' }]; @@ -178,8 +177,8 @@ suite('AsyncDataTree', function () { assert.deepStrictEqual(getChildrenCalls, ['root', 'root', 'root', 'root']); twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement; - assert(hasClass(twistie, 'collapsible')); - assert(hasClass(twistie, 'collapsed')); + assert(twistie.classList.contains('collapsible')); + assert(twistie.classList.contains('collapsed')); assert(tree.getNode().children[0].collapsed); }); @@ -241,8 +240,8 @@ suite('AsyncDataTree', function () { await tree.expand(model.get('a')); let twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement; - assert(hasClass(twistie, 'collapsible')); - assert(!hasClass(twistie, 'collapsed')); + assert(twistie.classList.contains('collapsible')); + assert(!twistie.classList.contains('collapsed')); assert(!tree.getNode(model.get('a')).collapsed); tree.collapse(model.get('a')); @@ -250,8 +249,8 @@ suite('AsyncDataTree', function () { await tree.updateChildren(model.root); twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement; - assert(!hasClass(twistie, 'collapsible')); - assert(!hasClass(twistie, 'collapsed')); + assert(!twistie.classList.contains('collapsible')); + assert(!twistie.classList.contains('collapsed')); assert(tree.getNode(model.get('a')).collapsed); }); @@ -295,7 +294,7 @@ suite('AsyncDataTree', function () { return !!element.children && element.children.length > 0; } getChildren(element: Element): Promise { - return new Promise(c => calls.push(() => c(element.children))); + return new Promise(c => calls.push(() => c(element.children || []))); } }; @@ -338,7 +337,7 @@ suite('AsyncDataTree', function () { return !!element.children && element.children.length > 0; } getChildren(element: Element): Promise { - return new Promise(c => calls.push(() => c(element.children))); + return new Promise(c => calls.push(() => c(element.children || []))); } }; @@ -387,22 +386,22 @@ suite('AsyncDataTree', function () { assert.equal(container.querySelectorAll('.monaco-list-row').length, 1); let twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement; - assert(!hasClass(twistie, 'collapsible')); - assert(!hasClass(twistie, 'collapsed')); + assert(!twistie.classList.contains('collapsible')); + assert(!twistie.classList.contains('collapsed')); model.get('a').children = [{ id: 'aa' }]; await tree.updateChildren(model.get('a'), false); assert.equal(container.querySelectorAll('.monaco-list-row').length, 1); twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement; - assert(hasClass(twistie, 'collapsible')); - assert(hasClass(twistie, 'collapsed')); + assert(twistie.classList.contains('collapsible')); + assert(twistie.classList.contains('collapsed')); model.get('a').children = []; await tree.updateChildren(model.get('a'), false); assert.equal(container.querySelectorAll('.monaco-list-row').length, 1); twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement; - assert(!hasClass(twistie, 'collapsible')); - assert(!hasClass(twistie, 'collapsed')); + assert(!twistie.classList.contains('collapsible')); + assert(!twistie.classList.contains('collapsed')); }); test('issues #84569, #82629 - rerender', async () => { diff --git a/src/vs/base/test/common/async.test.ts b/src/vs/base/test/common/async.test.ts index 5c4879cc5a..0f8e12e6d7 100644 --- a/src/vs/base/test/common/async.test.ts +++ b/src/vs/base/test/common/async.test.ts @@ -527,7 +527,7 @@ suite('Async', () => { r1Queue.queue(syncPromiseFactory); - return new Promise(c => setTimeout(() => c(), 0)).then(() => { + return new Promise(c => setTimeout(() => c(), 0)).then(() => { const r1Queue2 = queue.queueFor(URI.file('/some/path')); assert.notEqual(r1Queue, r1Queue2); // previous one got disposed after finishing }); diff --git a/src/vs/base/test/common/utils.ts b/src/vs/base/test/common/utils.ts index 865004bbf7..e44ab132f0 100644 --- a/src/vs/base/test/common/utils.ts +++ b/src/vs/base/test/common/utils.ts @@ -25,21 +25,21 @@ export class DeferredPromise { } public complete(value: T) { - return new Promise(resolve => { + return new Promise(resolve => { this.completeCallback(value); resolve(); }); } public error(err: any) { - return new Promise(resolve => { + return new Promise(resolve => { this.errorCallback(err); resolve(); }); } public cancel() { - new Promise(resolve => { + new Promise(resolve => { this.errorCallback(canceled()); resolve(); }); diff --git a/src/vs/base/worker/workerMain.ts b/src/vs/base/worker/workerMain.ts index db3a7829ca..f526252abb 100644 --- a/src/vs/base/worker/workerMain.ts +++ b/src/vs/base/worker/workerMain.ts @@ -14,7 +14,8 @@ require.config({ baseUrl: monacoBaseUrl, - catchError: true + catchError: true, + createTrustedScriptURL: (value: string) => value, }); let loadCode = function (moduleId: string) { diff --git a/src/vs/code/browser/workbench/workbench-dev.html b/src/vs/code/browser/workbench/workbench-dev.html index f15d6754b8..db250908f6 100644 --- a/src/vs/code/browser/workbench/workbench-dev.html +++ b/src/vs/code/browser/workbench/workbench-dev.html @@ -20,9 +20,6 @@ - - - @@ -36,6 +33,7 @@ self.require = { baseUrl: `${window.location.origin}/static/out`, recordStats: true, + createTrustedScriptURL: value => value, paths: { 'vscode-textmate': `${window.location.origin}/static/remote/web/node_modules/vscode-textmate/release/main`, 'vscode-oniguruma': `${window.location.origin}/static/remote/web/node_modules/vscode-oniguruma/release/main`, diff --git a/src/vs/code/browser/workbench/workbench.html b/src/vs/code/browser/workbench/workbench.html index e1b01d5ebd..0ab4647438 100644 --- a/src/vs/code/browser/workbench/workbench.html +++ b/src/vs/code/browser/workbench/workbench.html @@ -14,9 +14,6 @@ - - - @@ -34,6 +31,7 @@ self.require = { baseUrl: `${window.location.origin}/static/out`, recordStats: true, + createTrustedScriptURL: value => value, paths: { 'vscode-textmate': `${window.location.origin}/static/node_modules/vscode-textmate/release/main`, 'vscode-oniguruma': `${window.location.origin}/static/node_modules/vscode-oniguruma/release/main`, diff --git a/src/vs/code/browser/workbench/workbench.ts b/src/vs/code/browser/workbench/workbench.ts index d7cbb94985..a2c92b5b39 100644 --- a/src/vs/code/browser/workbench/workbench.ts +++ b/src/vs/code/browser/workbench/workbench.ts @@ -27,13 +27,6 @@ class LocalStorageCredentialsProvider implements ICredentialsProvider { static readonly CREDENTIALS_OPENED_KEY = 'credentials.provider'; - constructor(credentials: ICredential[]) { - this._credentials = credentials; - for (const { service, account, password } of this._credentials) { - this.setPassword(service, account, password); - } - } - private _credentials: ICredential[] | undefined; private get credentials(): ICredential[] { if (!this._credentials) { @@ -437,16 +430,20 @@ class WindowIndicator implements IWindowIndicator { window.location.href = `${window.location.origin}?${queryString}`; }; - // Find credentials from DOM + // Credentials (with support of predefined ones via meta element) + const credentialsProvider = new LocalStorageCredentialsProvider(); + const credentialsElement = document.getElementById('vscode-workbench-credentials'); const credentialsElementAttribute = credentialsElement ? credentialsElement.getAttribute('data-settings') : undefined; let credentials = undefined; if (credentialsElementAttribute) { try { credentials = JSON.parse(credentialsElementAttribute); + for (const { service, account, password } of credentials) { + credentialsProvider.setPassword(service, account, password); + } } catch (error) { /* Invalid credentials are passed. Ignore. */ } } - const credentialsProvider = new LocalStorageCredentialsProvider(credentials || []); // Finally create workbench create(document.body, { diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts index 2a1353a48a..1b7bc3b4da 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts @@ -10,8 +10,7 @@ import product from 'vs/platform/product/common/product'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { onUnexpectedError } from 'vs/base/common/errors'; import { ILogService } from 'vs/platform/log/common/log'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; interface ExtensionEntry { version: string; diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts index 68822853c9..37d1fcf920 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts @@ -7,8 +7,7 @@ import { basename, dirname, join } from 'vs/base/common/path'; import { onUnexpectedError } from 'vs/base/common/errors'; import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { readdir, rimraf, stat } from 'vs/base/node/pfs'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import product from 'vs/platform/product/common/product'; export class NodeCachedDataCleaner { diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts index f15410f935..3971ce6f57 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts @@ -3,8 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { join } from 'vs/base/common/path'; import { readdir, readFile, rimraf } from 'vs/base/node/pfs'; import { onUnexpectedError } from 'vs/base/common/errors'; diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 5b10506809..78d43cf01b 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -11,7 +11,7 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { ParsedArgs } from 'vs/platform/environment/node/argv'; +import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; import { ExtensionManagementChannel, ExtensionTipsChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService, IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -81,7 +81,7 @@ export function startup(configuration: ISharedProcessConfiguration) { interface ISharedProcessInitData { sharedIPCHandle: string; - args: ParsedArgs; + args: NativeParsedArgs; logLevel: LogLevel; } @@ -116,7 +116,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat disposables.add(server); - const environmentService = new EnvironmentService(initData.args, process.execPath); + const environmentService = new EnvironmentService(initData.args); const mainRouter = new StaticRouter(ctx => ctx === 'main'); const loggerClient = new LoggerChannelClient(server.getChannel('logger', mainRouter)); diff --git a/src/vs/code/electron-browser/workbench/workbench.js b/src/vs/code/electron-browser/workbench/workbench.js index 0da0faef2e..5002eb707c 100644 --- a/src/vs/code/electron-browser/workbench/workbench.js +++ b/src/vs/code/electron-browser/workbench/workbench.js @@ -77,7 +77,7 @@ bootstrapWindow.load([ /** * @param {{ * partsSplashPath?: string, - * highContrast?: boolean, + * colorScheme: ('light' | 'dark' | 'hc'), * autoDetectHighContrast?: boolean, * extensionDevelopmentPath?: string[], * folderUri?: object, @@ -97,7 +97,7 @@ function showPartsSplash(configuration) { } // high contrast mode has been turned on from the outside, e.g. OS -> ignore stored colors and layouts - const isHighContrast = configuration.highContrast && configuration.autoDetectHighContrast; + const isHighContrast = configuration.colorScheme === 'hc' /* ColorScheme.HIGH_CONTRAST */ && configuration.autoDetectHighContrast; if (data && isHighContrast && data.baseTheme !== 'hc-black') { data = undefined; } @@ -125,7 +125,7 @@ function showPartsSplash(configuration) { const style = document.createElement('style'); style.className = 'initialShellColors'; document.head.appendChild(style); - style.innerHTML = `body { background-color: ${shellBackground}; color: ${shellForeground}; margin: 0; padding: 0; }`; + style.textContent = `body { background-color: ${shellBackground}; color: ${shellForeground}; margin: 0; padding: 0; }`; if (data && data.layoutInfo) { // restore parts if possible (we might not always store layout info) @@ -149,22 +149,29 @@ function showPartsSplash(configuration) { // ensure there is enough space layoutInfo.sideBarWidth = Math.min(layoutInfo.sideBarWidth, window.innerWidth - (layoutInfo.activityBarWidth + layoutInfo.editorPartMinWidth)); + // part: title + const titleDiv = document.createElement('div'); + titleDiv.setAttribute('style', `position: absolute; width: 100%; left: 0; top: 0; height: ${layoutInfo.titleBarHeight}px; background-color: ${colorInfo.titleBarBackground}; -webkit-app-region: drag;`); + splash.appendChild(titleDiv); + + // part: activity bar + const activityDiv = document.createElement('div'); + activityDiv.setAttribute('style', `position: absolute; height: calc(100% - ${layoutInfo.titleBarHeight}px); top: ${layoutInfo.titleBarHeight}px; ${layoutInfo.sideBarSide}: 0; width: ${layoutInfo.activityBarWidth}px; background-color: ${colorInfo.activityBarBackground};`); + splash.appendChild(activityDiv); + + // part: side bar (only when opening workspace/folder) if (configuration.folderUri || configuration.workspace) { // folder or workspace -> status bar color, sidebar - splash.innerHTML = ` -
-
-
-
- `; - } else { - // empty -> speical status bar color, no sidebar - splash.innerHTML = ` -
-
-
- `; + const sideDiv = document.createElement('div'); + sideDiv.setAttribute('style', `position: absolute; height: calc(100% - ${layoutInfo.titleBarHeight}px); top: ${layoutInfo.titleBarHeight}px; ${layoutInfo.sideBarSide}: ${layoutInfo.activityBarWidth}px; width: ${layoutInfo.sideBarWidth}px; background-color: ${colorInfo.sideBarBackground};`); + splash.appendChild(sideDiv); } + + // part: statusbar + const statusDiv = document.createElement('div'); + statusDiv.setAttribute('style', `position: absolute; width: 100%; bottom: 0; left: 0; height: ${layoutInfo.statusBarHeight}px; background-color: ${configuration.folderUri || configuration.workspace ? colorInfo.statusBarBackground : colorInfo.statusBarNoFolderBackground};`); + splash.appendChild(statusDiv); + document.body.appendChild(splash); } diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index bc9b2563ee..a60f38740f 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -22,7 +22,7 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ILogService } from 'vs/platform/log/common/log'; import { IStateService } from 'vs/platform/state/node/state'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IURLService } from 'vs/platform/url/common/url'; import { URLHandlerChannelClient, URLHandlerRouter } from 'vs/platform/url/common/urlIpc'; @@ -76,7 +76,6 @@ import { withNullAsUndefined } from 'vs/base/common/types'; import { coalesce } from 'vs/base/common/arrays'; import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; import { StorageKeysSyncRegistryChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { mnemonicButtonLabel, getPathLabel } from 'vs/base/common/labels'; import { WebviewMainService } from 'vs/platform/webview/electron-main/webviewMainService'; import { IWebviewManagerService } from 'vs/platform/webview/common/webviewManagerService'; diff --git a/src/vs/code/electron-main/auth.ts b/src/vs/code/electron-main/auth.ts index c38c6bf4ad..8dd409f1e8 100644 --- a/src/vs/code/electron-main/auth.ts +++ b/src/vs/code/electron-main/auth.ts @@ -55,7 +55,7 @@ export class ProxyAuthHandler extends Disposable { skipTaskbar: true, resizable: false, width: 450, - height: 220, + height: 225, show: true, title: 'VS Code', webPreferences: { @@ -64,6 +64,7 @@ export class ProxyAuthHandler extends Disposable { contextIsolation: true, enableWebSQL: false, enableRemoteModule: false, + spellcheck: false, devTools: false } }; @@ -92,9 +93,9 @@ export class ProxyAuthHandler extends Disposable { if (channel === 'vscode:proxyAuthResponse') { const { username, password } = credentials; cb(username, password); + win.removeListener('close', onWindowClose); + win.close(); } - win.removeListener('close', onWindowClose); - win.close(); }); win.loadURL(url); } diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 45a3e3e30f..397f6b05f8 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -5,12 +5,12 @@ import 'vs/platform/update/common/update.config.contribution'; import { app, dialog } from 'electron'; -import { isWindows, IProcessEnvironment } from 'vs/base/common/platform'; +import * as fs from 'fs'; +import { isWindows, IProcessEnvironment, isMacintosh } from 'vs/base/common/platform'; import product from 'vs/platform/product/common/product'; import { parseMainProcessArgv, addArg } from 'vs/platform/environment/node/argvHelper'; import { createWaitMarkerFile } from 'vs/platform/environment/node/waitMarkerFile'; import { mkdirp } from 'vs/base/node/pfs'; -import { validatePaths } from 'vs/code/node/paths'; import { LifecycleMainService, ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { Server, serve, connect } from 'vs/base/parts/ipc/node/ipc.net'; import { createChannelSender } from 'vs/base/parts/ipc/common/ipc'; @@ -22,14 +22,13 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ILogService, ConsoleLogMainService, MultiplexLogService, getLogLevel } from 'vs/platform/log/common/log'; import { StateService } from 'vs/platform/state/node/stateService'; import { IStateService } from 'vs/platform/state/node/state'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { ParsedArgs } from 'vs/platform/environment/node/argv'; -import { EnvironmentService, xdgRuntimeDir, INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; +import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; +import { EnvironmentService, xdgRuntimeDir } from 'vs/platform/environment/node/environmentService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ConfigurationService } from 'vs/platform/configuration/common/configurationService'; import { IRequestService } from 'vs/platform/request/common/request'; import { RequestMainService } from 'vs/platform/request/electron-main/requestMainService'; -import * as fs from 'fs'; import { CodeApplication } from 'vs/code/electron-main/app'; import { localize } from 'vs/nls'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; @@ -50,6 +49,11 @@ import { IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService } from import { ITunnelService } from 'vs/platform/remote/common/tunnel'; import { TunnelService } from 'vs/platform/remote/node/tunnelService'; import { IProductService } from 'vs/platform/product/common/productService'; +import { IPathWithLineAndColumn, isValidBasename, parseLineAndColumnAware, sanitizeFilePath } from 'vs/base/common/extpath'; +import { isNumber } from 'vs/base/common/types'; +import { rtrim, trim } from 'vs/base/common/strings'; +import { basename, resolve } from 'vs/base/common/path'; +import { coalesce, distinct } from 'vs/base/common/arrays'; class ExpectedError extends Error { readonly isExpected = true; @@ -64,10 +68,10 @@ class CodeMain { setUnexpectedErrorHandler(err => console.error(err)); // Parse arguments - let args: ParsedArgs; + let args: NativeParsedArgs; try { args = parseMainProcessArgv(process.argv); - args = validatePaths(args); + args = this.validatePaths(args); } catch (err) { console.error(err.message); app.exit(1); @@ -94,7 +98,7 @@ class CodeMain { this.startup(args); } - private async startup(args: ParsedArgs): Promise { + private async startup(args: NativeParsedArgs): Promise { // We need to buffer the spdlog logs until we are sure // we are the only instance running, otherwise we'll have concurrent @@ -142,10 +146,10 @@ class CodeMain { } } - private createServices(args: ParsedArgs, bufferLogService: BufferLogService): [IInstantiationService, IProcessEnvironment, INativeEnvironmentService] { + private createServices(args: NativeParsedArgs, bufferLogService: BufferLogService): [IInstantiationService, IProcessEnvironment, INativeEnvironmentService] { const services = new ServiceCollection(); - const environmentService = new EnvironmentService(args, process.execPath); + const environmentService = new EnvironmentService(args); const instanceEnvironment = this.patchEnvironment(environmentService); // Patch `process.env` with the instance's environment services.set(IEnvironmentService, environmentService); @@ -180,7 +184,7 @@ class CodeMain { environmentService.logsPath, environmentService.globalStorageHome.fsPath, environmentService.workspaceStorageHome.fsPath, - environmentService.backupHome.fsPath + environmentService.backupHome ].map((path): undefined | Promise => path ? mkdirp(path) : undefined)); // Configuration service @@ -209,7 +213,7 @@ class CodeMain { return instanceEnvironment; } - private async doStartup(args: ParsedArgs, logService: ILogService, environmentService: INativeEnvironmentService, lifecycleMainService: ILifecycleMainService, instantiationService: IInstantiationService, retry: boolean): Promise { + private async doStartup(args: NativeParsedArgs, logService: ILogService, environmentService: INativeEnvironmentService, lifecycleMainService: ILifecycleMainService, instantiationService: IInstantiationService, retry: boolean): Promise { // Try to setup a server for running. If that succeeds it means // we are the first instance to startup. Otherwise it is likely @@ -409,6 +413,100 @@ class CodeMain { lifecycleMainService.kill(exitCode); } + + //#region Helpers + + private validatePaths(args: NativeParsedArgs): NativeParsedArgs { + + // Track URLs if they're going to be used + if (args['open-url']) { + args._urls = args._; + args._ = []; + } + + // Normalize paths and watch out for goto line mode + if (!args['remote']) { + const paths = this.doValidatePaths(args._, args.goto); + args._ = paths; + } + + return args; + } + + private doValidatePaths(args: string[], gotoLineMode?: boolean): string[] { + const cwd = process.env['VSCODE_CWD'] || process.cwd(); + const result = args.map(arg => { + let pathCandidate = String(arg); + + let parsedPath: IPathWithLineAndColumn | undefined = undefined; + if (gotoLineMode) { + parsedPath = parseLineAndColumnAware(pathCandidate); + pathCandidate = parsedPath.path; + } + + if (pathCandidate) { + pathCandidate = this.preparePath(cwd, pathCandidate); + } + + const sanitizedFilePath = sanitizeFilePath(pathCandidate, cwd); + + const filePathBasename = basename(sanitizedFilePath); + if (filePathBasename /* can be empty if code is opened on root */ && !isValidBasename(filePathBasename)) { + return null; // do not allow invalid file names + } + + if (gotoLineMode && parsedPath) { + parsedPath.path = sanitizedFilePath; + + return this.toPath(parsedPath); + } + + return sanitizedFilePath; + }); + + const caseInsensitive = isWindows || isMacintosh; + const distinctPaths = distinct(result, path => path && caseInsensitive ? path.toLowerCase() : (path || '')); + + return coalesce(distinctPaths); + } + + private preparePath(cwd: string, path: string): string { + + // Trim trailing quotes + if (isWindows) { + path = rtrim(path, '"'); // https://github.com/Microsoft/vscode/issues/1498 + } + + // Trim whitespaces + path = trim(trim(path, ' '), '\t'); + + if (isWindows) { + + // Resolve the path against cwd if it is relative + path = resolve(cwd, path); + + // Trim trailing '.' chars on Windows to prevent invalid file names + path = rtrim(path, '.'); + } + + return path; + } + + private toPath(pathWithLineAndCol: IPathWithLineAndColumn): string { + const segments = [pathWithLineAndCol.path]; + + if (isNumber(pathWithLineAndCol.line)) { + segments.push(String(pathWithLineAndCol.line)); + } + + if (isNumber(pathWithLineAndCol.column)) { + segments.push(String(pathWithLineAndCol.column)); + } + + return segments.join(':'); + } + + //#endregion } // Main Startup diff --git a/src/vs/code/electron-main/sharedProcess.ts b/src/vs/code/electron-main/sharedProcess.ts index 597ce9b07a..f01d9317e5 100644 --- a/src/vs/code/electron-main/sharedProcess.ts +++ b/src/vs/code/electron-main/sharedProcess.ts @@ -5,7 +5,7 @@ import { URI } from 'vs/base/common/uri'; import { memoize } from 'vs/base/common/decorators'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { BrowserWindow, ipcMain, WebContents, Event as ElectronEvent } from 'electron'; import { ISharedProcess } from 'vs/platform/ipc/electron-main/sharedProcessMainService'; import { Barrier } from 'vs/base/common/async'; @@ -14,7 +14,6 @@ import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifec import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; export class SharedProcess implements ISharedProcess { @@ -46,6 +45,7 @@ export class SharedProcess implements ISharedProcess { nodeIntegration: true, enableWebSQL: false, enableRemoteModule: false, + spellcheck: false, nativeWindowOpen: true, images: false, webgl: false, diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 68a5cf6b76..a24a412bc5 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -9,17 +9,16 @@ import * as nls from 'vs/nls'; import { Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display, TouchBarSegmentedControl, NativeImage, BrowserWindowConstructorOptions, SegmentedControlSegment, nativeTheme, Event, Details } from 'electron'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { ILogService } from 'vs/platform/log/common/log'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { parseArgs, OPTIONS, ParsedArgs } from 'vs/platform/environment/node/argv'; +import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; +import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import product from 'vs/platform/product/common/product'; -import { IWindowSettings, MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility, zoomLevelToZoomFactor } from 'vs/platform/windows/common/windows'; +import { IWindowSettings, MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility, zoomLevelToZoomFactor, INativeWindowConfiguration } from 'vs/platform/windows/common/windows'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { ICodeWindow, IWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows'; -import { INativeWindowConfiguration } from 'vs/platform/windows/node/window'; import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; @@ -35,6 +34,7 @@ import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IStorageMainService } from 'vs/platform/storage/node/storageMainService'; import { IFileService } from 'vs/platform/files/common/files'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; export interface IWindowCreationOptions { state: IWindowState; @@ -169,6 +169,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { preload: URI.parse(this.doGetPreloadUrl()).fsPath, enableWebSQL: false, enableRemoteModule: false, + spellcheck: false, nativeWindowOpen: true, webviewTag: true, zoomFactor: zoomLevelToZoomFactor(windowConfig?.zoomLevel), @@ -740,7 +741,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { this._onLoad.fire(); } - reload(configurationIn?: INativeWindowConfiguration, cli?: ParsedArgs): void { + reload(configurationIn?: INativeWindowConfiguration, cli?: NativeParsedArgs): void { // If config is not provided, copy our current one const configuration = configurationIn ? configurationIn : objects.mixin({}, this.currentConfig); @@ -785,7 +786,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { windowConfiguration.fullscreen = this.isFullScreen; // Set Accessibility Config - windowConfiguration.highContrast = nativeTheme.shouldUseInvertedColorScheme || nativeTheme.shouldUseHighContrastColors; + windowConfiguration.colorScheme = (nativeTheme.shouldUseInvertedColorScheme || nativeTheme.shouldUseHighContrastColors) ? ColorScheme.HIGH_CONTRAST : nativeTheme.shouldUseDarkColors ? ColorScheme.DARK : ColorScheme.LIGHT; windowConfiguration.autoDetectHighContrast = windowConfig?.autoDetectHighContrast ?? true; windowConfiguration.accessibilitySupport = app.accessibilitySupportEnabled; diff --git a/src/vs/code/electron-sandbox/issue/issueReporterMain.ts b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts index 0044a57b48..eee6674bad 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterMain.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts @@ -23,7 +23,7 @@ import { localize } from 'vs/nls'; import { isRemoteDiagnosticError, SystemInfo } from 'vs/platform/diagnostics/common/diagnostics'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IMainProcessService, MainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; -import { ISettingsSearchIssueReporterData, IssueReporterData, IssueReporterExtensionData, IssueReporterFeatures, IssueReporterStyles, IssueType } from 'vs/platform/issue/common/issue'; +import { IssueReporterData, IssueReporterExtensionData, IssueReporterFeatures, IssueReporterStyles, IssueType } from 'vs/platform/issue/common/issue'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; const MAX_URL_LENGTH = 2045; @@ -148,10 +148,6 @@ export class IssueReporter extends Disposable { applyZoom(configuration.data.zoomLevel); this.applyStyles(configuration.data.styles); this.handleExtensionData(configuration.data.enabledExtensions); - - if (configuration.data.issueType === IssueType.SettingsSearchIssue) { - this.handleSettingsSearchData(configuration.data); - } } render(): void { @@ -244,7 +240,7 @@ export class IssueReporter extends Disposable { content.push(`.monaco-text-button:not(.disabled):hover, .monaco-text-button:focus { background-color: ${styles.buttonHoverBackground} !important; }`); } - styleTag.innerHTML = content.join('\n'); + styleTag.textContent = content.join('\n'); document.head.appendChild(styleTag); document.body.style.color = styles.color || ''; } @@ -266,39 +262,6 @@ export class IssueReporter extends Disposable { this.updateExtensionSelector(installedExtensions); } - private handleSettingsSearchData(data: ISettingsSearchIssueReporterData): void { - this.issueReporterModel.update({ - actualSearchResults: data.actualSearchResults, - query: data.query, - filterResultCount: data.filterResultCount - }); - this.updateSearchedExtensionTable(data.enabledExtensions); - this.updateSettingsSearchDetails(data); - } - - private updateSettingsSearchDetails(data: ISettingsSearchIssueReporterData): void { - const target = document.querySelector('.block-settingsSearchResults .block-info'); - if (target) { - const queryDiv = $('div', undefined, `Query: "${data.query}"` as string); - const countDiv = $('div', undefined, `Literal match count: ${data.filterResultCount}` as string); - const detailsDiv = $('.block-settingsSearchResults-details', undefined, queryDiv, countDiv); - - const table = $('table', undefined, - $('tr', undefined, - $('th', undefined, 'Setting'), - $('th', undefined, 'Extension'), - $('th', undefined, 'Score'), - ), - ...data.actualSearchResults.map(setting => $('tr', undefined, - $('td', undefined, setting.key), - $('td', undefined, setting.extensionId), - $('td', undefined, String(setting.score).slice(0, 5)), - )) - ); - reset(target, detailsDiv, table); - } - } - private initServices(configuration: IssueReporterConfiguration): void { const serviceCollection = new ServiceCollection(); const mainProcessService = new MainProcessService(configuration.windowId); @@ -498,10 +461,6 @@ export class IssueReporter extends Disposable { return true; } - if (issueType === IssueType.SettingsSearchIssue) { - return true; - } - return false; } @@ -668,16 +627,11 @@ export class IssueReporter extends Disposable { const typeSelect = this.getElementById('issue-type')! as HTMLSelectElement; const { issueType } = this.issueReporterModel.getData(); - if (issueType === IssueType.SettingsSearchIssue) { - reset(typeSelect, makeOption(IssueType.SettingsSearchIssue, localize('settingsSearchIssue', "Settings Search Issue"))); - typeSelect.disabled = true; - } else { - reset(typeSelect, - makeOption(IssueType.Bug, localize('bugReporter', "Bug Report")), - makeOption(IssueType.FeatureRequest, localize('featureRequest', "Feature Request")), - makeOption(IssueType.PerformanceIssue, localize('performanceIssue', "Performance Issue")) - ); - } + reset(typeSelect, + makeOption(IssueType.Bug, localize('bugReporter', "Bug Report")), + makeOption(IssueType.FeatureRequest, localize('featureRequest', "Feature Request")), + makeOption(IssueType.PerformanceIssue, localize('performanceIssue', "Performance Issue")) + ); typeSelect.value = issueType.toString(); @@ -791,13 +745,6 @@ export class IssueReporter extends Disposable { if (fileOnExtension) { show(extensionSelector); } - } else if (issueType === IssueType.SettingsSearchIssue) { - show(blockContainer); - show(searchedExtensionsBlock); - show(settingsSearchResultsBlock); - - reset(descriptionTitle, localize('expectedResults', "Expected Results"), $('span.required-input', undefined, '*')); - reset(descriptionSubtitle, localize('settingsSearchResultsDescription', "Please list the results that you were expecting to see when you searched with this query. We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub.")); } } @@ -1135,20 +1082,6 @@ export class IssueReporter extends Disposable { } } - private updateSearchedExtensionTable(extensions: IssueReporterExtensionData[]): void { - const target = document.querySelector('.block-searchedExtensions .block-info'); - if (target) { - if (!extensions.length) { - target.innerText = 'Extensions: none'; - return; - } - - const table = this.getExtensionTableHtml(extensions); - target.innerText = ''; - target.appendChild(table); - } - } - private getExtensionTableHtml(extensions: IssueReporterExtensionData[]): HTMLTableElement { return $('table', undefined, $('tr', undefined, diff --git a/src/vs/code/electron-sandbox/issue/issueReporterModel.ts b/src/vs/code/electron-sandbox/issue/issueReporterModel.ts index 7cc9455f6b..9f61349918 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterModel.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterModel.ts @@ -103,8 +103,6 @@ ${this.getInfos()} return 'Bug'; } else if (this._data.issueType === IssueType.PerformanceIssue) { return 'Performance Issue'; - } else if (this._data.issueType === IssueType.SettingsSearchIssue) { - return 'Settings Search Issue'; } else { return 'Feature Request'; } @@ -136,17 +134,6 @@ ${this.getInfos()} } } - if (this._data.issueType === IssueType.SettingsSearchIssue) { - if (this._data.includeSearchedExtensions) { - info += this.generateExtensionsMd(); - } - - if (this._data.includeSettingsSearchDetails) { - info += this.generateSettingSearchResultsMd(); - info += '\n' + this.generateSettingsSearchResultDetailsMd(); - } - } - return info; } @@ -244,35 +231,6 @@ ${tableHeader} ${table} ${themeExclusionStr} -`; - } - - private generateSettingsSearchResultDetailsMd(): string { - return ` -Query: ${this._data.query} -Literal matches: ${this._data.filterResultCount}`; - } - - private generateSettingSearchResultsMd(): string { - if (!this._data.actualSearchResults) { - return ''; - } - - if (!this._data.actualSearchResults.length) { - return `No fuzzy results`; - } - - const tableHeader = `Setting|Extension|Score ----|---|---`; - const table = this._data.actualSearchResults.map(setting => { - return `${setting.key}|${setting.extensionId}|${String(setting.score).slice(0, 5)}`; - }).join('\n'); - - return `
Results - -${tableHeader} -${table} -
`; } } diff --git a/src/vs/code/electron-sandbox/issue/test/testReporterModel.test.ts b/src/vs/code/electron-sandbox/issue/test/testReporterModel.test.ts index 564960924e..44f543b8cb 100644 --- a/src/vs/code/electron-sandbox/issue/test/testReporterModel.test.ts +++ b/src/vs/code/electron-sandbox/issue/test/testReporterModel.test.ts @@ -218,16 +218,5 @@ Remote OS version: Linux x64 4.18.0 assert.equal(issueReporterModel.fileOnExtension(), true); }); - - [ - IssueType.SettingsSearchIssue - ].forEach(type => { - const issueReporterModel = new IssueReporterModel({ - issueType: type, - fileOnExtension: true - }); - - assert.equal(issueReporterModel.fileOnExtension(), false); - }); }); }); diff --git a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts index 99c51222a9..6d0acb32de 100644 --- a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts +++ b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts @@ -13,7 +13,7 @@ import { applyZoom, zoomIn, zoomOut } from 'vs/platform/windows/electron-sandbox import { IContextMenuItem } from 'vs/base/parts/contextmenu/common/contextmenu'; import { popup } from 'vs/base/parts/contextmenu/electron-sandbox/contextmenu'; import { ProcessItem } from 'vs/base/common/processes'; -import { addDisposableListener, addClass } from 'vs/base/browser/dom'; +import { addDisposableListener, addClass, $ } from 'vs/base/browser/dom'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { isRemoteDiagnosticError, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; import { MainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; @@ -281,13 +281,14 @@ class ProcessExplorer { container.innerText = ''; this.listeners.clear(); - const tableHead = document.createElement('thead'); - tableHead.innerHTML = ` - ${localize('cpu', "CPU %")} - ${localize('memory', "Memory (MB)")} - ${localize('pid', "PID")} - ${localize('name', "Name")} - `; + const tableHead = $('thead', undefined); + const row = $('tr'); + tableHead.append(row); + + row.append($('th.cpu', { scope: 'col' }, localize('cpu', "CPU %"))); + row.append($('th.memory', { scope: 'col' }, localize('memory', "Memory (MB)"))); + row.append($('th.pid', { scope: 'col' }, localize('pid', "PID"))); + row.append($('th.nameLabel', { scope: 'col' }, localize('name', "Name"))); container.append(tableHead); diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index 9921f2346a..903f1da0a1 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -6,7 +6,8 @@ import * as os from 'os'; import * as fs from 'fs'; import { spawn, ChildProcess, SpawnOptions } from 'child_process'; -import { buildHelpMessage, buildVersionMessage, OPTIONS, ParsedArgs } from 'vs/platform/environment/node/argv'; +import { buildHelpMessage, buildVersionMessage, OPTIONS } from 'vs/platform/environment/node/argv'; +import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { parseCLIProcessArgv, addArg } from 'vs/platform/environment/node/argvHelper'; import { createWaitMarkerFile } from 'vs/platform/environment/node/waitMarkerFile'; import product from 'vs/platform/product/common/product'; @@ -18,7 +19,7 @@ import type { ProfilingSession, Target } from 'v8-inspect-profiler'; import { isString } from 'vs/base/common/types'; import { hasStdinWithoutTty, stdinDataListener, getStdinFilePath, readFromStdin } from 'vs/platform/environment/node/stdin'; -function shouldSpawnCliProcess(argv: ParsedArgs): boolean { +function shouldSpawnCliProcess(argv: NativeParsedArgs): boolean { return !!argv['install-source'] || !!argv['list-extensions'] || !!argv['install-extension'] @@ -28,11 +29,11 @@ function shouldSpawnCliProcess(argv: ParsedArgs): boolean { } interface IMainCli { - main: (argv: ParsedArgs) => Promise; + main: (argv: NativeParsedArgs) => Promise; } export async function main(argv: string[]): Promise { - let args: ParsedArgs; + let args: NativeParsedArgs; try { args = parseCLIProcessArgv(argv); @@ -137,7 +138,7 @@ export async function main(argv: string[]): Promise { child.stdout!.on('data', (data: Buffer) => console.log(data.toString('utf8').trim())); child.stderr!.on('data', (data: Buffer) => console.log(data.toString('utf8').trim())); - await new Promise(c => child.once('exit', () => c())); + await new Promise(c => child.once('exit', () => c())); }); } diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 3e5664b282..6ddf059f0f 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -11,9 +11,9 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { ParsedArgs } from 'vs/platform/environment/node/argv'; -import { EnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; +import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; +import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; import { IExtensionManagementService, IExtensionGalleryService, IGalleryExtension, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; @@ -79,7 +79,7 @@ export class Main { @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService ) { } - async run(argv: ParsedArgs): Promise { + async run(argv: NativeParsedArgs): Promise { if (argv['install-source']) { await this.setInstallSource(argv['install-source']); } else if (argv['list-extensions']) { @@ -295,11 +295,11 @@ export class Main { const eventPrefix = 'adsworkbench'; // {{SQL CARBON EDIT}} -export async function main(argv: ParsedArgs): Promise { +export async function main(argv: NativeParsedArgs): Promise { const services = new ServiceCollection(); const disposables = new DisposableStore(); - const environmentService = new EnvironmentService(argv, process.execPath); + const environmentService = new EnvironmentService(argv); const logService: ILogService = new SpdLogService('cli', environmentService.logsPath, getLogLevel(environmentService)); process.once('exit', () => logService.dispose()); logService.info('main', argv); diff --git a/src/vs/code/node/paths.ts b/src/vs/code/node/paths.ts deleted file mode 100644 index c54ef2f496..0000000000 --- a/src/vs/code/node/paths.ts +++ /dev/null @@ -1,102 +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 path from 'vs/base/common/path'; -import * as arrays from 'vs/base/common/arrays'; -import * as strings from 'vs/base/common/strings'; -import * as extpath from 'vs/base/common/extpath'; -import * as platform from 'vs/base/common/platform'; -import * as types from 'vs/base/common/types'; -import { ParsedArgs } from 'vs/platform/environment/node/argv'; - -export function validatePaths(args: ParsedArgs): ParsedArgs { - - // Track URLs if they're going to be used - if (args['open-url']) { - args._urls = args._; - args._ = []; - } - - // Normalize paths and watch out for goto line mode - if (!args['remote']) { - const paths = doValidatePaths(args._, args.goto); - args._ = paths; - } - - return args; -} - -function doValidatePaths(args: string[], gotoLineMode?: boolean): string[] { - const cwd = process.env['VSCODE_CWD'] || process.cwd(); - const result = args.map(arg => { - let pathCandidate = String(arg); - - let parsedPath: extpath.IPathWithLineAndColumn | undefined = undefined; - if (gotoLineMode) { - parsedPath = extpath.parseLineAndColumnAware(pathCandidate); - pathCandidate = parsedPath.path; - } - - if (pathCandidate) { - pathCandidate = preparePath(cwd, pathCandidate); - } - - const sanitizedFilePath = extpath.sanitizeFilePath(pathCandidate, cwd); - - const basename = path.basename(sanitizedFilePath); - if (basename /* can be empty if code is opened on root */ && !extpath.isValidBasename(basename)) { - return null; // do not allow invalid file names - } - - if (gotoLineMode && parsedPath) { - parsedPath.path = sanitizedFilePath; - - return toPath(parsedPath); - } - - return sanitizedFilePath; - }); - - const caseInsensitive = platform.isWindows || platform.isMacintosh; - const distinct = arrays.distinct(result, e => e && caseInsensitive ? e.toLowerCase() : (e || '')); - - return arrays.coalesce(distinct); -} - -function preparePath(cwd: string, p: string): string { - - // Trim trailing quotes - if (platform.isWindows) { - p = strings.rtrim(p, '"'); // https://github.com/Microsoft/vscode/issues/1498 - } - - // Trim whitespaces - p = strings.trim(strings.trim(p, ' '), '\t'); - - if (platform.isWindows) { - - // Resolve the path against cwd if it is relative - p = path.resolve(cwd, p); - - // Trim trailing '.' chars on Windows to prevent invalid file names - p = strings.rtrim(p, '.'); - } - - return p; -} - -function toPath(p: extpath.IPathWithLineAndColumn): string { - const segments = [p.path]; - - if (types.isNumber(p.line)) { - segments.push(String(p.line)); - } - - if (types.isNumber(p.column)) { - segments.push(String(p.column)); - } - - return segments.join(':'); -} diff --git a/src/vs/code/node/shellEnv.ts b/src/vs/code/node/shellEnv.ts index 545f591b11..c96633a3c0 100644 --- a/src/vs/code/node/shellEnv.ts +++ b/src/vs/code/node/shellEnv.ts @@ -7,7 +7,7 @@ import { spawn } from 'child_process'; import { generateUuid } from 'vs/base/common/uuid'; import { isWindows } from 'vs/base/common/platform'; import { ILogService } from 'vs/platform/log/common/log'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; function getUnixShellEnvironment(logService: ILogService): Promise { const promise = new Promise((resolve, reject) => { diff --git a/src/vs/editor/browser/controller/pointerHandler.ts b/src/vs/editor/browser/controller/pointerHandler.ts index b4222a8a18..a7081572cc 100644 --- a/src/vs/editor/browser/controller/pointerHandler.ts +++ b/src/vs/editor/browser/controller/pointerHandler.ts @@ -50,7 +50,7 @@ class StandardPointerHandler extends MouseHandler implements IDisposable { this._installGestureHandlerTimeout = -1; // TODO@Alex: replace the usage of MSGesture here with something that works across all browsers - if ((window).MSGesture) { + if (window.MSGesture) { const touchGesture = new MSGesture(); const penGesture = new MSGesture(); touchGesture.target = this.viewHelper.linesContentDomNode; @@ -226,9 +226,9 @@ export class PointerHandler extends Disposable { super(); if ((platform.isIOS && BrowserFeatures.pointerEvents)) { this.handler = this._register(new PointerEventHandler(context, viewController, viewHelper)); - } else if ((window).TouchEvent) { + } else if (window.TouchEvent) { this.handler = this._register(new TouchHandler(context, viewController, viewHelper)); - } else if (window.navigator.pointerEnabled || (window).PointerEvent) { + } else if (window.navigator.pointerEnabled || window.PointerEvent) { this.handler = this._register(new StandardPointerHandler(context, viewController, viewHelper)); } else { this.handler = this._register(new MouseHandler(context, viewController, viewHelper)); diff --git a/src/vs/editor/browser/services/openerService.ts b/src/vs/editor/browser/services/openerService.ts index 09e75bbcc6..cb324b554f 100644 --- a/src/vs/editor/browser/services/openerService.ts +++ b/src/vs/editor/browser/services/openerService.ts @@ -74,7 +74,8 @@ class EditorOpener implements IOpener { } await this._editorService.openCodeEditor( - { resource: target, options: { selection, context: options?.fromUserGesture ? EditorOpenContext.USER : EditorOpenContext.API } }, + // {{SQL CARBON EDIT}} - cast target to fix hygine break + { resource: target, options: { selection, context: options?.fromUserGesture ? EditorOpenContext.USER : EditorOpenContext.API } }, this._editorService.getFocusedCodeEditor(), options?.openToSide ); diff --git a/src/vs/editor/browser/viewParts/lines/viewLine.ts b/src/vs/editor/browser/viewParts/lines/viewLine.ts index a7f68fed03..2b94e41bff 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLine.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLine.ts @@ -15,7 +15,7 @@ import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations'; import { CharacterMapping, ForeignElementType, RenderLineInput, renderViewLine, LineRange } from 'vs/editor/common/viewLayout/viewLineRenderer'; import { ViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; import { InlineDecorationType } from 'vs/editor/common/viewModel/viewModel'; -import { HIGH_CONTRAST, ThemeType } from 'vs/platform/theme/common/themeService'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; import { EditorOption, EditorFontLigatures } from 'vs/editor/common/config/editorOptions'; const canUseFastRenderedViewLine = (function () { @@ -71,7 +71,7 @@ export class DomReadingContext { } export class ViewLineOptions { - public readonly themeType: ThemeType; + public readonly themeType: ColorScheme; public readonly renderWhitespace: 'none' | 'boundary' | 'selection' | 'trailing' | 'all'; public readonly renderControlCharacters: boolean; public readonly spaceWidth: number; @@ -83,7 +83,7 @@ export class ViewLineOptions { public readonly stopRenderingLineAfter: number; public readonly fontLigatures: string; - constructor(config: IConfiguration, themeType: ThemeType) { + constructor(config: IConfiguration, themeType: ColorScheme) { this.themeType = themeType; const options = config.options; const fontInfo = options.get(EditorOption.fontInfo); @@ -163,7 +163,7 @@ export class ViewLine implements IVisibleLine { this._options = newOptions; } public onSelectionChanged(): boolean { - if (alwaysRenderInlineSelection || this._options.themeType === HIGH_CONTRAST || this._options.renderWhitespace === 'selection') { + if (alwaysRenderInlineSelection || this._options.themeType === ColorScheme.HIGH_CONTRAST || this._options.renderWhitespace === 'selection') { this._isMaybeInvalid = true; return true; } @@ -184,7 +184,7 @@ export class ViewLine implements IVisibleLine { // Only send selection information when needed for rendering whitespace let selectionsOnLine: LineRange[] | null = null; - if (alwaysRenderInlineSelection || options.themeType === HIGH_CONTRAST || this._options.renderWhitespace === 'selection') { + if (alwaysRenderInlineSelection || options.themeType === ColorScheme.HIGH_CONTRAST || this._options.renderWhitespace === 'selection') { const selections = viewportData.selections; for (const selection of selections) { @@ -197,7 +197,7 @@ export class ViewLine implements IVisibleLine { const endColumn = (selection.endLineNumber === lineNumber ? selection.endColumn : lineData.maxColumn); if (startColumn < endColumn) { - if (options.themeType === HIGH_CONTRAST || this._options.renderWhitespace !== 'selection') { + if (options.themeType === ColorScheme.HIGH_CONTRAST || this._options.renderWhitespace !== 'selection') { actualInlineDecorations.push(new LineDecoration(startColumn, endColumn, 'inline-selected-text', InlineDecorationType.Regular)); } else { if (!selectionsOnLine) { diff --git a/src/vs/editor/common/modes/languageFeatureRegistry.ts b/src/vs/editor/common/modes/languageFeatureRegistry.ts index d71af7e5f8..08a23f0ae1 100644 --- a/src/vs/editor/common/modes/languageFeatureRegistry.ts +++ b/src/vs/editor/common/modes/languageFeatureRegistry.ts @@ -4,7 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; +import { hash } from 'vs/base/common/hash'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { LRUCache } from 'vs/base/common/map'; +import { MovingAverage } from 'vs/base/common/numbers'; import { ITextModel } from 'vs/editor/common/model'; import { LanguageSelector, score } from 'vs/editor/common/modes/languageSelector'; import { shouldSynchronizeModel } from 'vs/editor/common/services/modelService'; @@ -174,3 +177,48 @@ export class LanguageFeatureRegistry { } } } + + +/** + * Keeps moving average per model and set of providers so that requests + * can be debounce according to the provider performance + */ +export class LanguageFeatureRequestDelays { + + private readonly _cache = new LRUCache(50, 0.7); + + constructor( + private readonly _registry: LanguageFeatureRegistry, + readonly min: number, + readonly max: number = Number.MAX_SAFE_INTEGER, + ) { } + + private _key(model: ITextModel): string { + return model.id + hash(this._registry.all(model)); + } + + private _clamp(value: number | undefined): number { + if (value === undefined) { + return this.min; + } else { + return Math.min(this.max, Math.max(this.min, Math.floor(value * 1.3))); + } + } + + get(model: ITextModel): number { + const key = this._key(model); + const avg = this._cache.get(key); + return this._clamp(avg?.value); + } + + update(model: ITextModel, value: number): number { + const key = this._key(model); + let avg = this._cache.get(key); + if (!avg) { + avg = new MovingAverage(); + this._cache.set(key, avg); + } + avg.update(value); + return this.get(model); + } +} diff --git a/src/vs/editor/common/modes/supports/tokenization.ts b/src/vs/editor/common/modes/supports/tokenization.ts index 7108d36ae9..9183636441 100644 --- a/src/vs/editor/common/modes/supports/tokenization.ts +++ b/src/vs/editor/common/modes/supports/tokenization.ts @@ -395,14 +395,14 @@ export class ThemeTrieElement { } } -export function generateTokensCSSForColorMap(colorMap: Color[]): string { +export function generateTokensCSSForColorMap(colorMap: readonly Color[]): string { let rules: string[] = []; for (let i = 1, len = colorMap.length; i < len; i++) { let color = colorMap[i]; - rules[i] = `.monaco-editor .mtk${i} { color: ${color}; }`; + rules[i] = `.mtk${i} { color: ${color}; }`; } - rules.push('.monaco-editor .mtki { font-style: italic; }'); - rules.push('.monaco-editor .mtkb { font-weight: bold; }'); - rules.push('.monaco-editor .mtku { text-decoration: underline; text-underline-position: under; }'); + rules.push('.mtki { font-style: italic; }'); + rules.push('.mtkb { font-weight: bold; }'); + rules.push('.mtku { text-decoration: underline; text-underline-position: under; }'); return rules.join('\n'); } diff --git a/src/vs/editor/common/services/markerDecorationsServiceImpl.ts b/src/vs/editor/common/services/markerDecorationsServiceImpl.ts index 75e8d33fb3..c7db9b5755 100644 --- a/src/vs/editor/common/services/markerDecorationsServiceImpl.ts +++ b/src/vs/editor/common/services/markerDecorationsServiceImpl.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IMarkerService, IMarker, MarkerSeverity, MarkerTag } from 'vs/platform/markers/common/markers'; -import { Disposable, toDisposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IModelDeltaDecoration, ITextModel, IModelDecorationOptions, TrackedRangeStickiness, OverviewRulerLane, IModelDecoration, MinimapPosition, IModelDecorationMinimapOptions } from 'vs/editor/common/model'; import { ClassName } from 'vs/editor/common/model/intervalTree'; @@ -16,7 +16,6 @@ import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDeco import { Schemas } from 'vs/base/common/network'; import { Emitter, Event } from 'vs/base/common/event'; import { minimapWarning, minimapError } from 'vs/platform/theme/common/colorRegistry'; -import { Delayer } from 'vs/base/common/async'; function MODEL_ID(resource: URI): string { return resource.toString(); @@ -36,10 +35,6 @@ class MarkerDecorations extends Disposable { })); } - register(t: T): T { - return super._register(t); - } - public update(markers: IMarker[], newDecorations: IModelDeltaDecoration[]): boolean { const oldIds = [...this._markersData.keys()]; this._markersData.clear(); @@ -114,8 +109,6 @@ export class MarkerDecorationsService extends Disposable implements IMarkerDecor private _onModelAdded(model: ITextModel): void { const markerDecorations = new MarkerDecorations(model); this._markerDecorations.set(MODEL_ID(model.uri), markerDecorations); - const delayer = markerDecorations.register(new Delayer(100)); - markerDecorations.register(model.onDidChangeContent(() => delayer.trigger(() => this._updateDecorations(markerDecorations)))); this._updateDecorations(markerDecorations); } diff --git a/src/vs/editor/common/view/viewContext.ts b/src/vs/editor/common/view/viewContext.ts index 9e5a5bbad8..e40b7dc73a 100644 --- a/src/vs/editor/common/view/viewContext.ts +++ b/src/vs/editor/common/view/viewContext.ts @@ -6,15 +6,16 @@ import { IConfiguration } from 'vs/editor/common/editorCommon'; import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler'; import { IViewLayout, IViewModel } from 'vs/editor/common/viewModel/viewModel'; -import { IColorTheme, ThemeType } from 'vs/platform/theme/common/themeService'; +import { IColorTheme } from 'vs/platform/theme/common/themeService'; import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; import { Color } from 'vs/base/common/color'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; export class EditorTheme { private _theme: IColorTheme; - public get type(): ThemeType { + public get type(): ColorScheme { return this._theme.type; } diff --git a/src/vs/editor/contrib/codelens/codelens.ts b/src/vs/editor/contrib/codelens/codelens.ts index 8ad18a40bd..43d5e27634 100644 --- a/src/vs/editor/contrib/codelens/codelens.ts +++ b/src/vs/editor/contrib/codelens/codelens.ts @@ -36,44 +36,47 @@ export class CodeLensModel { } } -export function getCodeLensData(model: ITextModel, token: CancellationToken): Promise { +export async function getCodeLensModel(model: ITextModel, token: CancellationToken): Promise { const provider = CodeLensProviderRegistry.ordered(model); const providerRanks = new Map(); const result = new CodeLensModel(); - const promises = provider.map((provider, i) => { + const promises = provider.map(async (provider, i) => { providerRanks.set(provider, i); - return Promise.resolve(provider.provideCodeLenses(model, token)) - .then(list => list && result.add(list, provider)) - .catch(onUnexpectedExternalError); - }); - - return Promise.all(promises).then(() => { - - result.lenses = mergeSort(result.lenses, (a, b) => { - // sort by lineNumber, provider-rank, and column - if (a.symbol.range.startLineNumber < b.symbol.range.startLineNumber) { - return -1; - } else if (a.symbol.range.startLineNumber > b.symbol.range.startLineNumber) { - return 1; - } else if (providerRanks.get(a.provider)! < providerRanks.get(b.provider)!) { - return -1; - } else if (providerRanks.get(a.provider)! > providerRanks.get(b.provider)!) { - return 1; - } else if (a.symbol.range.startColumn < b.symbol.range.startColumn) { - return -1; - } else if (a.symbol.range.startColumn > b.symbol.range.startColumn) { - return 1; - } else { - return 0; + try { + const list = await Promise.resolve(provider.provideCodeLenses(model, token)); + if (list) { + result.add(list, provider); } - }); - - return result; + } catch (err) { + onUnexpectedExternalError(err); + } }); + + await Promise.all(promises); + + result.lenses = mergeSort(result.lenses, (a, b) => { + // sort by lineNumber, provider-rank, and column + if (a.symbol.range.startLineNumber < b.symbol.range.startLineNumber) { + return -1; + } else if (a.symbol.range.startLineNumber > b.symbol.range.startLineNumber) { + return 1; + } else if ((providerRanks.get(a.provider)!) < (providerRanks.get(b.provider)!)) { + return -1; + } else if ((providerRanks.get(a.provider)!) > (providerRanks.get(b.provider)!)) { + return 1; + } else if (a.symbol.range.startColumn < b.symbol.range.startColumn) { + return -1; + } else if (a.symbol.range.startColumn > b.symbol.range.startColumn) { + return 1; + } else { + return 0; + } + }); + return result; } registerLanguageCommand('_executeCodeLensProvider', function (accessor, args) { @@ -90,7 +93,7 @@ registerLanguageCommand('_executeCodeLensProvider', function (accessor, args) { const result: CodeLens[] = []; const disposables = new DisposableStore(); - return getCodeLensData(model, CancellationToken.None).then(value => { + return getCodeLensModel(model, CancellationToken.None).then(value => { disposables.add(value); let resolve: Promise[] = []; diff --git a/src/vs/editor/contrib/codelens/codelensController.ts b/src/vs/editor/contrib/codelens/codelensController.ts index a14518771b..f31d7dffbb 100644 --- a/src/vs/editor/contrib/codelens/codelensController.ts +++ b/src/vs/editor/contrib/codelens/codelensController.ts @@ -5,14 +5,14 @@ import { CancelablePromise, RunOnceScheduler, createCancelablePromise, disposableTimeout } from 'vs/base/common/async'; import { onUnexpectedError, onUnexpectedExternalError } from 'vs/base/common/errors'; -import { toDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; +import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { StableEditorScrollState } from 'vs/editor/browser/core/editorState'; import { ICodeEditor, MouseTargetType, IViewZoneChangeAccessor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution, ServicesAccessor, registerEditorAction, EditorAction } from 'vs/editor/browser/editorExtensions'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { IModelDecorationsChangeAccessor } from 'vs/editor/common/model'; import { CodeLensProviderRegistry, CodeLens, Command } from 'vs/editor/common/modes'; -import { CodeLensModel, getCodeLensData, CodeLensItem } from 'vs/editor/contrib/codelens/codelens'; +import { CodeLensModel, getCodeLensModel, CodeLensItem } from 'vs/editor/contrib/codelens/codelens'; import { CodeLensWidget, CodeLensHelper } from 'vs/editor/contrib/codelens/codelensWidget'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -23,24 +23,25 @@ import { hash } from 'vs/base/common/hash'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { localize } from 'vs/nls'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { LanguageFeatureRequestDelays } from 'vs/editor/common/modes/languageFeatureRegistry'; export class CodeLensContribution implements IEditorContribution { - public static readonly ID: string = 'css.editor.codeLens'; + static readonly ID: string = 'css.editor.codeLens'; - private _isEnabled: boolean; - - private readonly _globalToDispose = new DisposableStore(); + private readonly _disposables = new DisposableStore(); private readonly _localToDispose = new DisposableStore(); private readonly _styleElement: HTMLStyleElement; private readonly _styleClassName: string; - private _lenses: CodeLensWidget[] = []; - private _currentFindCodeLensSymbolsPromise: CancelablePromise | undefined; + private readonly _lenses: CodeLensWidget[] = []; + + private readonly _getCodeLensModelDelays = new LanguageFeatureRequestDelays(CodeLensProviderRegistry, 250, 2500); + private _getCodeLensModelPromise: CancelablePromise | undefined; private _oldCodeLensModels = new DisposableStore(); private _currentCodeLensModel: CodeLensModel | undefined; - private _modelChangeCounter: number = 0; - private _currentResolveCodeLensSymbolsPromise: CancelablePromise | undefined; - private _detectVisibleLenses: RunOnceScheduler | undefined; + private readonly _resolveCodeLensesDelays = new LanguageFeatureRequestDelays(CodeLensProviderRegistry, 250, 2500); + private readonly _resolveCodeLensesScheduler = new RunOnceScheduler(() => this._resolveCodeLensesInViewport(), this._resolveCodeLensesDelays.min); + private _resolveCodeLensesPromise: CancelablePromise | undefined; constructor( private readonly _editor: ICodeEditor, @@ -48,23 +49,18 @@ export class CodeLensContribution implements IEditorContribution { @INotificationService private readonly _notificationService: INotificationService, @ICodeLensCache private readonly _codeLensCache: ICodeLensCache ) { - this._isEnabled = this._editor.getOption(EditorOption.codeLens); - this._globalToDispose.add(this._editor.onDidChangeModel(() => this._onModelChange())); - this._globalToDispose.add(this._editor.onDidChangeModelLanguage(() => this._onModelChange())); - this._globalToDispose.add(this._editor.onDidChangeConfiguration(() => { - const prevIsEnabled = this._isEnabled; - this._isEnabled = this._editor.getOption(EditorOption.codeLens); - if (prevIsEnabled !== this._isEnabled) { - this._onModelChange(); - } - })); - this._globalToDispose.add(CodeLensProviderRegistry.onDidChange(this._onModelChange, this)); - this._globalToDispose.add(this._editor.onDidChangeConfiguration(e => { + this._disposables.add(this._editor.onDidChangeModel(() => this._onModelChange())); + this._disposables.add(this._editor.onDidChangeModelLanguage(() => this._onModelChange())); + this._disposables.add(this._editor.onDidChangeConfiguration((e) => { if (e.hasChanged(EditorOption.fontInfo)) { this._updateLensStyle(); } + if (e.hasChanged(EditorOption.codeLens)) { + this._onModelChange(); + } })); + this._disposables.add(CodeLensProviderRegistry.onDidChange(this._onModelChange, this)); this._onModelChange(); this._styleClassName = '_' + hash(this._editor.getId()).toString(16); @@ -78,9 +74,9 @@ export class CodeLensContribution implements IEditorContribution { dispose(): void { this._localDispose(); - this._globalToDispose.dispose(); + this._disposables.dispose(); this._oldCodeLensModels.dispose(); - dispose(this._currentCodeLensModel); + this._currentCodeLensModel?.dispose(); } private _updateLensStyle(): void { @@ -99,18 +95,13 @@ export class CodeLensContribution implements IEditorContribution { } private _localDispose(): void { - if (this._currentFindCodeLensSymbolsPromise) { - this._currentFindCodeLensSymbolsPromise.cancel(); - this._currentFindCodeLensSymbolsPromise = undefined; - this._modelChangeCounter++; - } - if (this._currentResolveCodeLensSymbolsPromise) { - this._currentResolveCodeLensSymbolsPromise.cancel(); - this._currentResolveCodeLensSymbolsPromise = undefined; - } + this._getCodeLensModelPromise?.cancel(); + this._getCodeLensModelPromise = undefined; + this._resolveCodeLensesPromise?.cancel(); + this._resolveCodeLensesPromise = undefined; this._localToDispose.clear(); this._oldCodeLensModels.clear(); - dispose(this._currentCodeLensModel); + this._currentCodeLensModel?.dispose(); } private _onModelChange(): void { @@ -122,7 +113,7 @@ export class CodeLensContribution implements IEditorContribution { return; } - if (!this._isEnabled) { + if (!this._editor.getOption(EditorOption.codeLens)) { return; } @@ -153,34 +144,34 @@ export class CodeLensContribution implements IEditorContribution { } } - const detectVisibleLenses = this._detectVisibleLenses = new RunOnceScheduler(() => this._onViewportChanged(), 250); - const scheduler = new RunOnceScheduler(() => { - const counterValue = ++this._modelChangeCounter; - if (this._currentFindCodeLensSymbolsPromise) { - this._currentFindCodeLensSymbolsPromise.cancel(); - } + const t1 = Date.now(); - this._currentFindCodeLensSymbolsPromise = createCancelablePromise(token => getCodeLensData(model, token)); + this._getCodeLensModelPromise?.cancel(); + this._getCodeLensModelPromise = createCancelablePromise(token => getCodeLensModel(model, token)); - this._currentFindCodeLensSymbolsPromise.then(result => { - if (counterValue === this._modelChangeCounter) { // only the last one wins - if (this._currentCodeLensModel) { - this._oldCodeLensModels.add(this._currentCodeLensModel); - } - this._currentCodeLensModel = result; - - // cache model to reduce flicker - this._codeLensCache.put(model, result); - - // render lenses - this._renderCodeLensSymbols(result); - detectVisibleLenses.schedule(); + this._getCodeLensModelPromise.then(result => { + if (this._currentCodeLensModel) { + this._oldCodeLensModels.add(this._currentCodeLensModel); } + this._currentCodeLensModel = result; + + // cache model to reduce flicker + this._codeLensCache.put(model, result); + + // update moving average + const newDelay = this._getCodeLensModelDelays.update(model, Date.now() - t1); + scheduler.delay = newDelay; + + // render lenses + this._renderCodeLensSymbols(result); + this._resolveCodeLensesInViewportSoon(); }, onUnexpectedError); - }, 250); + + }, this._getCodeLensModelDelays.get(model)); + this._localToDispose.add(scheduler); - this._localToDispose.add(detectVisibleLenses); + this._localToDispose.add(toDisposable(() => this._resolveCodeLensesScheduler.cancel())); this._localToDispose.add(this._editor.onDidChangeModelContent(() => { this._editor.changeDecorations(decorationsAccessor => { this._editor.changeViewZones(viewZonesAccessor => { @@ -209,17 +200,17 @@ export class CodeLensContribution implements IEditorContribution { }); // Compute new `visible` code lenses - detectVisibleLenses.schedule(); + this._resolveCodeLensesInViewportSoon(); // Ask for all references again scheduler.schedule(); })); this._localToDispose.add(this._editor.onDidScrollChange(e => { if (e.scrollTopChanged && this._lenses.length > 0) { - detectVisibleLenses.schedule(); + this._resolveCodeLensesInViewportSoon(); } })); this._localToDispose.add(this._editor.onDidLayoutChange(() => { - detectVisibleLenses.schedule(); + this._resolveCodeLensesInViewportSoon(); })); this._localToDispose.add(toDisposable(() => { if (this._editor.getModel()) { @@ -264,7 +255,7 @@ export class CodeLensContribution implements IEditorContribution { if (decChangeAccessor) { helper.commit(decChangeAccessor); } - this._lenses = []; + this._lenses.length = 0; } private _renderCodeLensSymbols(symbols: CodeLensModel): void { @@ -313,7 +304,7 @@ export class CodeLensContribution implements IEditorContribution { groupsIndex++; codeLensIndex++; } else { - this._lenses.splice(codeLensIndex, 0, new CodeLensWidget(groups[groupsIndex], this._editor, this._styleClassName, helper, viewZoneAccessor, () => this._detectVisibleLenses && this._detectVisibleLenses.schedule())); + this._lenses.splice(codeLensIndex, 0, new CodeLensWidget(groups[groupsIndex], this._editor, this._styleClassName, helper, viewZoneAccessor, () => this._resolveCodeLensesInViewportSoon())); codeLensIndex++; groupsIndex++; } @@ -327,7 +318,7 @@ export class CodeLensContribution implements IEditorContribution { // Create extra symbols while (groupsIndex < groups.length) { - this._lenses.push(new CodeLensWidget(groups[groupsIndex], this._editor, this._styleClassName, helper, viewZoneAccessor, () => this._detectVisibleLenses && this._detectVisibleLenses.schedule())); + this._lenses.push(new CodeLensWidget(groups[groupsIndex], this._editor, this._styleClassName, helper, viewZoneAccessor, () => this._resolveCodeLensesInViewportSoon())); groupsIndex++; } @@ -338,11 +329,17 @@ export class CodeLensContribution implements IEditorContribution { scrollState.restore(this._editor); } - private _onViewportChanged(): void { - if (this._currentResolveCodeLensSymbolsPromise) { - this._currentResolveCodeLensSymbolsPromise.cancel(); - this._currentResolveCodeLensSymbolsPromise = undefined; + private _resolveCodeLensesInViewportSoon(): void { + const model = this._editor.getModel(); + if (model) { + this._resolveCodeLensesScheduler.schedule(); } + } + + private _resolveCodeLensesInViewport(): void { + + this._resolveCodeLensesPromise?.cancel(); + this._resolveCodeLensesPromise = undefined; const model = this._editor.getModel(); if (!model) { @@ -363,6 +360,8 @@ export class CodeLensContribution implements IEditorContribution { return; } + const t1 = Date.now(); + const resolvePromise = createCancelablePromise(token => { const promises = toResolve.map((request, i) => { @@ -388,20 +387,25 @@ export class CodeLensContribution implements IEditorContribution { return Promise.all(promises); }); - this._currentResolveCodeLensSymbolsPromise = resolvePromise; + this._resolveCodeLensesPromise = resolvePromise; + + this._resolveCodeLensesPromise.then(() => { + + // update moving average + const newDelay = this._resolveCodeLensesDelays.update(model, Date.now() - t1); + this._resolveCodeLensesScheduler.delay = newDelay; - this._currentResolveCodeLensSymbolsPromise.then(() => { if (this._currentCodeLensModel) { // update the cached state with new resolved items this._codeLensCache.put(model, this._currentCodeLensModel); } this._oldCodeLensModels.clear(); // dispose old models once we have updated the UI with the current model - if (resolvePromise === this._currentResolveCodeLensSymbolsPromise) { - this._currentResolveCodeLensSymbolsPromise = undefined; + if (resolvePromise === this._resolveCodeLensesPromise) { + this._resolveCodeLensesPromise = undefined; } }, err => { onUnexpectedError(err); // can also be cancellation! - if (resolvePromise === this._currentResolveCodeLensSymbolsPromise) { - this._currentResolveCodeLensSymbolsPromise = undefined; + if (resolvePromise === this._resolveCodeLensesPromise) { + this._resolveCodeLensesPromise = undefined; } }); } diff --git a/src/vs/editor/contrib/codelens/codelensWidget.ts b/src/vs/editor/contrib/codelens/codelensWidget.ts index 4bdbaeb2bd..4044dc8d13 100644 --- a/src/vs/editor/contrib/codelens/codelensWidget.ts +++ b/src/vs/editor/contrib/codelens/codelensWidget.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./codelensWidget'; -import { renderCodicons } from 'vs/base/common/codicons'; -import { escape } from 'vs/base/common/strings'; +import * as dom from 'vs/base/browser/dom'; import { IViewZone, IContentWidget, IActiveCodeEditor, IContentWidgetPosition, ContentWidgetPositionPreference, IViewZoneChangeAccessor } from 'vs/editor/browser/editorBrowser'; import { Range } from 'vs/editor/common/core/range'; import { IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; @@ -15,6 +14,7 @@ import { editorCodeLensForeground } from 'vs/editor/common/view/editorColorRegis import { CodeLensItem } from 'vs/editor/contrib/codelens/codelens'; import { editorActiveLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { renderCodicons } from 'vs/base/browser/codicons'; class CodeLensViewZone implements IViewZone { @@ -79,7 +79,7 @@ class CodeLensContentWidget implements IContentWidget { withCommands(lenses: Array, animate: boolean): void { this._commands.clear(); - let innerHtml = ''; + let children: HTMLElement[] = []; let hasSymbol = false; for (let i = 0; i < lenses.length; i++) { const lens = lenses[i]; @@ -88,29 +88,26 @@ class CodeLensContentWidget implements IContentWidget { } hasSymbol = true; if (lens.command) { - const title = renderCodicons(escape(lens.command.title)); + const title = renderCodicons(lens.command.title); if (lens.command.id) { - innerHtml += `${title}`; + children.push(dom.$('a', { id: String(i) }, ...title)); this._commands.set(String(i), lens.command); } else { - innerHtml += `${title}`; + children.push(dom.$('span', undefined, ...title)); } if (i + 1 < lenses.length) { - innerHtml += ' | '; + children.push(dom.$('span', undefined, '\u00a0|\u00a0')); } } } if (!hasSymbol) { // symbols but no commands - this._domNode.innerHTML = 'no commands'; + dom.reset(this._domNode, dom.$('span', undefined, 'no commands')); } else { // symbols and commands - if (!innerHtml) { - innerHtml = '\u00a0'; - } - this._domNode.innerHTML = innerHtml; + dom.reset(this._domNode, ...children); if (this._isEmpty && animate) { this._domNode.classList.add('fadein'); } diff --git a/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts b/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts index a38a3de9bd..88b19ecd30 100644 --- a/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts +++ b/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts @@ -47,12 +47,12 @@ export class ColorPickerHeader extends Disposable { this._register(model.onDidChangeColor(this.onDidChangeColor, this)); this._register(model.onDidChangePresentation(this.onDidChangePresentation, this)); this.pickedColorNode.style.backgroundColor = Color.Format.CSS.format(model.color) || ''; - dom.toggleClass(this.pickedColorNode, 'light', model.color.rgba.a < 0.5 ? this.backgroundColor.isLighter() : model.color.isLighter()); + this.pickedColorNode.classList.toggle('light', model.color.rgba.a < 0.5 ? this.backgroundColor.isLighter() : model.color.isLighter()); } private onDidChangeColor(color: Color): void { this.pickedColorNode.style.backgroundColor = Color.Format.CSS.format(color) || ''; - dom.toggleClass(this.pickedColorNode, 'light', color.rgba.a < 0.5 ? this.backgroundColor.isLighter() : color.isLighter()); + this.pickedColorNode.classList.toggle('light', color.rgba.a < 0.5 ? this.backgroundColor.isLighter() : color.isLighter()); this.onDidChangePresentation(); } @@ -264,7 +264,7 @@ abstract class Strip extends Disposable { private onMouseDown(e: MouseEvent): void { const monitor = this._register(new GlobalMouseMoveMonitor()); const origin = dom.getDomNodePagePosition(this.domNode); - dom.addClass(this.domNode, 'grabbing'); + this.domNode.classList.add('grabbing'); if (e.target !== this.slider) { this.onDidChangeTop(e.offsetY); @@ -276,7 +276,7 @@ abstract class Strip extends Disposable { this._onColorFlushed.fire(); mouseUpListener.dispose(); monitor.stopMonitoring(true); - dom.removeClass(this.domNode, 'grabbing'); + this.domNode.classList.remove('grabbing'); }, true); } @@ -298,7 +298,7 @@ class OpacityStrip extends Strip { constructor(container: HTMLElement, model: ColorPickerModel) { super(container, model); - dom.addClass(this.domNode, 'opacity-strip'); + this.domNode.classList.add('opacity-strip'); this._register(model.onDidChangeColor(this.onDidChangeColor, this)); this.onDidChangeColor(this.model.color); @@ -321,7 +321,7 @@ class HueStrip extends Strip { constructor(container: HTMLElement, model: ColorPickerModel) { super(container, model); - dom.addClass(this.domNode, 'hue-strip'); + this.domNode.classList.add('hue-strip'); } protected getValue(color: Color): number { diff --git a/src/vs/editor/contrib/documentSymbols/outlineModel.ts b/src/vs/editor/contrib/documentSymbols/outlineModel.ts index e85ac10405..56c6085324 100644 --- a/src/vs/editor/contrib/documentSymbols/outlineModel.ts +++ b/src/vs/editor/contrib/documentSymbols/outlineModel.ts @@ -14,8 +14,8 @@ import { ITextModel } from 'vs/editor/common/model'; import { DocumentSymbol, DocumentSymbolProvider, DocumentSymbolProviderRegistry } from 'vs/editor/common/modes'; import { MarkerSeverity } from 'vs/platform/markers/common/markers'; import { Iterable } from 'vs/base/common/iterator'; -import { MovingAverage } from 'vs/base/common/numbers'; import { URI } from 'vs/base/common/uri'; +import { LanguageFeatureRequestDelays } from 'vs/editor/common/modes/languageFeatureRegistry'; export abstract class TreeElement { @@ -208,7 +208,7 @@ export class OutlineGroup extends TreeElement { export class OutlineModel extends TreeElement { - private static readonly _requestDurations = new LRUCache(50, 0.7); + private static readonly _requestDurations = new LanguageFeatureRequestDelays(DocumentSymbolProviderRegistry, 350); private static readonly _requests = new LRUCache, model: OutlineModel | undefined }>(9, 0.75); private static readonly _keys = new class { @@ -252,13 +252,7 @@ export class OutlineModel extends TreeElement { // keep moving average of request durations const now = Date.now(); data.promise.then(() => { - let key = this._keys.for(textModel, false); - let avg = this._requestDurations.get(key); - if (!avg) { - avg = new MovingAverage(); - this._requestDurations.set(key, avg); - } - avg.update(Date.now() - now); + this._requestDurations.update(textModel, Date.now() - now); }); } @@ -290,14 +284,7 @@ export class OutlineModel extends TreeElement { } static getRequestDelay(textModel: ITextModel | null): number { - if (!textModel) { - return 350; - } - const avg = this._requestDurations.get(this._keys.for(textModel, false)); - if (!avg) { - return 350; - } - return Math.max(350, Math.floor(1.3 * avg.value)); + return textModel ? this._requestDurations.get(textModel) : this._requestDurations.min; } private static _create(textModel: ITextModel, token: CancellationToken): Promise { diff --git a/src/vs/editor/contrib/format/format.ts b/src/vs/editor/contrib/format/format.ts index 78b088cfef..21d8b52e92 100644 --- a/src/vs/editor/contrib/format/format.ts +++ b/src/vs/editor/contrib/format/format.ts @@ -116,7 +116,7 @@ export abstract class FormattingConflicts { if (selector) { return await selector(formatter, document, mode); } - return formatter[0]; + return undefined; } } diff --git a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts index d3c39c21d9..fd02064d77 100644 --- a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts +++ b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts @@ -57,7 +57,7 @@ class MessageWidget { domNode.className = 'descriptioncontainer'; this._messageBlock = document.createElement('div'); - dom.addClass(this._messageBlock, 'message'); + this._messageBlock.classList.add('message'); this._messageBlock.setAttribute('aria-live', 'assertive'); this._messageBlock.setAttribute('role', 'alert'); domNode.appendChild(this._messageBlock); @@ -123,19 +123,19 @@ class MessageWidget { } if (source || code) { const detailsElement = document.createElement('span'); - dom.addClass(detailsElement, 'details'); + detailsElement.classList.add('details'); lastLineElement.appendChild(detailsElement); if (source) { const sourceElement = document.createElement('span'); sourceElement.innerText = source; - dom.addClass(sourceElement, 'source'); + sourceElement.classList.add('source'); detailsElement.appendChild(sourceElement); } if (code) { if (typeof code === 'string') { const codeElement = document.createElement('span'); codeElement.innerText = `(${code})`; - dom.addClass(codeElement, 'code'); + codeElement.classList.add('code'); detailsElement.appendChild(codeElement); } else { this._codeLink = dom.$('a.code-link'); @@ -166,7 +166,7 @@ class MessageWidget { let container = document.createElement('div'); let relatedResource = document.createElement('a'); - dom.addClass(relatedResource, 'filename'); + relatedResource.classList.add('filename'); relatedResource.innerText = `${getBaseLabel(related.resource)}(${related.startLineNumber}, ${related.startColumn}): `; relatedResource.title = getPathLabel(related.resource, undefined); this._relatedDiagnostics.set(relatedResource, related); @@ -318,7 +318,7 @@ export class MarkerNavigationWidget extends PeekViewWidget { protected _fillBody(container: HTMLElement): void { this._parentContainer = container; - dom.addClass(container, 'marker-widget'); + container.classList.add('marker-widget'); this._parentContainer.tabIndex = 0; this._parentContainer.setAttribute('role', 'tooltip'); diff --git a/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts b/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts index 7a80f845ee..d6c7342f0c 100644 --- a/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts +++ b/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { dispose, DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IContextKey, IContextKeyService, RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -64,8 +64,8 @@ export abstract class ReferencesController implements IEditorContribution { dispose(): void { this._referenceSearchVisible.reset(); this._disposables.dispose(); - dispose(this._widget); - dispose(this._model); + this._widget?.dispose(); + this._model?.dispose(); this._widget = undefined; this._model = undefined; } @@ -226,8 +226,8 @@ export abstract class ReferencesController implements IEditorContribution { } closeWidget(focusEditor = true): void { - dispose(this._widget); - dispose(this._model); + this._widget?.dispose(); + this._model?.dispose(); this._referenceSearchVisible.reset(); this._disposables.clear(); this._widget = undefined; diff --git a/src/vs/editor/contrib/gotoSymbol/symbolNavigation.ts b/src/vs/editor/contrib/gotoSymbol/symbolNavigation.ts index 9a1c11b5d0..e046aee7b5 100644 --- a/src/vs/editor/contrib/gotoSymbol/symbolNavigation.ts +++ b/src/vs/editor/contrib/gotoSymbol/symbolNavigation.ts @@ -55,8 +55,8 @@ class SymbolNavigationService implements ISymbolNavigationService { reset(): void { this._ctxHasSymbols.reset(); - dispose(this._currentState); - dispose(this._currentMessage); + this._currentState?.dispose(); + this._currentMessage?.dispose(); this._currentModel = undefined; this._currentIdx = -1; } @@ -138,7 +138,7 @@ class SymbolNavigationService implements ISymbolNavigationService { private _showMessage(): void { - dispose(this._currentMessage); + this._currentMessage?.dispose(); const kb = this._keybindingService.lookupKeybinding('editor.gotoNextSymbolFromResult'); const message = kb @@ -209,7 +209,7 @@ class EditorState { } private _onDidRemoveEditor(editor: ICodeEditor): void { - dispose(this._listener.get(editor)); + this._listener.get(editor)?.dispose(); this._listener.delete(editor); } } diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index 256be275ea..6c4a177164 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -240,11 +240,11 @@ export class ModesContentHoverWidget extends ContentHoverWidget { this._register(dom.addStandardDisposableListener(this.getDomNode(), dom.EventType.FOCUS, () => { if (this._colorPicker) { - dom.addClass(this.getDomNode(), 'colorpicker-hover'); + this.getDomNode().classList.add('colorpicker-hover'); } })); this._register(dom.addStandardDisposableListener(this.getDomNode(), dom.EventType.BLUR, () => { - dom.removeClass(this.getDomNode(), 'colorpicker-hover'); + this.getDomNode().classList.remove('colorpicker-hover'); })); this._register(editor.onDidChangeConfiguration((e) => { this._hoverOperation.setHoverTime(this._editor.getOption(EditorOption.hover).delay); diff --git a/src/vs/editor/contrib/links/getLinks.ts b/src/vs/editor/contrib/links/getLinks.ts index 390b134599..63ac4887a1 100644 --- a/src/vs/editor/contrib/links/getLinks.ts +++ b/src/vs/editor/contrib/links/getLinks.ts @@ -13,6 +13,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { isDisposable, Disposable } from 'vs/base/common/lifecycle'; import { coalesce } from 'vs/base/common/arrays'; +import { assertType } from 'vs/base/common/types'; export class Link implements ILink { @@ -152,10 +153,13 @@ export function getLinks(model: ITextModel, token: CancellationToken): Promise => { - const [uri] = args; - if (!(uri instanceof URI)) { - return []; + let [uri, resolveCount] = args; + assertType(uri instanceof URI); + + if (typeof resolveCount !== 'number') { + resolveCount = 0; } + const model = accessor.get(IModelService).getModel(uri); if (!model) { return []; @@ -164,6 +168,12 @@ CommandsRegistry.registerCommand('_executeLinkProvider', async (accessor, ...arg if (!list) { return []; } + + // resolve links + for (let i = 0; i < Math.min(resolveCount, list.links.length); i++) { + await list.links[i].resolve(CancellationToken.None); + } + const result = list.links.slice(0); list.dispose(); return result; diff --git a/src/vs/editor/contrib/message/messageController.ts b/src/vs/editor/contrib/message/messageController.ts index 5b0957122c..6e5f99307f 100644 --- a/src/vs/editor/contrib/message/messageController.ts +++ b/src/vs/editor/contrib/message/messageController.ts @@ -15,9 +15,10 @@ import { registerEditorContribution, EditorCommand, registerEditorCommand } from import { ICodeEditor, IContentWidget, IContentWidgetPosition, ContentWidgetPositionPreference } from 'vs/editor/browser/editorBrowser'; import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IPosition } from 'vs/editor/common/core/position'; -import { registerThemingParticipant, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { inputValidationInfoBorder, inputValidationInfoBackground, inputValidationInfoForeground } from 'vs/platform/theme/common/colorRegistry'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; export class MessageController extends Disposable implements IEditorContribution { @@ -184,7 +185,7 @@ registerEditorContribution(MessageController.ID, MessageController); registerThemingParticipant((theme, collector) => { const border = theme.getColor(inputValidationInfoBorder); if (border) { - let borderWidth = theme.type === HIGH_CONTRAST ? 2 : 1; + let borderWidth = theme.type === ColorScheme.HIGH_CONTRAST ? 2 : 1; collector.addRule(`.monaco-editor .monaco-editor-overlaymessage .anchor { border-top-color: ${border}; }`); collector.addRule(`.monaco-editor .monaco-editor-overlaymessage .message { border: ${borderWidth}px solid ${border}; }`); } diff --git a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts index b95350cbff..0340359c4b 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts +++ b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts @@ -20,11 +20,12 @@ import * as nls from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { editorHoverBackground, editorHoverBorder, textCodeBlockBackground, textLinkForeground, editorHoverForeground } from 'vs/platform/theme/common/colorRegistry'; -import { HIGH_CONTRAST, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ParameterHintsModel, TriggerContext } from 'vs/editor/contrib/parameterHints/parameterHintsModel'; import { pad } from 'vs/base/common/strings'; import { registerIcon, Codicon } from 'vs/base/common/codicons'; import { assertIsDefined } from 'vs/base/common/types'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; const $ = dom.$; @@ -364,7 +365,7 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget { registerThemingParticipant((theme, collector) => { const border = theme.getColor(editorHoverBorder); if (border) { - const borderWidth = theme.type === HIGH_CONTRAST ? 2 : 1; + const borderWidth = theme.type === ColorScheme.HIGH_CONTRAST ? 2 : 1; collector.addRule(`.monaco-editor .parameter-hints-widget { border: ${borderWidth}px solid ${border}; }`); collector.addRule(`.monaco-editor .parameter-hints-widget.multiple .body { border-left: 1px solid ${border.transparent(0.5)}; }`); collector.addRule(`.monaco-editor .parameter-hints-widget .signature.has-docs { border-bottom: 1px solid ${border.transparent(0.5)}; }`); diff --git a/src/vs/editor/contrib/parameterHints/test/parameterHintsModel.test.ts b/src/vs/editor/contrib/parameterHints/test/parameterHintsModel.test.ts index 49abcecad5..474707b4b3 100644 --- a/src/vs/editor/contrib/parameterHints/test/parameterHintsModel.test.ts +++ b/src/vs/editor/contrib/parameterHints/test/parameterHintsModel.test.ts @@ -290,7 +290,7 @@ suite('ParameterHintsModel', () => { hintsModel.trigger({ triggerKind: modes.SignatureHelpTriggerKind.Invoke }, 0); assert.strictEqual(-1, didRequestCancellationOf); - return new Promise((resolve, reject) => + return new Promise((resolve, reject) => hintsModel.onChangedHints(newParamterHints => { try { assert.strictEqual(0, didRequestCancellationOf); diff --git a/src/vs/editor/contrib/rename/test/onTypeRename.test.ts b/src/vs/editor/contrib/rename/test/onTypeRename.test.ts index 8d23f679e9..3635af7f4d 100644 --- a/src/vs/editor/contrib/rename/test/onTypeRename.test.ts +++ b/src/vs/editor/contrib/rename/test/onTypeRename.test.ts @@ -104,7 +104,7 @@ suite('On type rename', () => { await operations(testEditor); - return new Promise((resolve) => { + return new Promise((resolve) => { setTimeout(() => { if (typeof expectedEndText === 'string') { assert.equal(editor.getModel()!.getValue(), expectedEndText); diff --git a/src/vs/editor/contrib/smartSelect/bracketSelections.ts b/src/vs/editor/contrib/smartSelect/bracketSelections.ts index f4ec413c53..e5b8a52684 100644 --- a/src/vs/editor/contrib/smartSelect/bracketSelections.ts +++ b/src/vs/editor/contrib/smartSelect/bracketSelections.ts @@ -19,8 +19,8 @@ export class BracketSelectionRangeProvider implements SelectionRangeProvider { result.push(bucket); const ranges = new Map>(); - await new Promise(resolve => BracketSelectionRangeProvider._bracketsRightYield(resolve, 0, model, position, ranges)); - await new Promise(resolve => BracketSelectionRangeProvider._bracketsLeftYield(resolve, 0, model, position, ranges, bucket)); + await new Promise(resolve => BracketSelectionRangeProvider._bracketsRightYield(resolve, 0, model, position, ranges)); + await new Promise(resolve => BracketSelectionRangeProvider._bracketsLeftYield(resolve, 0, model, position, ranges, bucket)); } return result; diff --git a/src/vs/editor/contrib/smartSelect/smartSelect.ts b/src/vs/editor/contrib/smartSelect/smartSelect.ts index 16a7673496..745618006c 100644 --- a/src/vs/editor/contrib/smartSelect/smartSelect.ts +++ b/src/vs/editor/contrib/smartSelect/smartSelect.ts @@ -18,7 +18,7 @@ import * as modes from 'vs/editor/common/modes'; import * as nls from 'vs/nls'; import { MenuId } from 'vs/platform/actions/common/actions'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable } from 'vs/base/common/lifecycle'; import { WordSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/wordSelections'; import { BracketSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/bracketSelections'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -64,7 +64,7 @@ class SmartSelectController implements IEditorContribution { } dispose(): void { - dispose(this._selectionListener); + this._selectionListener?.dispose(); } run(forward: boolean): Promise | void { @@ -106,10 +106,10 @@ class SmartSelectController implements IEditorContribution { this._state = ranges.map(ranges => new SelectionRanges(0, ranges)); // listen to caret move and forget about state - dispose(this._selectionListener); + this._selectionListener?.dispose(); this._selectionListener = this._editor.onDidChangeCursorPosition(() => { if (!this._ignoreSelection) { - dispose(this._selectionListener); + this._selectionListener?.dispose(); this._state = undefined; } }); diff --git a/src/vs/editor/contrib/snippet/snippetController2.ts b/src/vs/editor/contrib/snippet/snippetController2.ts index e9eb1ae732..53da911c42 100644 --- a/src/vs/editor/contrib/snippet/snippetController2.ts +++ b/src/vs/editor/contrib/snippet/snippetController2.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { dispose, DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorCommand, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { Range } from 'vs/editor/common/core/range'; @@ -75,7 +75,7 @@ export class SnippetController2 implements IEditorContribution { this._inSnippet.reset(); this._hasPrevTabstop.reset(); this._hasNextTabstop.reset(); - dispose(this._session); + this._session?.dispose(); this._snippetListener.dispose(); } @@ -211,7 +211,7 @@ export class SnippetController2 implements IEditorContribution { this._hasPrevTabstop.reset(); this._hasNextTabstop.reset(); this._snippetListener.clear(); - dispose(this._session); + this._session?.dispose(); this._session = undefined; this._modelVersionId = -1; if (resetSelection) { diff --git a/src/vs/editor/contrib/suggest/media/suggest.css b/src/vs/editor/contrib/suggest/media/suggest.css index 1e4acf9955..57308e0558 100644 --- a/src/vs/editor/contrib/suggest/media/suggest.css +++ b/src/vs/editor/contrib/suggest/media/suggest.css @@ -351,6 +351,7 @@ box-sizing: border-box; height: 100%; width: 100%; + padding-right: 22px; } .monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .header > .type { diff --git a/src/vs/editor/contrib/suggest/suggestAlternatives.ts b/src/vs/editor/contrib/suggest/suggestAlternatives.ts index 7c40d67c04..bba9725b61 100644 --- a/src/vs/editor/contrib/suggest/suggestAlternatives.ts +++ b/src/vs/editor/contrib/suggest/suggestAlternatives.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { CompletionModel } from './completionModel'; @@ -34,7 +34,7 @@ export class SuggestAlternatives { reset(): void { this._ckOtherSuggestions.reset(); - dispose(this._listener); + this._listener?.dispose(); this._model = undefined; this._acceptNext = undefined; this._ignore = false; diff --git a/src/vs/editor/contrib/suggest/suggestController.ts b/src/vs/editor/contrib/suggest/suggestController.ts index 3ec18c4ba0..7b803add91 100644 --- a/src/vs/editor/contrib/suggest/suggestController.ts +++ b/src/vs/editor/contrib/suggest/suggestController.ts @@ -442,7 +442,7 @@ export class SuggestController implements IEditorContribution { private _alertCompletionItem({ completion: suggestion }: CompletionItem): void { const textLabel = typeof suggestion.label === 'string' ? suggestion.label : suggestion.label.name; if (isNonEmptyArray(suggestion.additionalTextEdits)) { - let msg = nls.localize('arai.alert.snippet', "Accepting '{0}' made {1} additional edits", textLabel, suggestion.additionalTextEdits.length); + let msg = nls.localize('aria.alert.snippet', "Accepting '{0}' made {1} additional edits", textLabel, suggestion.additionalTextEdits.length); alert(msg); } } @@ -598,7 +598,8 @@ export class TriggerSuggestAction extends EditorAction { kbOpts: { kbExpr: EditorContextKeys.textInputFocus, primary: KeyMod.CtrlCmd | KeyCode.Space, - mac: { primary: KeyMod.WinCtrl | KeyCode.Space, secondary: [KeyMod.Alt | KeyCode.Escape] }, + secondary: [KeyMod.CtrlCmd | KeyCode.KEY_I], + mac: { primary: KeyMod.WinCtrl | KeyCode.Space, secondary: [KeyMod.Alt | KeyCode.Escape, KeyMod.CtrlCmd | KeyCode.KEY_I] }, weight: KeybindingWeight.EditorContrib } }); diff --git a/src/vs/editor/contrib/suggest/suggestModel.ts b/src/vs/editor/contrib/suggest/suggestModel.ts index 5059ac341a..d60bfb0051 100644 --- a/src/vs/editor/contrib/suggest/suggestModel.ts +++ b/src/vs/editor/contrib/suggest/suggestModel.ts @@ -436,7 +436,7 @@ export class SuggestModel implements IDisposable { Promise.all([completions, wordDistance]).then(async ([completions, wordDistance]) => { - dispose(this._requestToken); + this._requestToken?.dispose(); if (this._state === State.Idle) { return; diff --git a/src/vs/editor/contrib/suggest/wordContextKey.ts b/src/vs/editor/contrib/suggest/wordContextKey.ts index a5f4004377..bb57b377e8 100644 --- a/src/vs/editor/contrib/suggest/wordContextKey.ts +++ b/src/vs/editor/contrib/suggest/wordContextKey.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -29,7 +29,7 @@ export class WordContextKey extends Disposable { dispose(): void { super.dispose(); - dispose(this._selectionListener); + this._selectionListener?.dispose(); this._ckAtEnd.reset(); } diff --git a/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts b/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts index a0949ffe26..b110e64979 100644 --- a/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts +++ b/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts @@ -239,7 +239,7 @@ class WordHighlighter { public moveNext() { let highlights = this._getSortedHighlights(); - let index = arrays.firstIndex(highlights, (range) => range.containsPosition(this.editor.getPosition())); + let index = highlights.findIndex((range) => range.containsPosition(this.editor.getPosition())); let newIndex = ((index + 1) % highlights.length); let dest = highlights[newIndex]; try { @@ -258,7 +258,7 @@ class WordHighlighter { public moveBack() { let highlights = this._getSortedHighlights(); - let index = arrays.firstIndex(highlights, (range) => range.containsPosition(this.editor.getPosition())); + let index = highlights.findIndex((range) => range.containsPosition(this.editor.getPosition())); let newIndex = ((index - 1 + highlights.length) % highlights.length); let dest = highlights[newIndex]; try { diff --git a/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts b/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts index 1151c65d98..56afbc4945 100644 --- a/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts +++ b/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts @@ -20,8 +20,9 @@ import { NULL_STATE, nullTokenize, nullTokenize2 } from 'vs/editor/common/modes/ import { IModeService } from 'vs/editor/common/services/modeService'; import { IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneThemeService'; import { editorHoverBackground, editorHoverBorder, editorHoverForeground } from 'vs/platform/theme/common/colorRegistry'; -import { HIGH_CONTRAST, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { InspectTokensNLS } from 'vs/editor/common/standaloneStrings'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; class InspectTokensController extends Disposable implements IEditorContribution { @@ -332,7 +333,7 @@ registerEditorAction(InspectTokens); registerThemingParticipant((theme, collector) => { const border = theme.getColor(editorHoverBorder); if (border) { - let borderWidth = theme.type === HIGH_CONTRAST ? 2 : 1; + let borderWidth = theme.type === ColorScheme.HIGH_CONTRAST ? 2 : 1; collector.addRule(`.monaco-editor .tokens-inspect-widget { border: ${borderWidth}px solid ${border}; }`); collector.addRule(`.monaco-editor .tokens-inspect-widget .tokens-inspect-separator { background-color: ${border}; }`); } diff --git a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts index 0e4a445609..0ad86f22c9 100644 --- a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts +++ b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts @@ -15,6 +15,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { ColorIdentifier, Extensions, IColorRegistry } from 'vs/platform/theme/common/colorRegistry'; import { Extensions as ThemingExtensions, ICssStyleCollector, IFileIconTheme, IThemingRegistry, ITokenStyle } from 'vs/platform/theme/common/themeService'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; const VS_THEME_NAME = 'vs'; const VS_DARK_THEME_NAME = 'vs-dark'; @@ -107,11 +108,11 @@ class StandaloneTheme implements IStandaloneTheme { return Object.prototype.hasOwnProperty.call(this.getColors(), colorId); } - public get type() { + public get type(): ColorScheme { switch (this.base) { - case VS_THEME_NAME: return 'light'; - case HC_BLACK_THEME_NAME: return 'hc'; - default: return 'dark'; + case VS_THEME_NAME: return ColorScheme.LIGHT; + case HC_BLACK_THEME_NAME: return ColorScheme.HIGH_CONTRAST; + default: return ColorScheme.DARK; } } diff --git a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts index e3d8b6c364..7b8db90102 100644 --- a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts +++ b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts @@ -12,7 +12,8 @@ import { TokenTheme } from 'vs/editor/common/modes/supports/tokenization'; import { ILineTokens, IToken, TokenizationSupport2Adapter, TokensProvider } from 'vs/editor/standalone/browser/standaloneLanguages'; import { IStandaloneTheme, IStandaloneThemeData, IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneThemeService'; import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; -import { IFileIconTheme, IColorTheme, LIGHT, ITokenStyle } from 'vs/platform/theme/common/themeService'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; +import { IFileIconTheme, IColorTheme, ITokenStyle } from 'vs/platform/theme/common/themeService'; suite('TokenizationSupport2Adapter', () => { @@ -46,9 +47,9 @@ suite('TokenizationSupport2Adapter', () => { tokenTheme: new MockTokenTheme(), - themeName: LIGHT, + themeName: ColorScheme.LIGHT, - type: LIGHT, + type: ColorScheme.LIGHT, getColor: (color: ColorIdentifier, useDefault?: boolean): Color => { throw new Error('Not implemented'); diff --git a/src/vs/editor/test/common/modes/supports/characterPair.test.ts b/src/vs/editor/test/common/modes/supports/characterPair.test.ts index 530e716018..fcafb33296 100644 --- a/src/vs/editor/test/common/modes/supports/characterPair.test.ts +++ b/src/vs/editor/test/common/modes/supports/characterPair.test.ts @@ -8,7 +8,6 @@ import { StandardTokenType } from 'vs/editor/common/modes'; import { CharacterPairSupport } from 'vs/editor/common/modes/supports/characterPair'; import { TokenText, createFakeScopedLineTokens } from 'vs/editor/test/common/modesTestUtils'; import { StandardAutoClosingPairConditional } from 'vs/editor/common/modes/languageConfiguration'; -import { find } from 'vs/base/common/arrays'; suite('CharacterPairSupport', () => { @@ -55,7 +54,7 @@ suite('CharacterPairSupport', () => { }); function findAutoClosingPair(characterPairSupport: CharacterPairSupport, character: string): StandardAutoClosingPairConditional | undefined { - return find(characterPairSupport.getAutoClosingPairs(), autoClosingPair => autoClosingPair.open === character); + return characterPairSupport.getAutoClosingPairs().find(autoClosingPair => autoClosingPair.open === character); } function testShouldAutoClose(characterPairSupport: CharacterPairSupport, line: TokenText[], character: string, column: number): boolean { diff --git a/src/vs/loader.js b/src/vs/loader.js index 27fdaec435..79f848e639 100644 --- a/src/vs/loader.js +++ b/src/vs/loader.js @@ -618,8 +618,37 @@ var AMDLoader; }; return OnlyOnceScriptLoader; }()); + var trustedTypesPolyfill = new /** @class */(function () { + function class_1() { + } + class_1.prototype.installIfNeeded = function () { + if (typeof globalThis.trustedTypes !== 'undefined') { + return; // already defined + } + var _defaultRules = { + createHTML: function () { throw new Error('Policy\'s TrustedTypePolicyOptions did not specify a \'createHTML\' member'); }, + createScript: function () { throw new Error('Policy\'s TrustedTypePolicyOptions did not specify a \'createScript\' member'); }, + createScriptURL: function () { throw new Error('Policy\'s TrustedTypePolicyOptions did not specify a \'createScriptURL\' member'); }, + }; + globalThis.trustedTypes = { + createPolicy: function (name, rules) { + var _a, _b, _c; + return { + name: name, + createHTML: (_a = rules.createHTML) !== null && _a !== void 0 ? _a : _defaultRules.createHTML, + createScript: (_b = rules.createScript) !== null && _b !== void 0 ? _b : _defaultRules.createScript, + createScriptURL: (_c = rules.createScriptURL) !== null && _c !== void 0 ? _c : _defaultRules.createScriptURL, + }; + } + }; + }; + return class_1; + }()); + //#endregion var BrowserScriptLoader = /** @class */ (function () { function BrowserScriptLoader() { + // polyfill trustedTypes-support if missing + trustedTypesPolyfill.installIfNeeded(); } /** * Attach load / error listeners to a script element and remove them when either one has fired. @@ -662,6 +691,13 @@ var AMDLoader; script.setAttribute('async', 'async'); script.setAttribute('type', 'text/javascript'); this.attachListeners(script, callback, errorback); + var createTrustedScriptURL = moduleManager.getConfig().getOptionsLiteral().createTrustedScriptURL; + if (createTrustedScriptURL) { + if (!this.scriptSourceURLPolicy) { + this.scriptSourceURLPolicy = trustedTypes.createPolicy('amdLoader', { createScriptURL: createTrustedScriptURL }); + } + scriptSrc = this.scriptSourceURLPolicy.createScriptURL(scriptSrc); + } script.setAttribute('src', scriptSrc); // Propagate CSP nonce to dynamically created script tag. var cspNonce = moduleManager.getConfig().getOptionsLiteral().cspNonce; @@ -675,8 +711,17 @@ var AMDLoader; }()); var WorkerScriptLoader = /** @class */ (function () { function WorkerScriptLoader() { + // polyfill trustedTypes-support if missing + trustedTypesPolyfill.installIfNeeded(); } WorkerScriptLoader.prototype.load = function (moduleManager, scriptSrc, callback, errorback) { + var createTrustedScriptURL = moduleManager.getConfig().getOptionsLiteral().createTrustedScriptURL; + if (createTrustedScriptURL) { + if (!this.scriptSourceURLPolicy) { + this.scriptSourceURLPolicy = trustedTypes.createPolicy('amdLoader', { createScriptURL: createTrustedScriptURL }); + } + scriptSrc = this.scriptSourceURLPolicy.createScriptURL(scriptSrc); + } try { importScripts(scriptSrc); callback(); diff --git a/src/vs/platform/backup/electron-main/backupMainService.ts b/src/vs/platform/backup/electron-main/backupMainService.ts index cfdb3fd5dd..5261b6e900 100644 --- a/src/vs/platform/backup/electron-main/backupMainService.ts +++ b/src/vs/platform/backup/electron-main/backupMainService.ts @@ -10,8 +10,7 @@ import * as platform from 'vs/base/common/platform'; import { writeFileSync, writeFile, readFile, readdir, exists, rimraf, rename, RimRafMode } from 'vs/base/node/pfs'; import { IBackupMainService, IWorkspaceBackupInfo, isWorkspaceBackupInfo } from 'vs/platform/backup/electron-main/backup'; import { IBackupWorkspacesFormat, IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IFilesConfiguration, HotExitConfiguration } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; @@ -43,7 +42,7 @@ export class BackupMainService implements IBackupMainService { @IConfigurationService private readonly configurationService: IConfigurationService, @ILogService private readonly logService: ILogService ) { - this.backupHome = environmentService.backupHome.fsPath; + this.backupHome = environmentService.backupHome; this.workspacesJsonPath = environmentService.backupWorkspacesPath; } diff --git a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts index 28d758f4a1..a17fa3caae 100644 --- a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts +++ b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts @@ -34,7 +34,7 @@ suite('BackupMainService', () => { const backupHome = path.join(parentDir, 'Backups'); const backupWorkspacesPath = path.join(backupHome, 'workspaces.json'); - const environmentService = new EnvironmentService(parseArgs(process.argv, OPTIONS), process.execPath); + const environmentService = new EnvironmentService(parseArgs(process.argv, OPTIONS)); class TestBackupMainService extends BackupMainService { diff --git a/src/vs/platform/configuration/test/common/configurationService.test.ts b/src/vs/platform/configuration/test/common/configurationService.test.ts index 1706a10ae2..a479ad55ca 100644 --- a/src/vs/platform/configuration/test/common/configurationService.test.ts +++ b/src/vs/platform/configuration/test/common/configurationService.test.ts @@ -89,7 +89,7 @@ suite('ConfigurationService', () => { test('trigger configuration change event when file does not exist', async () => { const testObject = disposables.add(new ConfigurationService(settingsResource, fileService)); await testObject.initialize(); - return new Promise(async (c, e) => { + return new Promise(async (c) => { disposables.add(Event.filter(testObject.onDidChangeConfiguration, e => e.source === ConfigurationTarget.USER)(() => { assert.equal(testObject.getValue('foo'), 'bar'); c(); @@ -104,7 +104,7 @@ suite('ConfigurationService', () => { await fileService.writeFile(settingsResource, VSBuffer.fromString('{ "foo": "bar" }')); await testObject.initialize(); - return new Promise((c, e) => { + return new Promise((c) => { disposables.add(Event.filter(testObject.onDidChangeConfiguration, e => e.source === ConfigurationTarget.USER)(async (e) => { assert.equal(testObject.getValue('foo'), 'barz'); c(); diff --git a/src/vs/platform/debug/electron-main/extensionHostDebugIpc.ts b/src/vs/platform/debug/electron-main/extensionHostDebugIpc.ts index 8725455e5d..1b4b99c42a 100644 --- a/src/vs/platform/debug/electron-main/extensionHostDebugIpc.ts +++ b/src/vs/platform/debug/electron-main/extensionHostDebugIpc.ts @@ -101,7 +101,7 @@ export class ElectronExtensionHostDebugBroadcastChannel extends Extens }); }); - await new Promise(r => server.listen(0, r)); + await new Promise(r => server.listen(0, r)); codeWindow.win.on('close', () => server.close()); return { rendererDebugPort: (server.address() as AddressInfo).port }; diff --git a/src/vs/platform/dialogs/electron-browser/dialogIpc.ts b/src/vs/platform/dialogs/electron-browser/dialogIpc.ts deleted file mode 100644 index 5ff5faa824..0000000000 --- a/src/vs/platform/dialogs/electron-browser/dialogIpc.ts +++ /dev/null @@ -1,26 +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 { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { Event } from 'vs/base/common/event'; - -export class DialogChannel implements IServerChannel { - - constructor(@IDialogService private readonly dialogService: IDialogService) { } - - listen(_: unknown, event: string): Event { - throw new Error(`Event not found: ${event}`); - } - - call(_: unknown, command: string, args?: any[]): Promise { - switch (command) { - case 'show': return this.dialogService.show(args![0], args![1], args![2]); - case 'confirm': return this.dialogService.confirm(args![0]); - case 'about': return this.dialogService.about(); - } - return Promise.reject(new Error('invalid command')); - } -} diff --git a/src/vs/platform/driver/browser/baseDriver.ts b/src/vs/platform/driver/browser/baseDriver.ts index e5cd680a29..884ac0bde4 100644 --- a/src/vs/platform/driver/browser/baseDriver.ts +++ b/src/vs/platform/driver/browser/baseDriver.ts @@ -186,5 +186,5 @@ export abstract class BaseWindowDriver implements IWindowDriver { return { x, y }; } - abstract async openDevTools(): Promise; + abstract openDevTools(): Promise; } diff --git a/src/vs/platform/driver/electron-main/driver.ts b/src/vs/platform/driver/electron-main/driver.ts index 457e6c76c7..8babed8780 100644 --- a/src/vs/platform/driver/electron-main/driver.ts +++ b/src/vs/platform/driver/electron-main/driver.ts @@ -13,7 +13,7 @@ import { SimpleKeybinding, KeyCode } from 'vs/base/common/keyCodes'; import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; import { OS } from 'vs/base/common/platform'; import { Emitter, Event } from 'vs/base/common/event'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { ScanCodeBinding } from 'vs/base/common/scanCode'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; import { timeout } from 'vs/base/common/async'; diff --git a/src/vs/platform/electron/common/electron.ts b/src/vs/platform/electron/common/electron.ts index 11859bd6c1..cfb5637f66 100644 --- a/src/vs/platform/electron/common/electron.ts +++ b/src/vs/platform/electron/common/electron.ts @@ -8,6 +8,14 @@ import { MessageBoxOptions, MessageBoxReturnValue, OpenDevToolsOptions, SaveDial import { IOpenedWindow, IWindowOpenable, IOpenEmptyWindowOptions, IOpenWindowOptions } from 'vs/platform/windows/common/windows'; import { INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; + +export interface IOSProperties { + type: string; + release: string; + arch: string; + platform: string; +} export interface ICommonElectronService { @@ -27,6 +35,8 @@ export interface ICommonElectronService { readonly onOSResume: Event; + readonly onColorSchemeChange: Event; + // Window getWindows(): Promise; getWindowCount(): Promise; @@ -73,6 +83,7 @@ export interface ICommonElectronService { moveItemToTrash(fullPath: string, deleteOnFail?: boolean): Promise; isAdmin(): Promise; getTotalMem(): Promise; + getOS(): Promise; // Process killProcess(pid: number, code: string): Promise; diff --git a/src/vs/platform/electron/electron-main/electronMainService.ts b/src/vs/platform/electron/electron-main/electronMainService.ts index 571a1f87d5..354eaf5cb3 100644 --- a/src/vs/platform/electron/electron-main/electronMainService.ts +++ b/src/vs/platform/electron/electron-main/electronMainService.ts @@ -3,26 +3,26 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; -import { MessageBoxOptions, MessageBoxReturnValue, shell, OpenDevToolsOptions, SaveDialogOptions, SaveDialogReturnValue, OpenDialogOptions, OpenDialogReturnValue, Menu, BrowserWindow, app, clipboard, powerMonitor } from 'electron'; +import { MessageBoxOptions, MessageBoxReturnValue, shell, OpenDevToolsOptions, SaveDialogOptions, SaveDialogReturnValue, OpenDialogOptions, OpenDialogReturnValue, Menu, BrowserWindow, app, clipboard, powerMonitor, nativeTheme } from 'electron'; import { OpenContext } from 'vs/platform/windows/node/window'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IOpenedWindow, IOpenWindowOptions, IWindowOpenable, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; import { INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs'; import { isMacintosh, isWindows, isRootUser } from 'vs/base/common/platform'; -import { ICommonElectronService } from 'vs/platform/electron/common/electron'; +import { ICommonElectronService, IOSProperties } from 'vs/platform/electron/common/electron'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { AddFirstParameterToFunctions } from 'vs/base/common/types'; import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs'; import { dirExists } from 'vs/base/node/pfs'; import { URI } from 'vs/base/common/uri'; import { ITelemetryData, ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { MouseInputEvent } from 'vs/base/parts/sandbox/common/electronTypes'; -import { totalmem } from 'os'; +import { arch, totalmem, release, platform, type } from 'os'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; export interface IElectronMainService extends AddFirstParameterToFunctions /* only methods, not events */, number | undefined /* window ID */> { } @@ -39,8 +39,27 @@ export class ElectronMainService implements IElectronMainService { @IEnvironmentService private readonly environmentService: INativeEnvironmentService, @ITelemetryService private readonly telemetryService: ITelemetryService ) { + this.registerListeners(); } + private registerListeners(): void { + + // Color Scheme changes + nativeTheme.on('updated', () => { + let colorScheme: ColorScheme; + if (nativeTheme.shouldUseInvertedColorScheme || nativeTheme.shouldUseHighContrastColors) { + colorScheme = ColorScheme.HIGH_CONTRAST; + } else if (nativeTheme.shouldUseDarkColors) { + colorScheme = ColorScheme.DARK; + } else { + colorScheme = ColorScheme.LIGHT; + } + + this._onColorSchemeChange.fire(colorScheme); + }); + } + + //#region Properties get windowId(): never { throw new Error('Not implemented in electron-main'); } @@ -62,6 +81,9 @@ export class ElectronMainService implements IElectronMainService { readonly onOSResume = Event.fromNodeEventEmitter(powerMonitor, 'resume'); + private readonly _onColorSchemeChange = new Emitter(); + readonly onColorSchemeChange = this._onColorSchemeChange.event; + //#endregion //#region Window @@ -316,6 +338,15 @@ export class ElectronMainService implements IElectronMainService { return totalmem(); } + async getOS(): Promise { + return { + arch: arch(), + platform: platform(), + release: release(), + type: type() + }; + } + //#endregion diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts new file mode 100644 index 0000000000..88bca25e7d --- /dev/null +++ b/src/vs/platform/environment/common/argv.ts @@ -0,0 +1,105 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * A list of command line arguments we support natively. + */ +export interface NativeParsedArgs { + _: string[]; + 'folder-uri'?: string[]; // undefined or array of 1 or more + 'file-uri'?: string[]; // undefined or array of 1 or more + _urls?: string[]; + help?: boolean; + version?: boolean; + telemetry?: boolean; + status?: boolean; + wait?: boolean; + waitMarkerFilePath?: string; + diff?: boolean; + add?: boolean; + goto?: boolean; + 'new-window'?: boolean; + 'unity-launch'?: boolean; // Always open a new window, except if opening the first window or opening a file or folder as part of the launch. + 'reuse-window'?: boolean; + locale?: string; + 'user-data-dir'?: string; + 'prof-startup'?: boolean; + 'prof-startup-prefix'?: string; + 'prof-append-timers'?: string; + verbose?: boolean; + trace?: boolean; + 'trace-category-filter'?: string; + 'trace-options'?: string; + 'open-devtools'?: boolean; + 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 + 'inspect-extensions'?: string; + 'inspect-brk-extensions'?: string; + debugId?: string; + 'inspect-search'?: string; + 'inspect-brk-search'?: string; + 'disable-extensions'?: boolean; + 'disable-extension'?: string[]; // undefined or array of 1 or more + 'list-extensions'?: boolean; + 'show-versions'?: boolean; + 'category'?: string; + 'install-extension'?: string[]; // undefined or array of 1 or more + 'uninstall-extension'?: string[]; // undefined or array of 1 or more + '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-release-notes'?: boolean; + 'disable-restore-windows'?: boolean; + 'disable-telemetry'?: boolean; + 'export-default-configuration'?: string; + 'install-source'?: string; + 'disable-updates'?: boolean; + 'disable-crash-reporter'?: boolean; + 'crash-reporter-directory'?: string; + 'crash-reporter-id'?: string; + 'skip-add-to-recently-opened'?: boolean; + 'max-memory'?: string; + 'file-write'?: boolean; + 'file-chmod'?: boolean; + 'driver'?: string; + 'driver-verbose'?: boolean; + 'remote'?: string; + 'disable-user-env-probe'?: boolean; + 'force'?: boolean; + 'do-not-sync'?: boolean; + 'force-user-env'?: boolean; + 'sync'?: 'on' | 'off'; + '__sandbox'?: boolean; + + // {{SQL CARBON EDIT}} Start + aad?: boolean; + database?: string; + integrated?: boolean; + server?: string; + user?: string; + command?: string; + // {{SQL CARBON EDIT}} End + + // chromium command line args: https://electronjs.org/docs/all#supported-chrome-command-line-switches + 'no-proxy-server'?: boolean; + 'proxy-server'?: string; + 'proxy-bypass-list'?: string; + 'proxy-pac-url'?: string; + 'inspect'?: string; + 'inspect-brk'?: string; + 'js-flags'?: string; + 'disable-gpu'?: boolean; + 'nolazy'?: boolean; + 'force-device-scale-factor'?: string; + 'force-renderer-accessibility'?: boolean; + 'ignore-certificate-errors'?: boolean; + 'allow-insecure-localhost'?: boolean; + 'log-net-log'?: string; +} diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 504d826912..80e3cab4ef 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -5,6 +5,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; +import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; export const IEnvironmentService = createDecorator('environmentService'); @@ -17,14 +18,18 @@ export interface IExtensionHostDebugParams extends IDebugParams { debugId?: string; } -export const BACKUPS = 'Backups'; - +/** + * A basic environment service that can be used in various processes, + * such as main, renderer and shared process. Use subclasses of this + * service for specific environment. + */ export interface IEnvironmentService { - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - // NOTE: DO NOT ADD ANY OTHER PROPERTY INTO THE COLLECTION HERE - // UNLESS THIS PROPERTY IS SUPPORTED BOTH IN WEB AND NATIVE!!!! - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // NOTE: KEEP THIS INTERFACE AS SMALL AS POSSIBLE. AS SUCH: + // - PUT NON-WEB PROPERTIES INTO NATIVE ENV SERVICE + // - PUT WORKBENCH ONLY PROPERTIES INTO WB ENV SERVICE + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! readonly _serviceBrand: undefined; @@ -37,7 +42,6 @@ export interface IEnvironmentService { snippetsHome: URI; // --- data paths - backupHome: URI; untitledWorkspacesHome: URI; globalStorageHome: URI; @@ -55,8 +59,6 @@ export interface IEnvironmentService { disableExtensions: boolean | string[]; extensionDevelopmentLocationURI?: URI[]; extensionTestsLocationURI?: URI; - extensionEnabledProposedApi?: string[]; - logExtensionHostCommunication?: boolean; // --- logging logsPath: string; @@ -68,8 +70,57 @@ export interface IEnvironmentService { disableTelemetry: boolean; serviceMachineIdResource: URI; - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - // NOTE: DO NOT ADD ANY OTHER PROPERTY INTO THE COLLECTION HERE - // UNLESS THIS PROPERTY IS SUPPORTED BOTH IN WEB AND NATIVE!!!! - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // NOTE: KEEP THIS INTERFACE AS SMALL AS POSSIBLE. AS SUCH: + // - PUT NON-WEB PROPERTIES INTO NATIVE ENV SERVICE + // - PUT WORKBENCH ONLY PROPERTIES INTO WB ENV SERVICE + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +} + +/** + * A subclass of the `IEnvironmentService` to be used only in native + * environments (Windows, Linux, macOS) but not e.g. web. + */ +export interface INativeEnvironmentService extends IEnvironmentService { + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // NOTE: KEEP THIS INTERFACE AS SMALL AS POSSIBLE. AS SUCH: + // - PUT WORKBENCH ONLY PROPERTIES INTO WB ENV SERVICE + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + // --- CLI Arguments + args: NativeParsedArgs; + + // --- paths + appRoot: string; + userHome: URI; + appSettingsHome: URI; + userDataPath: string; + machineSettingsResource: URI; + backupHome: string; + backupWorkspacesPath: string; + nodeCachedDataDir?: string; + installSourcePath: string; + + // --- IPC Handles + mainIPCHandle: string; + sharedIPCHandle: string; + + // --- Extensions + extensionsPath?: string; + extensionsDownloadPath: string; + builtinExtensionsPath: string; + + // --- Smoke test support + driverHandle?: string; + driverVerbose: boolean; + + // --- Misc. config + disableUpdates: boolean; + sandbox: boolean; + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // NOTE: KEEP THIS INTERFACE AS SMALL AS POSSIBLE. AS SUCH: + // - PUT WORKBENCH ONLY PROPERTIES INTO WB ENV SERVICE + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! } diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index ed3bcb4f40..3fac801ea9 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -6,104 +6,7 @@ import * as minimist from 'minimist'; import { localize } from 'vs/nls'; import { isWindows } from 'vs/base/common/platform'; - -export interface ParsedArgs { - _: string[]; - 'folder-uri'?: string[]; // undefined or array of 1 or more - 'file-uri'?: string[]; // undefined or array of 1 or more - _urls?: string[]; - help?: boolean; - version?: boolean; - telemetry?: boolean; - status?: boolean; - wait?: boolean; - waitMarkerFilePath?: string; - diff?: boolean; - add?: boolean; - goto?: boolean; - 'new-window'?: boolean; - 'unity-launch'?: boolean; // Always open a new window, except if opening the first window or opening a file or folder as part of the launch. - 'reuse-window'?: boolean; - locale?: string; - 'user-data-dir'?: string; - 'prof-startup'?: boolean; - 'prof-startup-prefix'?: string; - 'prof-append-timers'?: string; - verbose?: boolean; - trace?: boolean; - 'trace-category-filter'?: string; - 'trace-options'?: string; - 'open-devtools'?: boolean; - 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 - 'inspect-extensions'?: string; - 'inspect-brk-extensions'?: string; - debugId?: string; - 'inspect-search'?: string; - 'inspect-brk-search'?: string; - 'disable-extensions'?: boolean; - 'disable-extension'?: string[]; // undefined or array of 1 or more - 'list-extensions'?: boolean; - 'show-versions'?: boolean; - 'category'?: string; - 'install-extension'?: string[]; // undefined or array of 1 or more - 'uninstall-extension'?: string[]; // undefined or array of 1 or more - '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-release-notes'?: boolean; - 'disable-restore-windows'?: boolean; - 'disable-telemetry'?: boolean; - 'export-default-configuration'?: string; - 'install-source'?: string; - 'disable-updates'?: boolean; - 'disable-crash-reporter'?: boolean; - 'crash-reporter-directory'?: string; - 'crash-reporter-id'?: string; - 'skip-add-to-recently-opened'?: boolean; - 'max-memory'?: string; - 'file-write'?: boolean; - 'file-chmod'?: boolean; - 'driver'?: string; - 'driver-verbose'?: boolean; - 'remote'?: string; - 'disable-user-env-probe'?: boolean; - 'force'?: boolean; - 'do-not-sync'?: boolean; - 'force-user-env'?: boolean; - 'sync'?: 'on' | 'off'; - '__sandbox'?: boolean; - - // {{SQL CARBON EDIT}} Start - aad?: boolean; - database?: string; - integrated?: boolean; - server?: string; - user?: string; - command?: string; - // {{SQL CARBON EDIT}} End - - // chromium command line args: https://electronjs.org/docs/all#supported-chrome-command-line-switches - 'no-proxy-server'?: boolean; - 'proxy-server'?: string; - 'proxy-bypass-list'?: string; - 'proxy-pac-url'?: string; - 'inspect'?: string; - 'inspect-brk'?: string; - 'js-flags'?: string; - 'disable-gpu'?: boolean; - 'nolazy'?: boolean; - 'force-device-scale-factor'?: string; - 'force-renderer-accessibility'?: boolean; - 'ignore-certificate-errors'?: boolean; - 'allow-insecure-localhost'?: boolean; - 'log-net-log'?: string; -} +import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; /** * This code is also used by standalone cli's. Avoid adding any other dependencies. @@ -134,7 +37,7 @@ type OptionTypeName = T extends undefined ? 'undefined' : 'unknown'; -export const OPTIONS: OptionDescriptions> = { +export const OPTIONS: OptionDescriptions> = { 'diff': { type: 'boolean', cat: 'o', alias: 'd', args: ['file', 'file'], description: localize('diff', "Compare two files with each other.") }, 'add': { type: 'boolean', cat: 'o', alias: 'a', args: 'folder', description: localize('add', "Add folder(s) to the last active window.") }, 'goto': { type: 'boolean', cat: 'o', alias: 'g', args: 'file:line[:character]', description: localize('goto', "Open a file at the path on the specified line and character position.") }, @@ -423,4 +326,3 @@ export function buildHelpMessage(productName: string, executableName: string, ve export function buildVersionMessage(version: string | undefined, commit: string | undefined): string { return `${version || localize('unknownVersion', "Unknown version")}\n${commit || localize('unknownCommit', "Unknown commit")}\n${process.arch}`; } - diff --git a/src/vs/platform/environment/node/argvHelper.ts b/src/vs/platform/environment/node/argvHelper.ts index f23df051b3..40d86d7dc5 100644 --- a/src/vs/platform/environment/node/argvHelper.ts +++ b/src/vs/platform/environment/node/argvHelper.ts @@ -4,12 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { firstIndex } from 'vs/base/common/arrays'; import { localize } from 'vs/nls'; import { MIN_MAX_MEMORY_SIZE_MB } from 'vs/platform/files/common/files'; -import { parseArgs, ErrorReporter, OPTIONS, ParsedArgs } from 'vs/platform/environment/node/argv'; +import { parseArgs, ErrorReporter, OPTIONS } from 'vs/platform/environment/node/argv'; +import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; -function parseAndValidate(cmdLineArgs: string[], reportWarnings: boolean): ParsedArgs { +function parseAndValidate(cmdLineArgs: string[], reportWarnings: boolean): NativeParsedArgs { const errorReporter: ErrorReporter = { onUnknownOption: (id) => { console.warn(localize('unknownOption', "Warning: '{0}' is not in the list of known options, but still passed to Electron/Chromium.", id)); @@ -32,7 +32,7 @@ function parseAndValidate(cmdLineArgs: string[], reportWarnings: boolean): Parse } function stripAppPath(argv: string[]): string[] | undefined { - const index = firstIndex(argv, a => !/^-/.test(a)); + const index = argv.findIndex(a => !/^-/.test(a)); if (index > -1) { return [...argv.slice(0, index), ...argv.slice(index + 1)]; @@ -43,7 +43,7 @@ function stripAppPath(argv: string[]): string[] | undefined { /** * Use this to parse raw code process.argv such as: `Electron . --verbose --wait` */ -export function parseMainProcessArgv(processArgv: string[]): ParsedArgs { +export function parseMainProcessArgv(processArgv: string[]): NativeParsedArgs { let [, ...args] = processArgv; // If dev, remove the first non-option argument: it's the app location @@ -59,7 +59,7 @@ export function parseMainProcessArgv(processArgv: string[]): ParsedArgs { /** * Use this to parse raw code CLI process.argv such as: `Electron cli.js . --verbose --wait` */ -export function parseCLIProcessArgv(processArgv: string[]): ParsedArgs { +export function parseCLIProcessArgv(processArgv: string[]): NativeParsedArgs { let [, , ...args] = processArgv; if (process.env['VSCODE_DEV']) { diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index f8035dcfdb..24b21adeb8 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -3,8 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IEnvironmentService, IDebugParams, IExtensionHostDebugParams, BACKUPS } from 'vs/platform/environment/common/environment'; -import { ParsedArgs } from 'vs/platform/environment/node/argv'; +import { IDebugParams, IExtensionHostDebugParams, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; +import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import * as crypto from 'crypto'; import * as paths from 'vs/base/node/paths'; import * as os from 'os'; @@ -13,54 +13,19 @@ import * as resources from 'vs/base/common/resources'; import { memoize } from 'vs/base/common/decorators'; import product from 'vs/platform/product/common/product'; import { toLocalISOString } from 'vs/base/common/date'; -import { isWindows, isLinux, Platform, platform } from 'vs/base/common/platform'; +import { isWindows, Platform, platform } from 'vs/base/common/platform'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { URI } from 'vs/base/common/uri'; -export interface INativeEnvironmentService extends IEnvironmentService { - args: ParsedArgs; - - appRoot: string; - execPath: string; - - appSettingsHome: URI; - userDataPath: string; - userHome: URI; - machineSettingsResource: URI; - backupWorkspacesPath: string; - nodeCachedDataDir?: string; - - mainIPCHandle: string; - sharedIPCHandle: string; - - installSourcePath: string; - - extensionsPath?: string; - extensionsDownloadPath: string; - builtinExtensionsPath: string; - - driverHandle?: string; - driverVerbose: boolean; - - disableUpdates: boolean; - - sandbox: boolean; -} - export class EnvironmentService implements INativeEnvironmentService { declare readonly _serviceBrand: undefined; - get args(): ParsedArgs { return this._args; } + get args(): NativeParsedArgs { return this._args; } @memoize get appRoot(): string { return path.dirname(getPathFromAmdModule(require, '')); } - get execPath(): string { return this._execPath; } - - @memoize - get cliPath(): string { return getCLIPath(this.execPath, this.appRoot, this.isBuilt); } - readonly logsPath: string; @memoize @@ -129,10 +94,10 @@ export class EnvironmentService implements INativeEnvironmentService { get isExtensionDevelopment(): boolean { return !!this._args.extensionDevelopmentPath; } @memoize - get backupHome(): URI { return URI.file(path.join(this.userDataPath, BACKUPS)); } + get backupHome(): string { return path.join(this.userDataPath, 'Backups'); } @memoize - get backupWorkspacesPath(): string { return path.join(this.backupHome.fsPath, 'workspaces.json'); } + get backupWorkspacesPath(): string { return path.join(this.backupHome, 'workspaces.json'); } @memoize get untitledWorkspacesHome(): URI { return URI.file(path.join(this.userDataPath, 'Workspaces')); } @@ -222,22 +187,8 @@ export class EnvironmentService implements INativeEnvironmentService { return false; } - get extensionEnabledProposedApi(): string[] | undefined { - if (Array.isArray(this.args['enable-proposed-api'])) { - return this.args['enable-proposed-api']; - } - - if ('enable-proposed-api' in this.args) { - return []; - } - - return undefined; - } - @memoize get debugExtensionHost(): IExtensionHostDebugParams { return parseExtensionHostPort(this._args, this.isBuilt); } - @memoize - get logExtensionHostCommunication(): boolean { return !!this.args.logExtensionHostCommunication; } get isBuilt(): boolean { return !process.env['VSCODE_DEV']; } get verbose(): boolean { return !!this._args.verbose; } @@ -266,7 +217,7 @@ export class EnvironmentService implements INativeEnvironmentService { get sandbox(): boolean { return !!this._args['__sandbox']; } - constructor(private _args: ParsedArgs, private _execPath: string) { + constructor(private _args: NativeParsedArgs) { if (!process.env['VSCODE_LOGS']) { const key = toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, ''); process.env['VSCODE_LOGS'] = path.join(this.userDataPath, 'logs', key); @@ -327,39 +278,11 @@ function getIPCHandle(userDataPath: string, type: string): string { return getNixIPCHandle(userDataPath, type); } -function getCLIPath(execPath: string, appRoot: string, isBuilt: boolean): string { - - // Windows - if (isWindows) { - if (isBuilt) { - return path.join(path.dirname(execPath), 'bin', `${product.applicationName}.cmd`); - } - - return path.join(appRoot, 'scripts', 'code-cli.bat'); - } - - // Linux - if (isLinux) { - if (isBuilt) { - return path.join(path.dirname(execPath), 'bin', `${product.applicationName}`); - } - - return path.join(appRoot, 'scripts', 'code-cli.sh'); - } - - // macOS - if (isBuilt) { - return path.join(appRoot, 'bin', 'code'); - } - - return path.join(appRoot, 'scripts', 'code-cli.sh'); -} - -export function parseExtensionHostPort(args: ParsedArgs, isBuild: boolean): IExtensionHostDebugParams { +export function parseExtensionHostPort(args: NativeParsedArgs, isBuild: boolean): IExtensionHostDebugParams { return parseDebugPort(args['inspect-extensions'], args['inspect-brk-extensions'], 5870, isBuild, args.debugId); } -export function parseSearchPort(args: ParsedArgs, isBuild: boolean): IDebugParams { +export function parseSearchPort(args: NativeParsedArgs, isBuild: boolean): IDebugParams { return parseDebugPort(args['inspect-search'], args['inspect-brk-search'], 5876, isBuild); } @@ -387,6 +310,6 @@ export function parsePathArg(arg: string | undefined, process: NodeJS.Process): return path.resolve(process.env['VSCODE_CWD'] || process.cwd(), arg); } -export function parseUserDataDir(args: ParsedArgs, process: NodeJS.Process): string { +export function parseUserDataDir(args: NativeParsedArgs, process: NodeJS.Process): string { return parsePathArg(args['user-data-dir'], process) || path.resolve(paths.getDefaultUserDataPath(process.platform)); } diff --git a/src/vs/platform/extensionManagement/common/configRemotes.ts b/src/vs/platform/extensionManagement/common/configRemotes.ts index 834b3a0514..cc392ced50 100644 --- a/src/vs/platform/extensionManagement/common/configRemotes.ts +++ b/src/vs/platform/extensionManagement/common/configRemotes.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { endsWith } from 'vs/base/common/strings'; const SshProtocolMatcher = /^([^@:]+@)?([^:]+):/; const SshUrlMatcher = /^([^@:]+@)?([^:]+):(.+)$/; @@ -76,7 +75,7 @@ function stripPort(authority: string): string | null { function normalizeRemote(host: string | null, path: string, stripEndingDotGit: boolean): string | null { if (host && path) { - if (stripEndingDotGit && endsWith(path, '.git')) { + if (stripEndingDotGit && path.endsWith('.git')) { path = path.substr(0, path.length - 4); } return (path.indexOf('/') === 0) ? `${host}${path}` : `${host}/${path}`; diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 6027e5816b..3ede24bbae 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -6,7 +6,7 @@ import { getErrorMessage, isPromiseCanceledError, canceled } from 'vs/base/common/errors'; import { StatisticType, IGalleryExtension, IExtensionGalleryService, IGalleryExtensionAsset, IQueryOptions, SortBy, SortOrder, IExtensionIdentifier, IReportedExtension, InstallOperation, ITranslation, IGalleryExtensionVersion, IGalleryExtensionAssets, isIExtensionIdentifier, DefaultIconPath } from 'vs/platform/extensionManagement/common/extensionManagement'; import { getGalleryExtensionId, getGalleryExtensionTelemetryData, adoptToGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { assign, getOrDefault } from 'vs/base/common/objects'; +import { getOrDefault } from 'vs/base/common/objects'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IPager } from 'vs/base/common/paging'; import { IRequestService, asJson, asText } from 'vs/platform/request/common/request'; @@ -21,7 +21,6 @@ import { IFileService } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { IProductService } from 'vs/platform/product/common/productService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { find } from 'vs/base/common/arrays'; import { getServiceMachineId } from 'vs/platform/serviceMachineId/common/serviceMachineId'; import { optional } from 'vs/platform/instantiation/common/instantiation'; import { joinPath } from 'vs/base/common/resources'; @@ -166,7 +165,7 @@ class Query { get criteria(): ICriterium[] { return this.state.criteria ? this.state.criteria : []; } withPage(pageNumber: number, pageSize: number = this.state.pageSize): Query { - return new Query(assign({}, this.state, { pageNumber, pageSize })); + return new Query({ ...this.state, pageNumber, pageSize }); } withFilter(filterType: FilterType, ...values: string[]): Query { @@ -175,23 +174,23 @@ class Query { ...values.length ? values.map(value => ({ filterType, value })) : [{ filterType }] ]; - return new Query(assign({}, this.state, { criteria })); + return new Query({ ...this.state, criteria }); } withSortBy(sortBy: SortBy): Query { - return new Query(assign({}, this.state, { sortBy })); + return new Query({ ...this.state, sortBy }); } withSortOrder(sortOrder: SortOrder): Query { - return new Query(assign({}, this.state, { sortOrder })); + return new Query({ ...this.state, sortOrder }); } withFlags(...flags: Flags[]): Query { - return new Query(assign({}, this.state, { flags: flags.reduce((r, f) => r | f, 0) })); + return new Query({ ...this.state, flags: flags.reduce((r, f) => r | f, 0) }); } withAssetTypes(...assetTypes: string[]): Query { - return new Query(assign({}, this.state, { assetTypes })); + return new Query({ ...this.state, assetTypes }); } get raw(): any { @@ -429,7 +428,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { } } - private getCompatibleExtensionByEngine(arg1: IExtensionIdentifier | IGalleryExtension, version?: string): Promise { + private async getCompatibleExtensionByEngine(arg1: IExtensionIdentifier | IGalleryExtension, version?: string): Promise { const extension: IGalleryExtension | null = isIExtensionIdentifier(arg1) ? null : arg1; // {{SQL CARBON EDIT}} // Change to original version: removed the extension version validation @@ -451,40 +450,38 @@ export class ExtensionGalleryService implements IExtensionGalleryService { query = query.withFilter(FilterType.ExtensionName, id); } - return this.queryGallery(query, CancellationToken.None) - .then(({ galleryExtensions }) => { - const [rawExtension] = galleryExtensions; - if (!rawExtension || !rawExtension.versions.length) { - return null; + const { galleryExtensions } = await this.queryGallery(query, CancellationToken.None); + const [rawExtension] = galleryExtensions; + if (!rawExtension || !rawExtension.versions.length) { + return null; + } + + if (version) { + const versionAsset = rawExtension.versions.filter(v => v.version === version)[0]; + if (versionAsset) { + const extension = toExtension(rawExtension, versionAsset, 0, query); + if (extension.properties.engine && isEngineValid(extension.properties.engine, this.productService.version)) { + return extension; } - if (version) { - const versionAsset = rawExtension.versions.filter(v => v.version === version)[0]; - if (versionAsset) { - const extension = toExtension(rawExtension, versionAsset, 0, query); - if (extension.properties.engine && isEngineValid(extension.properties.engine, this.productService.version)) { - return extension; - } - } - return null; - } - return this.getLastValidExtensionVersion(rawExtension, rawExtension.versions) - .then(rawVersion => { - if (rawVersion) { - return toExtension(rawExtension, rawVersion, 0, query); - } - return null; - }); - }); + } + return null; + } + + const rawVersion = await this.getLastValidExtensionVersion(rawExtension, rawExtension.versions); + if (rawVersion) { + return toExtension(rawExtension, rawVersion, 0, query); + } + return null; } query(token: CancellationToken): Promise>; query(options: IQueryOptions, token: CancellationToken): Promise>; - query(arg1: any, arg2?: any): Promise> { + async query(arg1: any, arg2?: any): Promise> { const options: IQueryOptions = CancellationToken.isCancellationToken(arg1) ? {} : arg1; const token: CancellationToken = CancellationToken.isCancellationToken(arg1) ? arg1 : arg2; if (!this.isEnabled()) { - return Promise.reject(new Error('No extension gallery service configured.')); + throw new Error('No extension gallery service configured.'); } const type = options.names ? 'ids' : (options.text ? 'text' : 'all'); @@ -549,22 +546,19 @@ export class ExtensionGalleryService implements IExtensionGalleryService { query = query.withSortOrder(options.sortOrder); } - return this.queryGallery(query, token).then(({ galleryExtensions, total }) => { - const extensions = galleryExtensions.map((e, index) => toExtension(e, e.versions[0], index, query, options.source)); - // {{SQL CARBON EDIT}} - const pageSize = extensions.length; - const getPage = (pageIndex: number, ct: CancellationToken) => { - if (ct.isCancellationRequested) { - return Promise.reject(canceled()); - } + const { galleryExtensions, total } = await this.queryGallery(query, token); + const extensions = galleryExtensions.map((e, index) => toExtension(e, e.versions[0], index, query, options.source)); + const getPage = async (pageIndex: number, ct: CancellationToken) => { + if (ct.isCancellationRequested) { + throw canceled(); + } + const nextPageQuery = query.withPage(pageIndex + 1); + const { galleryExtensions } = await this.queryGallery(nextPageQuery, ct); + return galleryExtensions.map((e, index) => toExtension(e, e.versions[0], index, nextPageQuery, options.source)); + }; - const nextPageQuery = query.withPage(pageIndex + 1); - return this.queryGallery(nextPageQuery, ct) - .then(({ galleryExtensions }) => galleryExtensions.map((e, index) => toExtension(e, e.versions[0], index, nextPageQuery, options.source))); - }; - - return { firstPage: extensions, total, pageSize, getPage } as IPager; - }); + // {{ SQL CARBON EDIT }} + return { firstPage: extensions, total, pageSize: extensions.length, getPage } as IPager; } // {{SQL CARBON EDIT}} @@ -579,16 +573,16 @@ export class ExtensionGalleryService implements IExtensionGalleryService { if (query.criteria) { const ids = query.criteria.filter(x => x.filterType === FilterType.ExtensionId).map(v => v.value ? v.value.toLocaleLowerCase() : undefined); if (ids && ids.length > 0) { - filteredExtensions = filteredExtensions.filter(e => e.extensionId && find(ids, x => x === e.extensionId.toLocaleLowerCase())); + filteredExtensions = filteredExtensions.filter(e => e.extensionId && ids.find(x => x === e.extensionId.toLocaleLowerCase())); } const names = query.criteria.filter(x => x.filterType === FilterType.ExtensionName).map(v => v.value ? v.value.toLocaleLowerCase() : undefined); if (names && names.length > 0) { - filteredExtensions = filteredExtensions.filter(e => e.extensionName && e.publisher.publisherName && find(names, x => x === `${e.publisher.publisherName.toLocaleLowerCase()}.${e.extensionName.toLocaleLowerCase()}`)); + filteredExtensions = filteredExtensions.filter(e => e.extensionName && e.publisher.publisherName && names.find(x => x === `${e.publisher.publisherName.toLocaleLowerCase()}.${e.extensionName.toLocaleLowerCase()}`)); } const categoryFilters = query.criteria.filter(x => x.filterType === FilterType.Category).map(v => v.value ? v.value.toLowerCase() : undefined); if (categoryFilters && categoryFilters.length > 0) { // Implement the @category: "language packs" filtering - if (find(categoryFilters, x => x === 'language packs')) { + if (categoryFilters.find(x => x === 'language packs')) { filteredExtensions = filteredExtensions.filter(e => { // we only have 1 version for our extensions in the gallery file, so this should always be the case if (e.versions.length === 1) { @@ -670,74 +664,72 @@ export class ExtensionGalleryService implements IExtensionGalleryService { return a[fieldName] < b[fieldName] ? -1 : 1; } - private queryGallery(query: Query, token: CancellationToken): Promise<{ galleryExtensions: IRawGalleryExtension[], total: number; }> { + private async queryGallery(query: Query, token: CancellationToken): Promise<{ galleryExtensions: IRawGalleryExtension[], total: number; }> { + if (!this.isEnabled()) { + throw new Error('No extension gallery service configured.'); + } // Always exclude non validated and unpublished extensions query = query .withFlags(query.flags, Flags.ExcludeNonValidated) .withFilter(FilterType.ExcludeWithFlags, flagsToString(Flags.Unpublished)); - if (!this.isEnabled()) { - return Promise.reject(new Error('No extension gallery service configured.')); + const commonHeaders = await this.commonHeadersPromise; + const data = JSON.stringify(query.raw); + const headers = { + ...commonHeaders, + 'Content-Type': 'application/json', + 'Accept': 'application/json;api-version=3.0-preview.1', + 'Accept-Encoding': 'gzip', + 'Content-Length': String(data.length) + }; + + const context = await this.requestService.request({ + // {{SQL CARBON EDIT}} + type: 'GET', + url: this.api('/extensionquery'), + data, + headers + }, token); + + // {{SQL CARBON EDIT}} + let extensionPolicy: string = this.configurationService.getValue(ExtensionsPolicyKey); + if (context.res.statusCode && context.res.statusCode >= 400 && context.res.statusCode < 500 || extensionPolicy === ExtensionsPolicy.allowNone) { + return { galleryExtensions: [], total: 0 }; } - return this.commonHeadersPromise.then(commonHeaders => { - const data = JSON.stringify(query.raw); - const headers = assign({}, commonHeaders, { - 'Content-Type': 'application/json', - 'Accept': 'application/json;api-version=3.0-preview.1', - 'Accept-Encoding': 'gzip', - 'Content-Length': data.length - }); - return this.requestService.request({ - // {{SQL CARBON EDIT}} - type: 'GET', - url: this.api('/extensionquery'), - data, - headers - }, token).then(context => { + const result = await asJson(context); + if (result) { + const r = result.results[0]; + const galleryExtensions = r.extensions; + // const resultCount = r.resultMetadata && r.resultMetadata.filter(m => m.metadataType === 'ResultCount')[0]; {{SQL CARBON EDIT}} comment out for no unused + // const total = resultCount && resultCount.metadataItems.filter(i => i.name === 'TotalCount')[0].count || 0; {{SQL CARBON EDIT}} comment out for no unused - // {{SQL CARBON EDIT}} - let extensionPolicy: string = this.configurationService.getValue(ExtensionsPolicyKey); - if (context.res.statusCode && context.res.statusCode >= 400 && context.res.statusCode < 500 || extensionPolicy === ExtensionsPolicy.allowNone) { - return { galleryExtensions: [], total: 0 }; - } + // {{SQL CARBON EDIT}} + let filteredExtensionsResult = this.createQueryResult(query, galleryExtensions); - return asJson(context).then(result => { - if (result) { - const r = result.results[0]; - const galleryExtensions = r.extensions; - // const resultCount = r.resultMetadata && r.resultMetadata.filter(m => m.metadataType === 'ResultCount')[0]; {{SQL CARBON EDIT}} comment out for no unused - // const total = resultCount && resultCount.metadataItems.filter(i => i.name === 'TotalCount')[0].count || 0; {{SQL CARBON EDIT}} comment out for no unused - - // {{SQL CARBON EDIT}} - let filteredExtensionsResult = this.createQueryResult(query, galleryExtensions); - - return { galleryExtensions: filteredExtensionsResult.galleryExtensions, total: filteredExtensionsResult.total }; - // {{SQL CARBON EDIT}} - End - } - return { galleryExtensions: [], total: 0 }; - }); - }); - }); + return { galleryExtensions: filteredExtensionsResult.galleryExtensions, total: filteredExtensionsResult.total }; + // {{SQL CARBON EDIT}} - End + } + return { galleryExtensions: [], total: 0 }; } - reportStatistic(publisher: string, name: string, version: string, type: StatisticType): Promise { + async reportStatistic(publisher: string, name: string, version: string, type: StatisticType): Promise { if (!this.isEnabled()) { - return Promise.resolve(undefined); + return undefined; } - return this.commonHeadersPromise.then(commonHeaders => { - const headers = { ...commonHeaders, Accept: '*/*;api-version=4.0-preview.1' }; - - return this.requestService.request({ + const commonHeaders = await this.commonHeadersPromise; + const headers = { ...commonHeaders, Accept: '*/*;api-version=4.0-preview.1' }; + try { + await this.requestService.request({ type: 'POST', url: this.api(`/publishers/${publisher}/extensions/${name}/${version}/stats?statType=${type}`), headers - }, CancellationToken.None).then(undefined, () => undefined); - }); + }, CancellationToken.None); + } catch (error) { /* Ignore */ } } - download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise { + async download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise { this.logService.trace('ExtensionGalleryService#download', extension.identifier.id); const data = getGalleryExtensionTelemetryData(extension); const startTime = new Date().getTime(); @@ -749,7 +741,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { ] } */ - const log = (duration: number) => this.telemetryService.publicLog('galleryService:downloadVSIX', assign(data, { duration })); + const log = (duration: number) => this.telemetryService.publicLog('galleryService:downloadVSIX', { ...data, duration }); // {{SQL Carbon Edit}} - Don't append install or update on to the URL // const operationParam = operation === InstallOperation.Install ? 'install' : operation === InstallOperation.Update ? 'update' : ''; @@ -759,46 +751,46 @@ export class ExtensionGalleryService implements IExtensionGalleryService { fallbackUri: `${extension.assets.download.fallbackUri}?${operationParam}=true` } : extension.assets.download; - return this.getAsset(downloadAsset) - .then(context => this.fileService.writeFile(location, context.stream)) - .then(() => log(new Date().getTime() - startTime)); + const context = await this.getAsset(downloadAsset); + await this.fileService.writeFile(location, context.stream); + log(new Date().getTime() - startTime); } - getReadme(extension: IGalleryExtension, token: CancellationToken): Promise { + async getReadme(extension: IGalleryExtension, token: CancellationToken): Promise { if (extension.assets.readme) { - return this.getAsset(extension.assets.readme, {}, token) - .then(context => asText(context)) - .then(content => content || ''); + const context = await this.getAsset(extension.assets.readme, {}, token); + const content = await asText(context); + return content || ''; } - return Promise.resolve(''); + return ''; } - getManifest(extension: IGalleryExtension, token: CancellationToken): Promise { + async getManifest(extension: IGalleryExtension, token: CancellationToken): Promise { if (extension.assets.manifest) { - return this.getAsset(extension.assets.manifest, {}, token) - .then(asText) - .then(text => text ? JSON.parse(text) : null); + const context = await this.getAsset(extension.assets.manifest, {}, token); + const text = await asText(context); + return text ? JSON.parse(text) : null; } - return Promise.resolve(null); + return null; } - getCoreTranslation(extension: IGalleryExtension, languageId: string): Promise { + async getCoreTranslation(extension: IGalleryExtension, languageId: string): Promise { const asset = extension.assets.coreTranslations.filter(t => t[0] === languageId.toUpperCase())[0]; if (asset) { - return this.getAsset(asset[1]) - .then(asText) - .then(text => text ? JSON.parse(text) : null); + const context = await this.getAsset(asset[1]); + const text = await asText(context); + return text ? JSON.parse(text) : null; } - return Promise.resolve(null); + return null; } - getChangelog(extension: IGalleryExtension, token: CancellationToken): Promise { + async getChangelog(extension: IGalleryExtension, token: CancellationToken): Promise { if (extension.assets.changelog) { - return this.getAsset(extension.assets.changelog, {}, token) - .then(context => asText(context)) - .then(content => content || ''); + const context = await this.getAsset(extension.assets.changelog, {}, token); + const content = await asText(context); + return content || ''; } - return Promise.resolve(''); + return ''; } async getAllVersions(extension: IGalleryExtension, compatible: boolean): Promise { @@ -833,48 +825,45 @@ export class ExtensionGalleryService implements IExtensionGalleryService { return result; } - private getAsset(asset: IGalleryExtensionAsset, options: IRequestOptions = {}, token: CancellationToken = CancellationToken.None): Promise { - return this.commonHeadersPromise.then(commonHeaders => { - const baseOptions = { type: 'GET' }; - const headers = assign({}, commonHeaders, options.headers || {}); - options = assign({}, options, baseOptions, { headers }); + private async getAsset(asset: IGalleryExtensionAsset, options: IRequestOptions = {}, token: CancellationToken = CancellationToken.None): Promise { + const commonHeaders = await this.commonHeadersPromise; + const baseOptions = { type: 'GET' }; + const headers = { ...commonHeaders, ...(options.headers || {}) }; + options = { ...options, ...baseOptions, headers }; - const url = asset.uri; - const fallbackUrl = asset.fallbackUri; - const firstOptions = assign({}, options, { url }); + const url = asset.uri; + const fallbackUrl = asset.fallbackUri; + const firstOptions = { ...options, url }; - return this.requestService.request(firstOptions, token) - .then(context => { - if (context.res.statusCode === 200) { - return Promise.resolve(context); - } + try { + const context = await this.requestService.request(firstOptions, token); + if (context.res.statusCode === 200) { + return context; + } + const message = await asText(context); + throw new Error(`Expected 200, got back ${context.res.statusCode} instead.\n\n${message}`); + } catch (err) { + if (isPromiseCanceledError(err)) { + throw err; + } - return asText(context) - .then(message => Promise.reject(new Error(`Expected 200, got back ${context.res.statusCode} instead.\n\n${message}`))); - }) - .then(undefined, err => { - if (isPromiseCanceledError(err)) { - return Promise.reject(err); - } + const message = getErrorMessage(err); + type GalleryServiceCDNFallbackClassification = { + url: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + message: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + }; + type GalleryServiceCDNFallbackEvent = { + url: string; + message: string; + }; + this.telemetryService.publicLog2('galleryService:cdnFallback', { url, message }); - const message = getErrorMessage(err); - type GalleryServiceCDNFallbackClassification = { - url: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; - message: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; - }; - type GalleryServiceCDNFallbackEvent = { - url: string; - message: string; - }; - this.telemetryService.publicLog2('galleryService:cdnFallback', { url, message }); - - const fallbackOptions = assign({}, options, { url: fallbackUrl }); - return this.requestService.request(fallbackOptions, token); - }); - }); + const fallbackOptions = { ...options, url: fallbackUrl }; + return this.requestService.request(fallbackOptions, token); + } } - private getLastValidExtensionVersion(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): Promise { + private async getLastValidExtensionVersion(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): Promise { const version = this.getLastValidExtensionVersionFromProperties(extension, versions); if (version) { return version; @@ -882,7 +871,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { return this.getLastValidExtensionVersionRecursively(extension, versions); } - private getLastValidExtensionVersionFromProperties(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): Promise | null { + private getLastValidExtensionVersionFromProperties(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): IRawGalleryExtensionVersion | null { for (const version of versions) { // {{SQL CARBON EDIT}} const vsCodeEngine = getEngine(version); @@ -894,75 +883,75 @@ export class ExtensionGalleryService implements IExtensionGalleryService { const vsCodeEngineValid = !vsCodeEngine || (vsCodeEngine && isEngineValid(vsCodeEngine, this.productService.vscodeVersion)); const azDataEngineValid = !azDataEngine || (azDataEngine && isEngineValid(azDataEngine, this.productService.version)); if (vsCodeEngineValid && azDataEngineValid) { - return Promise.resolve(version); + return version; } } return null; } - private getEngine(version: IRawGalleryExtensionVersion): Promise { + private async getEngine(version: IRawGalleryExtensionVersion): Promise { const engine = getEngine(version); if (engine) { - return Promise.resolve(engine); + return engine; } - const manifest = getVersionAsset(version, AssetType.Manifest); - if (!manifest) { - return Promise.reject('Manifest was not found'); + const manifestAsset = getVersionAsset(version, AssetType.Manifest); + if (!manifestAsset) { + throw new Error('Manifest was not found'); } const headers = { 'Accept-Encoding': 'gzip' }; - return this.getAsset(manifest, { headers }) - .then(context => asJson(context)) - .then(manifest => manifest ? manifest.engines.vscode : Promise.reject('Error while reading manifest')); + const context = await this.getAsset(manifestAsset, { headers }); + const manifest = await asJson(context); + if (manifest) { + return manifest.engines.vscode; + } + + throw new Error('Error while reading manifest'); } - private getLastValidExtensionVersionRecursively(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): Promise { + private async getLastValidExtensionVersionRecursively(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): Promise { if (!versions.length) { - return Promise.resolve(null); + return null; } const version = versions[0]; - return this.getEngine(version) - .then(engine => { - if (!isEngineValid(engine, this.productService.version)) { - return this.getLastValidExtensionVersionRecursively(extension, versions.slice(1)); - } + const engine = await this.getEngine(version); + if (!isEngineValid(engine, this.productService.version)) { + return this.getLastValidExtensionVersionRecursively(extension, versions.slice(1)); + } - version.properties = version.properties || []; - version.properties.push({ key: PropertyType.Engine, value: engine }); - return version; - }); + version.properties = version.properties || []; + version.properties.push({ key: PropertyType.Engine, value: engine }); + return version; } - getExtensionsReport(): Promise { + async getExtensionsReport(): Promise { if (!this.isEnabled()) { - return Promise.reject(new Error('No extension gallery service configured.')); + throw new Error('No extension gallery service configured.'); } if (!this.extensionsControlUrl) { - return Promise.resolve([]); + return []; } - return this.requestService.request({ type: 'GET', url: this.extensionsControlUrl }, CancellationToken.None).then(context => { - if (context.res.statusCode !== 200) { - return Promise.reject(new Error('Could not get extensions report.')); + const context = await this.requestService.request({ type: 'GET', url: this.extensionsControlUrl }, CancellationToken.None); + if (context.res.statusCode !== 200) { + throw new Error('Could not get extensions report.'); + } + + const result = await asJson(context); + const map = new Map(); + + if (result) { + for (const id of result.malicious) { + const ext = map.get(id) || { id: { id }, malicious: true, slow: false }; + ext.malicious = true; + map.set(id, ext); } + } - return asJson(context).then(result => { - const map = new Map(); - - if (result) { - for (const id of result.malicious) { - const ext = map.get(id) || { id: { id }, malicious: true, slow: false }; - ext.malicious = true; - map.set(id, ext); - } - } - - return [...map.values()]; - }); - }); + return [...map.values()]; } } diff --git a/src/vs/platform/extensionManagement/node/extensionDownloader.ts b/src/vs/platform/extensionManagement/node/extensionDownloader.ts index fd50943797..f0b29b2eff 100644 --- a/src/vs/platform/extensionManagement/node/extensionDownloader.ts +++ b/src/vs/platform/extensionManagement/node/extensionDownloader.ts @@ -6,9 +6,8 @@ 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 { IEnvironmentService, INativeEnvironmentService } 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'; diff --git a/src/vs/platform/extensionManagement/node/extensionLifecycle.ts b/src/vs/platform/extensionManagement/node/extensionLifecycle.ts index f93a7d3f98..933b549a5e 100644 --- a/src/vs/platform/extensionManagement/node/extensionLifecycle.ts +++ b/src/vs/platform/extensionManagement/node/extensionLifecycle.ts @@ -12,7 +12,6 @@ import { join } from 'vs/base/common/path'; import { Limiter } from 'vs/base/common/async'; import { Event } from 'vs/base/common/event'; import { Schemas } from 'vs/base/common/network'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { rimraf } from 'vs/base/node/pfs'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -21,7 +20,7 @@ export class ExtensionsLifecycle extends Disposable { private processesLimiter: Limiter = new Limiter(5); // Run max 5 processes in parallel constructor( - @IEnvironmentService private environmentService: INativeEnvironmentService, + @IEnvironmentService private environmentService: IEnvironmentService, @ILogService private readonly logService: ILogService ) { super(); diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index ac6d80a89b..a895517c6f 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -6,7 +6,6 @@ import * as nls from 'vs/nls'; import * as path from 'vs/base/common/path'; import * as pfs from 'vs/base/node/pfs'; -import { assign } from 'vs/base/common/objects'; import { toDisposable, Disposable } from 'vs/base/common/lifecycle'; import { zip, IFile } from 'vs/base/node/zip'; import { @@ -22,8 +21,7 @@ import { ExtensionManagementError } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions, getGalleryExtensionId, getMaliciousExtensionsSet, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, ExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { createCancelablePromise, CancelablePromise } from 'vs/base/common/async'; import { Event, Emitter } from 'vs/base/common/event'; import * as semver from 'semver-umd'; @@ -66,7 +64,7 @@ export class ExtensionManagementService extends Disposable implements IExtension private readonly extensionsScanner: ExtensionsScanner; private reportedExtensions: Promise | undefined; private lastReportTimestamp = 0; - private readonly installingExtensions: Map> = new Map>(); + private readonly installingExtensions = new Map>(); private readonly uninstallingExtensions: Map> = new Map>(); private readonly manifestCache: ExtensionsManifestCache; private readonly extensionsDownloader: ExtensionsDownloader; @@ -105,16 +103,17 @@ export class ExtensionManagementService extends Disposable implements IExtension })); } - zip(extension: ILocalExtension): Promise { + async zip(extension: ILocalExtension): Promise { this.logService.trace('ExtensionManagementService#zip', extension.identifier.id); - return this.collectFiles(extension) - .then(files => zip(path.join(tmpdir(), generateUuid()), files)) - .then(path => URI.file(path)); + const files = await this.collectFiles(extension); + const location = await zip(path.join(tmpdir(), generateUuid()), files); + return URI.file(location); } - unzip(zipLocation: URI): Promise { + async unzip(zipLocation: URI): Promise { this.logService.trace('ExtensionManagementService#unzip', zipLocation.toString()); - return this.install(zipLocation).then(local => local.identifier); + const local = await this.install(zipLocation); + return local.identifier; } async getManifest(vsix: URI): Promise { @@ -123,7 +122,7 @@ export class ExtensionManagementService extends Disposable implements IExtension return getManifest(zipPath); } - private collectFiles(extension: ILocalExtension): Promise { + private async collectFiles(extension: ILocalExtension): Promise { const collectFilesFromDirectory = async (dir: string): Promise => { let entries = await pfs.readdir(dir); @@ -144,113 +143,119 @@ export class ExtensionManagementService extends Disposable implements IExtension return promise; }; - return collectFilesFromDirectory(extension.location.fsPath) - .then(files => files.map(f => ({ path: `extension/${path.relative(extension.location.fsPath, f)}`, localPath: f }))); - + const files = await collectFilesFromDirectory(extension.location.fsPath); + return files.map(f => ({ path: `extension/${path.relative(extension.location.fsPath, f)}`, localPath: f })); } - install(vsix: URI, isMachineScoped?: boolean): Promise { + async install(vsix: URI, isMachineScoped?: boolean): Promise { // {{SQL CARBON EDIT}} let startTime = new Date().getTime(); this.logService.trace('ExtensionManagementService#install', vsix.toString()); - return createCancelablePromise(token => { - return this.downloadVsix(vsix).then(downloadLocation => { - const zipPath = path.resolve(downloadLocation.fsPath); + return createCancelablePromise(async token => { - return getManifest(zipPath) - .then(manifest => { - const identifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) }; - // let operation: InstallOperation = InstallOperation.Install; {{SQL CARBON EDIT}} comment out for no unused - // {{SQL CARBON EDIT - Check VSCode and ADS version}} - if (manifest.engines && ((manifest.engines.vscode && !isEngineValid(manifest.engines.vscode, product.vscodeVersion)) || (manifest.engines.azdata && !isEngineValid(manifest.engines.azdata, product.version)))) { - return Promise.reject(new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with Azure Data Studio '{1}'.", identifier.id, product.version))); - } - const identifierWithVersion = new ExtensionIdentifierWithVersion(identifier, manifest.version); - return this.getInstalled(ExtensionType.User) - .then(installedExtensions => { - const existing = installedExtensions.filter(i => areSameExtensions(identifier, i.identifier))[0]; - if (existing) { - isMachineScoped = isMachineScoped || existing.isMachineScoped; - // operation = InstallOperation.Update; - if (identifierWithVersion.equals(new ExtensionIdentifierWithVersion(existing.identifier, existing.manifest.version))) { - return this.extensionsScanner.removeExtension(existing, 'existing').then(null, e => Promise.reject(new Error(nls.localize('restartCode', "Please restart Azure Data Studio before reinstalling {0}.", manifest.displayName || manifest.name)))); - } else if (semver.gt(existing.manifest.version, manifest.version)) { - return this.uninstallExtension(existing); - } - } else { - // Remove the extension with same version if it is already uninstalled. - // Installing a VSIX extension shall replace the existing extension always. - return this.unsetUninstalledAndGetLocal(identifierWithVersion) - .then(existing => { - if (existing) { - return this.extensionsScanner.removeExtension(existing, 'existing').then(null, e => Promise.reject(new Error(nls.localize('restartCode', "Please restart Azure Data Studio before reinstalling {0}.", manifest.displayName || manifest.name)))); - } - return undefined; - }); - } - return undefined; - }) - .then(() => { - this.logService.info('Installing the extension:', identifier.id); - this._onInstallExtension.fire({ identifier, zipPath }); - // {{SQL CARBON EDIT}} - // Until there's a gallery for SQL Ops Studio, skip retrieving the metadata from the gallery - return this.installExtension({ zipPath, identifierWithVersion, metadata: { isMachineScoped } }, token) - .then( - local => { - this.reportTelemetry(this.getTelemetryEvent(InstallOperation.Install), getLocalExtensionTelemetryData(local), new Date().getTime() - startTime, void 0); - this._onDidInstallExtension.fire({ identifier, zipPath, local, operation: InstallOperation.Install }); - return local; - }, - error => { this._onDidInstallExtension.fire({ identifier, zipPath, error, operation: InstallOperation.Install }); return Promise.reject(error); } - ); - // return this.getMetadata(getGalleryExtensionId(manifest.publisher, manifest.name)) - // .then( - // metadata => this.installFromZipPath(identifierWithVersion, zipPath, metadata, type, operation, token), - // () => this.installFromZipPath(identifierWithVersion, zipPath, null, type, operation, token)) - // .then( - // local => { this.logService.info('Successfully installed the extension:', identifier.id); return local; }, - // e => { - // this.logService.error('Failed to install the extension:', identifier.id, e.message); - // return Promise.reject(e); - // }); - // {{SQL CARBON EDIT}} - End - }); - }); - }); + const downloadLocation = await this.downloadVsix(vsix); + const zipPath = path.resolve(downloadLocation.fsPath); + + const manifest = await getManifest(zipPath); + const identifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) }; + // let operation: InstallOperation = InstallOperation.Install; {{ SQL CARBON EDIT }} + // {{ SQL CARBON EDIT }} + if (manifest.engines && ((manifest.engines.vscode && !isEngineValid(manifest.engines.vscode, product.vscodeVersion)) || (manifest.engines.azdata && !isEngineValid(manifest.engines.azdata, product.version)))) { + throw new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", identifier.id, product.version)); + } + + const identifierWithVersion = new ExtensionIdentifierWithVersion(identifier, manifest.version); + const installedExtensions = await this.getInstalled(ExtensionType.User); + const existing = installedExtensions.find(i => areSameExtensions(identifier, i.identifier)); + if (existing) { + isMachineScoped = isMachineScoped || existing.isMachineScoped; + // operation = InstallOperation.Update; {{ SQL CARBON EDIT }} + if (identifierWithVersion.equals(new ExtensionIdentifierWithVersion(existing.identifier, existing.manifest.version))) { + try { + await this.extensionsScanner.removeExtension(existing, 'existing'); + } catch (e) { + throw new Error(nls.localize('restartCode', "Please restart VS Code before reinstalling {0}.", manifest.displayName || manifest.name)); + } + } else if (semver.gt(existing.manifest.version, manifest.version)) { + await this.uninstallExtension(existing); + } + } else { + // Remove the extension with same version if it is already uninstalled. + // Installing a VSIX extension shall replace the existing extension always. + const existing = await this.unsetUninstalledAndGetLocal(identifierWithVersion); + if (existing) { + try { + await this.extensionsScanner.removeExtension(existing, 'existing'); + } catch (e) { + throw new Error(nls.localize('restartCode', "Please restart VS Code before reinstalling {0}.", manifest.displayName || manifest.name)); + } + } + } + + this.logService.info('Installing the extension:', identifier.id); + this._onInstallExtension.fire({ identifier, zipPath }); + + // {{SQL CARBON EDIT}} + // Until there's a gallery for SQL Ops Studio, skip retrieving the metadata from the gallery + return this.installExtension({ zipPath, identifierWithVersion, metadata: { isMachineScoped } }, token) + .then( + local => { + this.reportTelemetry(this.getTelemetryEvent(InstallOperation.Install), getLocalExtensionTelemetryData(local), new Date().getTime() - startTime, void 0); + this._onDidInstallExtension.fire({ identifier, zipPath, local, operation: InstallOperation.Install }); + return local; + }, + error => { this._onDidInstallExtension.fire({ identifier, zipPath, error, operation: InstallOperation.Install }); return Promise.reject(error); } + ); + // {{ SQL CARBON EDIT }} + // let metadata: IGalleryMetadata | undefined; + // try { + // metadata = await this.getGalleryMetadata(getGalleryExtensionId(manifest.publisher, manifest.name)); + // } catch (e) { /* Ignore */ } + // try { + // const local = await this.installFromZipPath(identifierWithVersion, zipPath, isMachineScoped ? { ...(metadata || {}), isMachineScoped } : metadata, operation, token); + // this.logService.info('Successfully installed the extension:', identifier.id); + // return local; + // } catch (e) { + // this.logService.error('Failed to install the extension:', identifier.id, e.message); + // throw e; + // } }); } - private downloadVsix(vsix: URI): Promise { + private async downloadVsix(vsix: URI): Promise { if (vsix.scheme === Schemas.file) { - return Promise.resolve(vsix); + return vsix; } if (!this.downloadService) { throw new Error('Download service is not available'); } - const downloadedLocation = path.join(tmpdir(), generateUuid()); - return this.downloadService.download(vsix, URI.file(downloadedLocation)).then(() => URI.file(downloadedLocation)); + + const downloadedLocation = URI.file(path.join(tmpdir(), generateUuid())); + await this.downloadService.download(vsix, downloadedLocation); + return downloadedLocation; } - /*private installFromZipPath(identifierWithVersion: ExtensionIdentifierWithVersion, zipPath: string, metadata: IMetadata | undefined, operation: InstallOperation, token: CancellationToken): Promise { {{SQL CARBON EDIT}} - return this.toNonCancellablePromise(this.installExtension({ zipPath, identifierWithVersion, metadata }, token) - .then(local => this.installDependenciesAndPackExtensions(local, undefined) - .then( - () => local, - error => { - if (isNonEmptyArray(local.manifest.extensionDependencies)) { - this.logService.warn(`Cannot install dependencies of extension:`, local.identifier.id, error.message); - } - if (isNonEmptyArray(local.manifest.extensionPack)) { - this.logService.warn(`Cannot install packed extensions of extension:`, local.identifier.id, error.message); - } - return local; - })) - .then( - local => { this._onDidInstallExtension.fire({ identifier: identifierWithVersion.identifier, zipPath, local, operation }); return local; }, - error => { this._onDidInstallExtension.fire({ identifier: identifierWithVersion.identifier, zipPath, operation, error }); return Promise.reject(error); } - )); + // {{ SQL CARBON EDIT }} + /*private async installFromZipPath(identifierWithVersion: ExtensionIdentifierWithVersion, zipPath: string, metadata: IMetadata | undefined, operation: InstallOperation, token: CancellationToken): Promise { + try { + const local = await this.installExtension({ zipPath, identifierWithVersion, metadata }, token); + try { + await this.installDependenciesAndPackExtensions(local, undefined); + } catch (error) { + if (isNonEmptyArray(local.manifest.extensionDependencies)) { + this.logService.warn(`Cannot install dependencies of extension:`, local.identifier.id, error.message); + } + if (isNonEmptyArray(local.manifest.extensionPack)) { + this.logService.warn(`Cannot install packed extensions of extension:`, local.identifier.id, error.message); + } + } + this._onDidInstallExtension.fire({ identifier: identifierWithVersion.identifier, zipPath, local, operation }); + return local; + } catch (error) { + this._onDidInstallExtension.fire({ identifier: identifierWithVersion.identifier, zipPath, operation, error }); + throw error; + } }*/ async canInstall(extension: IGalleryExtension): Promise { @@ -259,17 +264,68 @@ export class ExtensionManagementService extends Disposable implements IExtension async installFromGallery(extension: IGalleryExtension, isMachineScoped?: boolean): Promise { if (!this.galleryService.isEnabled()) { - return Promise.reject(new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled"))); + throw new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled")); } - const startTime = new Date().getTime(); - const onDidInstallExtensionSuccess = (extension: IGalleryExtension, operation: InstallOperation, local: ILocalExtension) => { + try { + extension = await this.checkAndGetCompatibleVersion(extension); + } catch (error) { + const errorCode = error && (error).code ? (error).code : ERROR_UNKNOWN; + this.logService.error(`Failed to install extension:`, extension.identifier.id, error ? error.message : errorCode); + this.reportTelemetry(this.getTelemetryEvent(InstallOperation.Install), getGalleryExtensionTelemetryData(extension), undefined, error); + if (error instanceof Error) { + error.name = errorCode; + } + throw error; + } + + const key = new ExtensionIdentifierWithVersion(extension.identifier, extension.version).key(); + let cancellablePromise = this.installingExtensions.get(key); + if (!cancellablePromise) { + cancellablePromise = createCancelablePromise(token => this.doInstallFromGallery(extension, !!isMachineScoped, token)); + this.installingExtensions.set(key, cancellablePromise); + cancellablePromise.finally(() => this.installingExtensions.delete(key)); + } + + return cancellablePromise; + } + + private async doInstallFromGallery(extension: IGalleryExtension, isMachineScoped: boolean, token: CancellationToken): Promise { + const startTime = new Date().getTime(); + let operation: InstallOperation = InstallOperation.Install; + this.logService.info('Installing extension:', extension.identifier.id); + this._onInstallExtension.fire({ identifier: extension.identifier, gallery: extension }); + + try { + const installed = await this.getInstalled(ExtensionType.User); + const existingExtension = installed.find(i => areSameExtensions(i.identifier, extension.identifier)); + if (existingExtension) { + operation = InstallOperation.Update; + } + + const installableExtension = await this.downloadInstallableExtension(extension, operation); + installableExtension.metadata.isMachineScoped = isMachineScoped || existingExtension?.isMachineScoped; + const local = await this.installExtension(installableExtension, token); + + try { await this.extensionsDownloader.delete(URI.file(installableExtension.zipPath)); } catch (error) { /* Ignore */ } + + try { + await this.installDependenciesAndPackExtensions(local, existingExtension); + } catch (error) { + try { await this.uninstall(local); } catch (error) { /* Ignore */ } + throw error; + } + + if (existingExtension && semver.neq(existingExtension.manifest.version, extension.version)) { + await this.setUninstalled(existingExtension); + } + this.logService.info(`Extensions installed successfully:`, extension.identifier.id); this._onDidInstallExtension.fire({ identifier: extension.identifier, gallery: extension, local, operation }); this.reportTelemetry(this.getTelemetryEvent(operation), getGalleryExtensionTelemetryData(extension), new Date().getTime() - startTime, undefined); - }; + return local; - const onDidInstallExtensionFailure = (extension: IGalleryExtension, operation: InstallOperation, error: Error) => { + } catch (error) { const errorCode = error && (error).code ? (error).code : ERROR_UNKNOWN; this.logService.error(`Failed to install extension:`, extension.identifier.id, error ? error.message : errorCode); this._onDidInstallExtension.fire({ identifier: extension.identifier, gallery: extension, operation, error: errorCode }); @@ -277,162 +333,107 @@ export class ExtensionManagementService extends Disposable implements IExtension if (error instanceof Error) { error.name = errorCode; } - }; - - try { - extension = await this.checkAndGetCompatibleVersion(extension); - } catch (error) { - onDidInstallExtensionFailure(extension, InstallOperation.Install, error); - return Promise.reject(error); + throw error; } - - const key = new ExtensionIdentifierWithVersion(extension.identifier, extension.version).key(); - let cancellablePromise = this.installingExtensions.get(key); - if (!cancellablePromise) { - - this.logService.info('Installing extension:', extension.identifier.id); - this._onInstallExtension.fire({ identifier: extension.identifier, gallery: extension }); - - let operation: InstallOperation = InstallOperation.Install; - let cancellationToken: CancellationToken, successCallback: (local: ILocalExtension) => void, errorCallback: (e?: any) => any | null; - cancellablePromise = createCancelablePromise(token => { cancellationToken = token; return new Promise((c, e) => { successCallback = c; errorCallback = e; }); }); - this.installingExtensions.set(key, cancellablePromise); - try { - const installed = await this.getInstalled(ExtensionType.User); - const existingExtension = installed.find(i => areSameExtensions(i.identifier, extension.identifier)); - if (existingExtension) { - operation = InstallOperation.Update; - } - - this.downloadInstallableExtension(extension, operation) - .then(installableExtension => { - installableExtension.metadata.isMachineScoped = isMachineScoped || existingExtension?.isMachineScoped; - return this.installExtension(installableExtension, cancellationToken) - .then(local => this.extensionsDownloader.delete(URI.file(installableExtension.zipPath)).finally(() => { }).then(() => local)); - }) - .then(local => this.installDependenciesAndPackExtensions(local, existingExtension) - .then(() => local, error => this.uninstall(local).then(() => Promise.reject(error), () => Promise.reject(error)))) - .then( - async local => { - if (existingExtension && semver.neq(existingExtension.manifest.version, extension.version)) { - await this.setUninstalled(existingExtension); - } - this.installingExtensions.delete(key); - onDidInstallExtensionSuccess(extension, operation, local); - successCallback(local); - }, - error => { - this.installingExtensions.delete(key); - onDidInstallExtensionFailure(extension, operation, error); - errorCallback(error); - }); - - } catch (error) { - this.installingExtensions.delete(key); - onDidInstallExtensionFailure(extension, operation, error); - return Promise.reject(error); - } - - } - - return cancellablePromise; } private async checkAndGetCompatibleVersion(extension: IGalleryExtension): Promise { if (await this.isMalicious(extension)) { - return Promise.reject(new ExtensionManagementError(nls.localize('malicious extension', "Can't install extension since it was reported to be problematic."), INSTALL_ERROR_MALICIOUS)); + throw new ExtensionManagementError(nls.localize('malicious extension', "Can't install extension since it was reported to be problematic."), INSTALL_ERROR_MALICIOUS); } const compatibleExtension = await this.galleryService.getCompatibleExtension(extension); - if (!compatibleExtension) { - return Promise.reject(new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Unable to install '{0}' extension because it is not compatible with the current version of VS Code (version {1}).", extension.identifier.id, product.version), INSTALL_ERROR_INCOMPATIBLE)); + throw new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Unable to install '{0}' extension because it is not compatible with the current version of VS Code (version {1}).", extension.identifier.id, product.version), INSTALL_ERROR_INCOMPATIBLE); } return compatibleExtension; } - reinstallFromGallery(extension: ILocalExtension): Promise { + async reinstallFromGallery(extension: ILocalExtension): Promise { this.logService.trace('ExtensionManagementService#reinstallFromGallery', extension.identifier.id); if (!this.galleryService.isEnabled()) { - return Promise.reject(new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled"))); + throw new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled")); } - return this.findGalleryExtension(extension) - .then(galleryExtension => { - if (galleryExtension) { - return this.setUninstalled(extension) - .then(() => this.extensionsScanner.removeUninstalledExtension(extension) - .then( - () => this.installFromGallery(galleryExtension).then(), - e => Promise.reject(new Error(nls.localize('removeError', "Error while removing the extension: {0}. Please Quit and Start VS Code before trying again.", toErrorMessage(e)))))); - } - return Promise.reject(new Error(nls.localize('Not a Marketplace extension', "Only Marketplace Extensions can be reinstalled"))); - }); + + const galleryExtension = await this.findGalleryExtension(extension); + if (!galleryExtension) { + throw new Error(nls.localize('Not a Marketplace extension', "Only Marketplace Extensions can be reinstalled")); + } + + await this.setUninstalled(extension); + try { + await this.extensionsScanner.removeUninstalledExtension(extension); + } catch (e) { + throw new Error(nls.localize('removeError', "Error while removing the extension: {0}. Please Quit and Start VS Code before trying again.", toErrorMessage(e))); + } + + await this.installFromGallery(galleryExtension); } private getTelemetryEvent(operation: InstallOperation): string { return operation === InstallOperation.Update ? 'extensionGallery:update' : 'extensionGallery:install'; } - private isMalicious(extension: IGalleryExtension): Promise { - return this.getExtensionsReport() - .then(report => getMaliciousExtensionsSet(report).has(extension.identifier.id)); + private async isMalicious(extension: IGalleryExtension): Promise { + const report = await this.getExtensionsReport(); + return getMaliciousExtensionsSet(report).has(extension.identifier.id); } - private downloadInstallableExtension(extension: IGalleryExtension, operation: InstallOperation): Promise> { + private async downloadInstallableExtension(extension: IGalleryExtension, operation: InstallOperation): Promise> { const metadata = { id: extension.identifier.uuid, publisherId: extension.publisherId, publisherDisplayName: extension.publisherDisplayName, }; - this.logService.trace('Started downloading extension:', extension.identifier.id); - return this.extensionsDownloader.downloadExtension(extension, operation) - .then( - zip => { - const zipPath = zip.fsPath; - this.logService.info('Downloaded extension:', extension.identifier.id, zipPath); - return getManifest(zipPath) - .then( - manifest => (>{ zipPath, identifierWithVersion: new ExtensionIdentifierWithVersion(extension.identifier, manifest.version), metadata }), - error => Promise.reject(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_VALIDATING)) - ); - }, - error => Promise.reject(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_DOWNLOADING))); + let zipPath; + try { + this.logService.trace('Started downloading extension:', extension.identifier.id); + const zip = await this.extensionsDownloader.downloadExtension(extension, operation); + this.logService.info('Downloaded extension:', extension.identifier.id, zipPath); + zipPath = zip.fsPath; + } catch (error) { + throw new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_DOWNLOADING); + } + + try { + const manifest = await getManifest(zipPath); + return (>{ zipPath, identifierWithVersion: new ExtensionIdentifierWithVersion(extension.identifier, manifest.version), metadata }); + } catch (error) { + throw new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_VALIDATING); + } } - private installExtension(installableExtension: InstallableExtension, token: CancellationToken): Promise { - return this.unsetUninstalledAndGetLocal(installableExtension.identifierWithVersion) - .then( - local => { - if (local) { - return local; - } - return this.extractAndInstall(installableExtension, token); - }, - e => { - if (isMacintosh) { - return Promise.reject(new ExtensionManagementError(nls.localize('quitCode', "Unable to install the extension. Please Quit and Start VS Code before reinstalling."), INSTALL_ERROR_UNSET_UNINSTALLED)); - } - return Promise.reject(new ExtensionManagementError(nls.localize('exitCode', "Unable to install the extension. Please Exit and Start VS Code before reinstalling."), INSTALL_ERROR_UNSET_UNINSTALLED)); - }); + private async installExtension(installableExtension: InstallableExtension, token: CancellationToken): Promise { + try { + const local = await this.unsetUninstalledAndGetLocal(installableExtension.identifierWithVersion); + if (local) { + return local; + } + } catch (e) { + if (isMacintosh) { + throw new ExtensionManagementError(nls.localize('quitCode', "Unable to install the extension. Please Quit and Start VS Code before reinstalling."), INSTALL_ERROR_UNSET_UNINSTALLED); + } else { + throw new ExtensionManagementError(nls.localize('exitCode', "Unable to install the extension. Please Exit and Start VS Code before reinstalling."), INSTALL_ERROR_UNSET_UNINSTALLED); + } + } + return this.extractAndInstall(installableExtension, token); } - private unsetUninstalledAndGetLocal(identifierWithVersion: ExtensionIdentifierWithVersion): Promise { - return this.isUninstalled(identifierWithVersion) - .then(isUninstalled => { - if (isUninstalled) { - this.logService.trace('Removing the extension from uninstalled list:', identifierWithVersion.identifier.id); - // If the same version of extension is marked as uninstalled, remove it from there and return the local. - return this.unsetUninstalled(identifierWithVersion) - .then(() => { - this.logService.info('Removed the extension from uninstalled list:', identifierWithVersion.identifier.id); - return this.getInstalled(ExtensionType.User); - }) - .then(installed => installed.filter(i => new ExtensionIdentifierWithVersion(i.identifier, i.manifest.version).equals(identifierWithVersion))[0]); - } - return null; - }); + private async unsetUninstalledAndGetLocal(identifierWithVersion: ExtensionIdentifierWithVersion): Promise { + const isUninstalled = await this.isUninstalled(identifierWithVersion); + if (!isUninstalled) { + return null; + } + + this.logService.trace('Removing the extension from uninstalled list:', identifierWithVersion.identifier.id); + // If the same version of extension is marked as uninstalled, remove it from there and return the local. + await this.unsetUninstalled(identifierWithVersion); + this.logService.info('Removed the extension from uninstalled list:', identifierWithVersion.identifier.id); + + const installed = await this.getInstalled(ExtensionType.User); + return installed.find(i => new ExtensionIdentifierWithVersion(i.identifier, i.manifest.version).equals(identifierWithVersion)) || null; } private async extractAndInstall({ zipPath, identifierWithVersion, metadata }: InstallableExtension, token: CancellationToken): Promise { @@ -446,57 +447,56 @@ export class ExtensionManagementService extends Disposable implements IExtension } private async installDependenciesAndPackExtensions(installed: ILocalExtension, existing: ILocalExtension | undefined): Promise { - if (this.galleryService.isEnabled()) { - const dependenciesAndPackExtensions: string[] = installed.manifest.extensionDependencies || []; - if (installed.manifest.extensionPack) { - for (const extension of installed.manifest.extensionPack) { - // add only those extensions which are new in currently installed extension - if (!(existing && existing.manifest.extensionPack && existing.manifest.extensionPack.some(old => areSameExtensions({ id: old }, { id: extension })))) { - if (dependenciesAndPackExtensions.every(e => !areSameExtensions({ id: e }, { id: extension }))) { - dependenciesAndPackExtensions.push(extension); - } + if (!this.galleryService.isEnabled()) { + return; + } + const dependenciesAndPackExtensions: string[] = installed.manifest.extensionDependencies || []; + if (installed.manifest.extensionPack) { + for (const extension of installed.manifest.extensionPack) { + // add only those extensions which are new in currently installed extension + if (!(existing && existing.manifest.extensionPack && existing.manifest.extensionPack.some(old => areSameExtensions({ id: old }, { id: extension })))) { + if (dependenciesAndPackExtensions.every(e => !areSameExtensions({ id: e }, { id: extension }))) { + dependenciesAndPackExtensions.push(extension); } } } - if (dependenciesAndPackExtensions.length) { - return this.getInstalled() - .then(installed => { - // filter out installed extensions - const names = dependenciesAndPackExtensions.filter(id => installed.every(({ identifier: galleryIdentifier }) => !areSameExtensions(galleryIdentifier, { id }))); - if (names.length) { - return this.galleryService.query({ names, pageSize: dependenciesAndPackExtensions.length }, CancellationToken.None) - .then(galleryResult => { - const extensionsToInstall = galleryResult.firstPage; - return Promise.all(extensionsToInstall.map(e => this.installFromGallery(e))) - .then(undefined, errors => this.rollback(extensionsToInstall).then(() => Promise.reject(errors), () => Promise.reject(errors))); - }); - } - return undefined; // {{SQL CARBON EDIT}} strict-null-checks - }); + } + if (dependenciesAndPackExtensions.length) { + const installed = await this.getInstalled(); + // filter out installed extensions + const names = dependenciesAndPackExtensions.filter(id => installed.every(({ identifier: galleryIdentifier }) => !areSameExtensions(galleryIdentifier, { id }))); + if (names.length) { + const galleryResult = await this.galleryService.query({ names, pageSize: dependenciesAndPackExtensions.length }, CancellationToken.None); + const extensionsToInstall = galleryResult.firstPage; + try { + await Promise.all(extensionsToInstall.map(e => this.installFromGallery(e))); + } catch (error) { + try { await this.rollback(extensionsToInstall); } catch (e) { /* ignore */ } + throw error; + } } } - return Promise.resolve(undefined); } - private rollback(extensions: IGalleryExtension[]): Promise { - return this.getInstalled(ExtensionType.User) - .then(installed => - Promise.all(installed.filter(local => extensions.some(galleryExtension => new ExtensionIdentifierWithVersion(local.identifier, local.manifest.version).equals(new ExtensionIdentifierWithVersion(galleryExtension.identifier, galleryExtension.version)))) // Check with version because we want to rollback the exact version - .map(local => this.uninstall(local)))) - .then(() => undefined, () => undefined); + private async rollback(extensions: IGalleryExtension[]): Promise { + const installed = await this.getInstalled(ExtensionType.User); + const extensionsToUninstall = installed.filter(local => extensions.some(galleryExtension => new ExtensionIdentifierWithVersion(local.identifier, local.manifest.version).equals(new ExtensionIdentifierWithVersion(galleryExtension.identifier, galleryExtension.version)))); // Check with version because we want to rollback the exact version + await Promise.all(extensionsToUninstall.map(local => this.uninstall(local))); } - uninstall(extension: ILocalExtension): Promise { + async uninstall(extension: ILocalExtension): Promise { this.logService.trace('ExtensionManagementService#uninstall', extension.identifier.id); - return this.toNonCancellablePromise(this.getInstalled(ExtensionType.User) - .then(installed => { - const extensionToUninstall = installed.filter(e => areSameExtensions(e.identifier, extension.identifier))[0]; - if (extensionToUninstall) { - return this.checkForDependenciesAndUninstall(extensionToUninstall, installed).then(undefined, error => Promise.reject(this.joinErrors(error))); - } else { - return Promise.reject(new Error(nls.localize('notInstalled', "Extension '{0}' is not installed.", extension.manifest.displayName || extension.manifest.name))); - } - })); + const installed = await this.getInstalled(ExtensionType.User); + const extensionToUninstall = installed.find(e => areSameExtensions(e.identifier, extension.identifier)); + if (!extensionToUninstall) { + throw new Error(nls.localize('notInstalled', "Extension '{0}' is not installed.", extension.manifest.displayName || extension.manifest.name)); + } + + try { + await this.checkForDependenciesAndUninstall(extensionToUninstall, installed); + } catch (error) { + throw this.joinErrors(error); + } } async updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise { @@ -506,25 +506,28 @@ export class ExtensionManagementService extends Disposable implements IExtension return local; } - /*private getGalleryMetadata(extensionName: string): Promise { {{SQL CARBON EDIT}} - return this.findGalleryExtensionByName(extensionName) - .then(galleryExtension => galleryExtension ? { id: galleryExtension.identifier.uuid, publisherDisplayName: galleryExtension.publisherDisplayName, publisherId: galleryExtension.publisherId } : undefined); + // {{ SQL CARBON EDIT }} + /*private async getGalleryMetadata(extensionName: string): Promise { + const galleryExtension = await this.findGalleryExtensionByName(extensionName); + return galleryExtension ? { id: galleryExtension.identifier.uuid, publisherDisplayName: galleryExtension.publisherDisplayName, publisherId: galleryExtension.publisherId } : undefined; }*/ - private findGalleryExtension(local: ILocalExtension): Promise { + private async findGalleryExtension(local: ILocalExtension): Promise { if (local.identifier.uuid) { - return this.findGalleryExtensionById(local.identifier.uuid) - .then(galleryExtension => galleryExtension ? galleryExtension : this.findGalleryExtensionByName(local.identifier.id)); + const galleryExtension = await this.findGalleryExtensionById(local.identifier.uuid); + return galleryExtension ? galleryExtension : this.findGalleryExtensionByName(local.identifier.id); } return this.findGalleryExtensionByName(local.identifier.id); } - private findGalleryExtensionById(uuid: string): Promise { - return this.galleryService.query({ ids: [uuid], pageSize: 1 }, CancellationToken.None).then(galleryResult => galleryResult.firstPage[0]); + private async findGalleryExtensionById(uuid: string): Promise { + const galleryResult = await this.galleryService.query({ ids: [uuid], pageSize: 1 }, CancellationToken.None); + return galleryResult.firstPage[0]; } - private findGalleryExtensionByName(name: string): Promise { - return this.galleryService.query({ names: [name], pageSize: 1 }, CancellationToken.None).then(galleryResult => galleryResult.firstPage[0]); + private async findGalleryExtensionByName(name: string): Promise { + const galleryResult = await this.galleryService.query({ names: [name], pageSize: 1 }, CancellationToken.None); + return galleryResult.firstPage[0]; } private joinErrors(errorOrErrors: (Error | string) | (Array)): Error { @@ -537,20 +540,20 @@ export class ExtensionManagementService extends Disposable implements IExtension }, new Error('')); } - private checkForDependenciesAndUninstall(extension: ILocalExtension, installed: ILocalExtension[]): Promise { - return this.preUninstallExtension(extension) - .then(() => { - const packedExtensions = this.getAllPackExtensionsToUninstall(extension, installed); - if (packedExtensions.length) { - return this.uninstallExtensions(extension, packedExtensions, installed); - } - return this.uninstallExtensions(extension, [], installed); - }) - .then(() => this.postUninstallExtension(extension), - error => { - this.postUninstallExtension(extension, new ExtensionManagementError(error instanceof Error ? error.message : error, INSTALL_ERROR_LOCAL)); - return Promise.reject(error); - }); + private async checkForDependenciesAndUninstall(extension: ILocalExtension, installed: ILocalExtension[]): Promise { + try { + await this.preUninstallExtension(extension); + const packedExtensions = this.getAllPackExtensionsToUninstall(extension, installed); + if (packedExtensions.length) { + await this.uninstallExtensions(extension, packedExtensions, installed); + } else { + await this.uninstallExtensions(extension, [], installed); + } + } catch (error) { + await this.postUninstallExtension(extension, new ExtensionManagementError(error instanceof Error ? error.message : error, INSTALL_ERROR_LOCAL)); + throw error; + } + await this.postUninstallExtension(extension); } private async uninstallExtensions(extension: ILocalExtension, otherExtensionsToUninstall: ILocalExtension[], installed: ILocalExtension[]): Promise { @@ -621,33 +624,36 @@ export class ExtensionManagementService extends Disposable implements IExtension return installed.filter(e => e.manifest.extensionDependencies && e.manifest.extensionDependencies.some(id => areSameExtensions({ id }, extension.identifier))); } - private doUninstall(extension: ILocalExtension): Promise { - return this.preUninstallExtension(extension) - .then(() => this.uninstallExtension(extension)) - .then(() => this.postUninstallExtension(extension), - error => { - this.postUninstallExtension(extension, new ExtensionManagementError(error instanceof Error ? error.message : error, INSTALL_ERROR_LOCAL)); - return Promise.reject(error); - }); + private async doUninstall(extension: ILocalExtension): Promise { + try { + await this.preUninstallExtension(extension); + await this.uninstallExtension(extension); + } catch (error) { + await this.postUninstallExtension(extension, new ExtensionManagementError(error instanceof Error ? error.message : error, INSTALL_ERROR_LOCAL)); + throw error; + } + await this.postUninstallExtension(extension); } - private preUninstallExtension(extension: ILocalExtension): Promise { - return Promise.resolve(pfs.exists(extension.location.fsPath)) - .then(exists => exists ? null : Promise.reject(new Error(nls.localize('notExists', "Could not find extension")))) - .then(() => { - this.logService.info('Uninstalling extension:', extension.identifier.id); - this._onUninstallExtension.fire(extension.identifier); - }); + private async preUninstallExtension(extension: ILocalExtension): Promise { + const exists = await pfs.exists(extension.location.fsPath); + if (!exists) { + throw new Error(nls.localize('notExists', "Could not find extension")); + } + this.logService.info('Uninstalling extension:', extension.identifier.id); + this._onUninstallExtension.fire(extension.identifier); } - private uninstallExtension(local: ILocalExtension): Promise { + private async uninstallExtension(local: ILocalExtension): Promise { let promise = this.uninstallingExtensions.get(local.identifier.id); if (!promise) { // Set all versions of the extension as uninstalled - promise = createCancelablePromise(token => this.extensionsScanner.scanUserExtensions(false) - .then(userExtensions => this.setUninstalled(...userExtensions.filter(u => areSameExtensions(u.identifier, local.identifier)))) - .then(() => { this.uninstallingExtensions.delete(local.identifier.id); })); + promise = createCancelablePromise(async () => { + const userExtensions = await this.extensionsScanner.scanUserExtensions(false); + await this.setUninstalled(...userExtensions.filter(u => areSameExtensions(u.identifier, local.identifier))); + }); this.uninstallingExtensions.set(local.identifier.id, promise); + promise.finally(() => this.uninstallingExtensions.delete(local.identifier.id)); } return promise; } @@ -659,7 +665,9 @@ export class ExtensionManagementService extends Disposable implements IExtension this.logService.info('Successfully uninstalled extension:', extension.identifier.id); // only report if extension has a mapped gallery extension. UUID identifies the gallery extension. if (extension.identifier.uuid) { - await this.galleryService.reportStatistic(extension.manifest.publisher, extension.manifest.name, extension.manifest.version, StatisticType.Uninstall); + try { + await this.galleryService.reportStatistic(extension.manifest.publisher, extension.manifest.name, extension.manifest.version, StatisticType.Uninstall); + } catch (error) { /* ignore */ } } } this.reportTelemetry('extensionGallery:uninstall', getLocalExtensionTelemetryData(extension), undefined, error); @@ -675,8 +683,9 @@ export class ExtensionManagementService extends Disposable implements IExtension return this.extensionsScanner.cleanUp(); } - private isUninstalled(identifier: ExtensionIdentifierWithVersion): Promise { - return this.filterUninstalled(identifier).then(uninstalled => uninstalled.length === 1); + private async isUninstalled(identifier: ExtensionIdentifierWithVersion): Promise { + const uninstalled = await this.filterUninstalled(identifier); + return uninstalled.length === 1; } private filterUninstalled(...identifiers: ExtensionIdentifierWithVersion[]): Promise { @@ -693,7 +702,10 @@ export class ExtensionManagementService extends Disposable implements IExtension private setUninstalled(...extensions: ILocalExtension[]): Promise<{ [id: string]: boolean }> { const ids: ExtensionIdentifierWithVersion[] = extensions.map(e => new ExtensionIdentifierWithVersion(e.identifier, e.manifest.version)); - return this.extensionsScanner.withUninstalledExtensions(uninstalled => assign(uninstalled, ids.reduce((result, id) => { result[id.key()] = true; return result; }, {} as { [id: string]: boolean }))); + return this.extensionsScanner.withUninstalledExtensions(uninstalled => { + ids.forEach(id => uninstalled[id.key()] = true); + return uninstalled; + }); } private unsetUninstalled(extensionIdentifier: ExtensionIdentifierWithVersion): Promise { @@ -711,21 +723,16 @@ export class ExtensionManagementService extends Disposable implements IExtension return this.reportedExtensions; } - private updateReportCache(): Promise { - this.logService.trace('ExtensionManagementService.refreshReportedCache'); - - return this.galleryService.getExtensionsReport() - .then(result => { - this.logService.trace(`ExtensionManagementService.refreshReportedCache - got ${result.length} reported extensions from service`); - return result; - }, err => { - this.logService.trace('ExtensionManagementService.refreshReportedCache - failed to get extension report'); - return []; - }); - } - - private toNonCancellablePromise(promise: Promise): Promise { - return new Promise((c, e) => promise.then(result => c(result), error => e(error))); + private async updateReportCache(): Promise { + try { + this.logService.trace('ExtensionManagementService.refreshReportedCache'); + const result = await this.galleryService.getExtensionsReport(); + this.logService.trace(`ExtensionManagementService.refreshReportedCache - got ${result.length} reported extensions from service`); + return result; + } catch (err) { + this.logService.trace('ExtensionManagementService.refreshReportedCache - failed to get extension report'); + return []; + } } private reportTelemetry(eventName: string, extensionData: any, duration?: number, error?: Error): void { @@ -761,6 +768,6 @@ export class ExtensionManagementService extends Disposable implements IExtension ] } */ - this.telemetryService.publicLogError(eventName, assign(extensionData, { success: !error, duration, errorcode })); + this.telemetryService.publicLogError(eventName, { ...extensionData, success: !error, duration, errorcode }); } } diff --git a/src/vs/platform/extensionManagement/node/extensionTipsService.ts b/src/vs/platform/extensionManagement/node/extensionTipsService.ts index a9fad7e688..855fd030dd 100644 --- a/src/vs/platform/extensionManagement/node/extensionTipsService.ts +++ b/src/vs/platform/extensionManagement/node/extensionTipsService.ts @@ -6,9 +6,8 @@ import { URI } from 'vs/base/common/uri'; import { join, } from 'vs/base/common/path'; import { IProductService } from 'vs/platform/product/common/productService'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { env as processEnv } from 'vs/base/common/process'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { IFileService } from 'vs/platform/files/common/files'; import { isWindows } from 'vs/base/common/platform'; import { isNonEmptyArray } from 'vs/base/common/arrays'; diff --git a/src/vs/platform/extensionManagement/node/extensionsManifestCache.ts b/src/vs/platform/extensionManagement/node/extensionsManifestCache.ts index 1ac396baa8..b272745b99 100644 --- a/src/vs/platform/extensionManagement/node/extensionsManifestCache.ts +++ b/src/vs/platform/extensionManagement/node/extensionsManifestCache.ts @@ -5,7 +5,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { join } from 'vs/base/common/path'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { IExtensionManagementService, DidInstallExtensionEvent, DidUninstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement'; import { MANIFEST_CACHE_FOLDER, USER_MANIFEST_CACHE_FILE } from 'vs/platform/extensions/common/extensions'; import * as pfs from 'vs/base/node/pfs'; diff --git a/src/vs/platform/extensionManagement/node/extensionsScanner.ts b/src/vs/platform/extensionManagement/node/extensionsScanner.ts index 82bac80e86..32b9fc6d6b 100644 --- a/src/vs/platform/extensionManagement/node/extensionsScanner.ts +++ b/src/vs/platform/extensionManagement/node/extensionsScanner.ts @@ -13,8 +13,7 @@ import { ExtensionType, IExtensionManifest, IExtensionIdentifier } from 'vs/plat import { areSameExtensions, ExtensionIdentifierWithVersion, groupByExtension, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { Limiter, Queue } from 'vs/base/common/async'; import { URI } from 'vs/base/common/uri'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { localizeManifest } from 'vs/platform/extensionManagement/common/extensionNls'; import { localize } from 'vs/nls'; @@ -23,7 +22,7 @@ import { CancellationToken } from 'vscode'; import { extract, ExtractError } from 'vs/base/node/zip'; import { isWindows } from 'vs/base/common/platform'; import { flatten } from 'vs/base/common/arrays'; -import { assign } from 'vs/base/common/objects'; +import { IStringDictionary } from 'vs/base/common/collections'; const ERROR_SCANNING_SYS_EXTENSIONS = 'scanningSystem'; const ERROR_SCANNING_USER_EXTENSIONS = 'scanningUser'; @@ -32,6 +31,7 @@ const INSTALL_ERROR_DELETING = 'deleting'; const INSTALL_ERROR_RENAMING = 'renaming'; export type IMetadata = Partial; +type ILocalExtensionManifest = IExtensionManifest & { __metadata?: IMetadata }; export class ExtensionsScanner extends Disposable { @@ -69,7 +69,12 @@ export class ExtensionsScanner extends Disposable { promises.push(this.scanUserExtensions(true).then(null, e => Promise.reject(new ExtensionManagementError(this.joinErrors(e).message, ERROR_SCANNING_USER_EXTENSIONS)))); } - return Promise.all(promises).then(flatten, errors => Promise.reject(this.joinErrors(errors))); + try { + const result = await Promise.all(promises); + return flatten(result); + } catch (error) { + throw this.joinErrors(error); + } } async scanUserExtensions(excludeOutdated: boolean): Promise { @@ -134,7 +139,7 @@ export class ExtensionsScanner extends Disposable { const manifestPath = path.join(local.location.fsPath, 'package.json'); const raw = await pfs.readFile(manifestPath, 'utf8'); const { manifest } = await this.parseManifest(raw); - assign(manifest, { __metadata: metadata }); + (manifest as ILocalExtensionManifest).__metadata = metadata; await pfs.writeFile(manifestPath, JSON.stringify(manifest, null, '\t')); return local; } @@ -143,22 +148,33 @@ export class ExtensionsScanner extends Disposable { return this.withUninstalledExtensions(uninstalled => uninstalled); } - async withUninstalledExtensions(fn: (uninstalled: { [id: string]: boolean; }) => T): Promise { + async withUninstalledExtensions(fn: (uninstalled: IStringDictionary) => T): Promise { return this.uninstalledFileLimiter.queue(async () => { - let result: T | null = null; - return pfs.readFile(this.uninstalledPath, 'utf8') - .then(undefined, err => err.code === 'ENOENT' ? Promise.resolve('{}') : Promise.reject(err)) - .then<{ [id: string]: boolean }>(raw => { try { return JSON.parse(raw); } catch (e) { return {}; } }) - .then(uninstalled => { result = fn(uninstalled); return uninstalled; }) - .then(uninstalled => { - if (Object.keys(uninstalled).length === 0) { - return pfs.rimraf(this.uninstalledPath); - } else { - const raw = JSON.stringify(uninstalled); - return pfs.writeFile(this.uninstalledPath, raw); - } - }) - .then(() => result); + let raw: string | undefined; + try { + raw = await pfs.readFile(this.uninstalledPath, 'utf8'); + } catch (err) { + if (err.code !== 'ENOENT') { + throw err; + } + } + + let uninstalled = {}; + if (raw) { + try { + uninstalled = JSON.parse(raw); + } catch (e) { /* ignore */ } + } + + const result = fn(uninstalled); + + if (Object.keys(uninstalled).length) { + await pfs.writeFile(this.uninstalledPath, JSON.stringify(uninstalled)); + } else { + await pfs.rimraf(this.uninstalledPath); + } + + return result; }); } @@ -173,27 +189,35 @@ export class ExtensionsScanner extends Disposable { await this.withUninstalledExtensions(uninstalled => delete uninstalled[new ExtensionIdentifierWithVersion(extension.identifier, extension.manifest.version).key()]); } - private extractAtLocation(identifier: IExtensionIdentifier, zipPath: string, location: string, token: CancellationToken): Promise { + private async extractAtLocation(identifier: IExtensionIdentifier, zipPath: string, location: string, token: CancellationToken): Promise { this.logService.trace(`Started extracting the extension from ${zipPath} to ${location}`); - return pfs.rimraf(location) - .then( - () => extract(zipPath, location, { sourcePath: 'extension', overwrite: true }, token) - .then( - () => this.logService.info(`Extracted extension to ${location}:`, identifier.id), - e => pfs.rimraf(location).finally(() => { }) - .then(() => Promise.reject(new ExtensionManagementError(e.message, e instanceof ExtractError && e.type ? e.type : INSTALL_ERROR_EXTRACTING)))), - e => Promise.reject(new ExtensionManagementError(this.joinErrors(e).message, INSTALL_ERROR_DELETING))); + + // Clean the location + try { + await pfs.rimraf(location); + } catch (e) { + throw new ExtensionManagementError(this.joinErrors(e).message, INSTALL_ERROR_DELETING); + } + + try { + await extract(zipPath, location, { sourcePath: 'extension', overwrite: true }, token); + this.logService.info(`Extracted extension to ${location}:`, identifier.id); + } catch (e) { + try { await pfs.rimraf(location); } catch (e) { /* Ignore */ } + throw new ExtensionManagementError(e.message, e instanceof ExtractError && e.type ? e.type : INSTALL_ERROR_EXTRACTING); + } } - private rename(identifier: IExtensionIdentifier, extractPath: string, renamePath: string, retryUntil: number): Promise { - return pfs.rename(extractPath, renamePath) - .then(undefined, error => { - if (isWindows && error && error.code === 'EPERM' && Date.now() < retryUntil) { - this.logService.info(`Failed renaming ${extractPath} to ${renamePath} with 'EPERM' error. Trying again...`, identifier.id); - return this.rename(identifier, extractPath, renamePath, retryUntil); - } - return Promise.reject(new ExtensionManagementError(error.message || localize('renameError', "Unknown error while renaming {0} to {1}", extractPath, renamePath), error.code || INSTALL_ERROR_RENAMING)); - }); + private async rename(identifier: IExtensionIdentifier, extractPath: string, renamePath: string, retryUntil: number): Promise { + try { + await pfs.rename(extractPath, renamePath); + } catch (error) { + if (isWindows && error && error.code === 'EPERM' && Date.now() < retryUntil) { + this.logService.info(`Failed renaming ${extractPath} to ${renamePath} with 'EPERM' error. Trying again...`, identifier.id); + return this.rename(identifier, extractPath, renamePath, retryUntil); + } + throw new ExtensionManagementError(error.message || localize('renameError', "Unknown error while renaming {0} to {1}", extractPath, renamePath), error.code || INSTALL_ERROR_RENAMING); + } } private async scanSystemExtensions(): Promise { diff --git a/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts b/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts index c71f948564..f4aac4fc3c 100644 --- a/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts +++ b/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts @@ -53,7 +53,7 @@ suite('Extension Gallery Service', () => { test('marketplace machine id', () => { const args = ['--user-data-dir', marketplaceHome]; - const environmentService = new EnvironmentService(parseArgs(args, OPTIONS), process.execPath); + const environmentService = new EnvironmentService(parseArgs(args, OPTIONS)); const storageService: IStorageService = new TestStorageService(); return resolveMarketplaceHeaders(product.version, environmentService, fileService, storageService).then(headers => { diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 0c5cbba541..847c4df38b 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -856,6 +856,7 @@ export function whenProviderRegistered(file: URI, fileService: IFileService): Pr if (fileService.canHandleResource(URI.from({ scheme: file.scheme }))) { return Promise.resolve(); } + return new Promise((c, e) => { const disposable = fileService.onDidChangeFileSystemProviderRegistrations(e => { if (e.scheme === file.scheme && e.added) { diff --git a/src/vs/platform/files/node/watcher/nodejs/watcherService.ts b/src/vs/platform/files/node/watcher/nodejs/watcherService.ts index 34ab3f2ebb..f2d7b1988b 100644 --- a/src/vs/platform/files/node/watcher/nodejs/watcherService.ts +++ b/src/vs/platform/files/node/watcher/nodejs/watcherService.ts @@ -85,7 +85,7 @@ export class FileWatcher extends Disposable { } // Handle emit through delayer to accommodate for bulk changes and thus reduce spam - this.fileChangesDelayer.trigger(() => { + this.fileChangesDelayer.trigger(async () => { const fileChanges = this.fileChangesBuffer; this.fileChangesBuffer = []; @@ -103,8 +103,6 @@ export class FileWatcher extends Disposable { if (normalizedFileChanges.length > 0) { this.onDidFilesChange(normalizedFileChanges); } - - return Promise.resolve(); }); } diff --git a/src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts b/src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts index c14f08cff4..f05401b386 100644 --- a/src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts +++ b/src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts @@ -22,8 +22,8 @@ nsfwActionToRawChangeType[nsfw.actions.MODIFIED] = FileChangeType.UPDATED; nsfwActionToRawChangeType[nsfw.actions.DELETED] = FileChangeType.DELETED; interface IWatcherObjet { - start(): any; - stop(): any; + start(): void; + stop(): void; } interface IPathWatcher { @@ -142,7 +142,7 @@ export class NsfwWatcherService implements IWatcherService { } // Delay and send buffer - fileEventDelayer.trigger(() => { + fileEventDelayer.trigger(async () => { const events = undeliveredFileEvents; undeliveredFileEvents = []; @@ -169,8 +169,6 @@ export class NsfwWatcherService implements IWatcherService { this.log(` >> normalized ${r.type === FileChangeType.ADDED ? '[ADDED]' : r.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${r.path}`); }); } - - return Promise.resolve(undefined); }); }).then(watcher => { this._pathWatchers[request.path].watcher = watcher; @@ -180,8 +178,7 @@ export class NsfwWatcherService implements IWatcherService { }); } - public setRoots(roots: IWatcherRequest[]): Promise { - const promises: Promise[] = []; + async setRoots(roots: IWatcherRequest[]): Promise { const normalizedRoots = this._normalizeRoots(roots); // Gather roots that are not currently being watched @@ -214,23 +211,19 @@ export class NsfwWatcherService implements IWatcherService { this._pathWatchers[root.path].ignored = Array.isArray(root.excludes) ? root.excludes.map(ignored => glob.parse(ignored)) : []; } }); - - return Promise.all(promises).then(() => undefined); } - public setVerboseLogging(enabled: boolean): Promise { + async setVerboseLogging(enabled: boolean): Promise { this._verboseLogging = enabled; - return Promise.resolve(undefined); } - public stop(): Promise { + async stop(): Promise { for (let path in this._pathWatchers) { let watcher = this._pathWatchers[path]; watcher.ready.then(watcher => watcher.stop()); delete this._pathWatchers[path]; } this._pathWatchers = Object.create(null); - return Promise.resolve(); } /** diff --git a/src/vs/platform/files/node/watcher/nsfw/test/nsfwWatcherService.test.ts b/src/vs/platform/files/node/watcher/nsfw/test/nsfwWatcherService.test.ts index 169142aa2b..66a6bceb71 100644 --- a/src/vs/platform/files/node/watcher/nsfw/test/nsfwWatcherService.test.ts +++ b/src/vs/platform/files/node/watcher/nsfw/test/nsfwWatcherService.test.ts @@ -10,11 +10,14 @@ import { NsfwWatcherService } from 'vs/platform/files/node/watcher/nsfw/nsfwWatc import { IWatcherRequest } from 'vs/platform/files/node/watcher/nsfw/watcher'; class TestNsfwWatcherService extends NsfwWatcherService { - public normalizeRoots(roots: string[]): string[] { + + normalizeRoots(roots: string[]): string[] { + // Work with strings as paths to simplify testing const requests: IWatcherRequest[] = roots.map(r => { return { path: r, excludes: [] }; }); + return this._normalizeRoots(requests).map(r => r.path); } } diff --git a/src/vs/platform/files/node/watcher/unix/chokidarWatcherService.ts b/src/vs/platform/files/node/watcher/unix/chokidarWatcherService.ts index 7ba4bd898c..c848d90fff 100644 --- a/src/vs/platform/files/node/watcher/unix/chokidarWatcherService.ts +++ b/src/vs/platform/files/node/watcher/unix/chokidarWatcherService.ts @@ -23,7 +23,7 @@ process.noAsar = true; // disable ASAR support in watcher process interface IWatcher { requests: ExtendedWatcherRequest[]; - stop(): any; + stop(): Promise; } interface ExtendedWatcherRequest extends IWatcherRequest { @@ -61,13 +61,11 @@ export class ChokidarWatcherService implements IWatcherService { return this.onWatchEvent; } - setVerboseLogging(enabled: boolean): Promise { + async setVerboseLogging(enabled: boolean): Promise { this._verboseLogging = enabled; - - return Promise.resolve(); } - setRoots(requests: IWatcherRequest[]): Promise { + async setRoots(requests: IWatcherRequest[]): Promise { const watchers = Object.create(null); const newRequests: string[] = []; @@ -86,7 +84,7 @@ export class ChokidarWatcherService implements IWatcherService { // stop all old watchers for (const path in this._watchers) { - this._watchers[path].stop(); + await this._watchers[path].stop(); } // start all new watchers @@ -96,7 +94,6 @@ export class ChokidarWatcherService implements IWatcherService { } this._watchers = watchers; - return Promise.resolve(); } // for test purposes @@ -166,13 +163,13 @@ export class ChokidarWatcherService implements IWatcherService { const watcher: IWatcher = { requests, - stop: () => { + stop: async () => { try { if (this._verboseLogging) { this.log(`Stop watching: ${basePath}]`); } if (chokidarWatcher) { - chokidarWatcher.close(); + await chokidarWatcher.close(); this._watcherCount--; chokidarWatcher = null; } @@ -248,8 +245,9 @@ export class ChokidarWatcherService implements IWatcherService { undeliveredFileEvents.push(event); if (fileEventDelayer) { + // Delay and send buffer - fileEventDelayer.trigger(() => { + fileEventDelayer.trigger(async () => { const events = undeliveredFileEvents; undeliveredFileEvents = []; @@ -264,7 +262,7 @@ export class ChokidarWatcherService implements IWatcherService { }); } - return Promise.resolve(undefined); + return undefined; }); } }); @@ -291,15 +289,13 @@ export class ChokidarWatcherService implements IWatcherService { return watcher; } - stop(): Promise { + async stop(): Promise { for (const path in this._watchers) { const watcher = this._watchers[path]; - watcher.stop(); + await watcher.stop(); } this._watchers = Object.create(null); - - return Promise.resolve(); } private log(message: string) { diff --git a/src/vs/platform/files/node/watcher/win32/csharpWatcherService.ts b/src/vs/platform/files/node/watcher/win32/csharpWatcherService.ts index 801f852077..80dd9d7d8b 100644 --- a/src/vs/platform/files/node/watcher/win32/csharpWatcherService.ts +++ b/src/vs/platform/files/node/watcher/win32/csharpWatcherService.ts @@ -131,7 +131,7 @@ export class OutOfProcessWin32FolderWatcher { this.logCallback({ type: 'trace', message: `[File Watcher (C#)] ${message}` }); } - public dispose(): void { + dispose(): void { if (this.handle) { this.handle.kill(); this.handle = undefined; diff --git a/src/vs/platform/files/test/common/nullFileSystemProvider.ts b/src/vs/platform/files/test/common/nullFileSystemProvider.ts index 74e8392928..685777e8e1 100644 --- a/src/vs/platform/files/test/common/nullFileSystemProvider.ts +++ b/src/vs/platform/files/test/common/nullFileSystemProvider.ts @@ -26,16 +26,16 @@ export class NullFileSystemProvider implements IFileSystemProvider { constructor(private disposableFactory: () => IDisposable = () => Disposable.None) { } watch(resource: URI, opts: IWatchOptions): IDisposable { return this.disposableFactory(); } - stat(resource: URI): Promise { return Promise.resolve(undefined!); } - mkdir(resource: URI): Promise { return Promise.resolve(undefined!); } - readdir(resource: URI): Promise<[string, FileType][]> { return Promise.resolve(undefined!); } - delete(resource: URI, opts: FileDeleteOptions): Promise { return Promise.resolve(undefined!); } - rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise { return Promise.resolve(undefined!); } - copy?(from: URI, to: URI, opts: FileOverwriteOptions): Promise { return Promise.resolve(undefined!); } - readFile?(resource: URI): Promise { return Promise.resolve(undefined!); } - writeFile?(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise { return Promise.resolve(undefined!); } - open?(resource: URI, opts: FileOpenOptions): Promise { return Promise.resolve(undefined!); } - close?(fd: number): Promise { return Promise.resolve(undefined!); } - read?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { return Promise.resolve(undefined!); } - write?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { return Promise.resolve(undefined!); } + async stat(resource: URI): Promise { return undefined!; } + async mkdir(resource: URI): Promise { return undefined; } + async readdir(resource: URI): Promise<[string, FileType][]> { return undefined!; } + async delete(resource: URI, opts: FileDeleteOptions): Promise { return undefined; } + async rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise { return undefined; } + async copy?(from: URI, to: URI, opts: FileOverwriteOptions): Promise { return undefined; } + async readFile?(resource: URI): Promise { return undefined!; } + async writeFile?(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise { return undefined; } + async open?(resource: URI, opts: FileOpenOptions): Promise { return undefined!; } + async close?(fd: number): Promise { return undefined; } + async read?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { return undefined!; } + async write?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { return undefined!; } } diff --git a/src/vs/platform/issue/common/issue.ts b/src/vs/platform/issue/common/issue.ts index a92b4101d5..a2f296deba 100644 --- a/src/vs/platform/issue/common/issue.ts +++ b/src/vs/platform/issue/common/issue.ts @@ -17,8 +17,7 @@ export interface WindowData { export const enum IssueType { Bug, PerformanceIssue, - FeatureRequest, - SettingsSearchIssue + FeatureRequest } export interface IssueReporterStyles extends WindowStyles { @@ -66,13 +65,6 @@ export interface ISettingSearchResult { score: number; } -export interface ISettingsSearchIssueReporterData extends IssueReporterData { - issueType: IssueType.SettingsSearchIssue; - actualSearchResults: ISettingSearchResult[]; - query: string; - filterResultCount: number; -} - export interface IssueReporterFeatures { } diff --git a/src/vs/platform/issue/electron-main/issueMainService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts index 2cba96a7c6..b9534a46c9 100644 --- a/src/vs/platform/issue/electron-main/issueMainService.ts +++ b/src/vs/platform/issue/electron-main/issueMainService.ts @@ -13,8 +13,7 @@ import { BrowserWindow, ipcMain, screen, IpcMainEvent, Display, shell } from 'el import { ILaunchMainService } from 'vs/platform/launch/electron-main/launchMainService'; import { PerformanceInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; import { IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { isMacintosh, IProcessEnvironment } from 'vs/base/common/platform'; import { ILogService } from 'vs/platform/log/common/log'; import { IWindowState } from 'vs/platform/windows/electron-main/windows'; @@ -200,6 +199,7 @@ export class IssueMainService implements ICommonIssueService { preload: URI.parse(require.toUrl('vs/base/parts/sandbox/electron-browser/preload.js')).fsPath, enableWebSQL: false, enableRemoteModule: false, + spellcheck: false, nativeWindowOpen: true, zoomFactor: zoomLevelToZoomFactor(data.zoomLevel), ...this.environmentService.sandbox ? @@ -265,6 +265,7 @@ export class IssueMainService implements ICommonIssueService { preload: URI.parse(require.toUrl('vs/base/parts/sandbox/electron-browser/preload.js')).fsPath, enableWebSQL: false, enableRemoteModule: false, + spellcheck: false, nativeWindowOpen: true, zoomFactor: zoomLevelToZoomFactor(data.zoomLevel), ...this.environmentService.sandbox ? diff --git a/src/vs/platform/launch/electron-main/launchMainService.ts b/src/vs/platform/launch/electron-main/launchMainService.ts index afaf5d9c76..af88432f42 100644 --- a/src/vs/platform/launch/electron-main/launchMainService.ts +++ b/src/vs/platform/launch/electron-main/launchMainService.ts @@ -6,7 +6,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IURLService } from 'vs/platform/url/common/url'; import { IProcessEnvironment, isMacintosh } from 'vs/base/common/platform'; -import { ParsedArgs } from 'vs/platform/environment/node/argv'; +import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IWindowSettings } from 'vs/platform/windows/common/windows'; import { OpenContext } from 'vs/platform/windows/node/window'; @@ -24,7 +24,7 @@ export const ID = 'launchMainService'; export const ILaunchMainService = createDecorator(ID); export interface IStartArguments { - args: ParsedArgs; + args: NativeParsedArgs; userEnv: IProcessEnvironment; } @@ -33,7 +33,7 @@ export interface IRemoteDiagnosticOptions { includeWorkspaceMetadata?: boolean; } -function parseOpenUrl(args: ParsedArgs): URI[] { +function parseOpenUrl(args: NativeParsedArgs): URI[] { if (args['open-url'] && args._urls && args._urls.length > 0) { // --open-url must contain -- followed by the url(s) // process.argv is used over args._ as args._ are resolved to file paths at this point @@ -52,7 +52,7 @@ function parseOpenUrl(args: ParsedArgs): URI[] { export interface ILaunchMainService { readonly _serviceBrand: undefined; - start(args: ParsedArgs, userEnv: IProcessEnvironment): Promise; + start(args: NativeParsedArgs, userEnv: IProcessEnvironment): Promise; getMainProcessId(): Promise; getMainProcessInfo(): Promise; getRemoteDiagnostics(options: IRemoteDiagnosticOptions): Promise<(IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]>; @@ -70,7 +70,7 @@ export class LaunchMainService implements ILaunchMainService { @IConfigurationService private readonly configurationService: IConfigurationService ) { } - async start(args: ParsedArgs, userEnv: IProcessEnvironment): Promise { + async start(args: NativeParsedArgs, userEnv: IProcessEnvironment): Promise { this.logService.trace('Received data from other instance: ', args, userEnv); // Since we now start to open a window, make sure the app has focus. @@ -103,7 +103,7 @@ export class LaunchMainService implements ILaunchMainService { } } - private startOpenWindow(args: ParsedArgs, userEnv: IProcessEnvironment): Promise { + private startOpenWindow(args: NativeParsedArgs, userEnv: IProcessEnvironment): Promise { const context = !!userEnv['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP; let usedWindows: ICodeWindow[] = []; @@ -238,7 +238,7 @@ export class LaunchMainService implements ILaunchMainService { getRemoteDiagnostics(options: IRemoteDiagnosticOptions): Promise<(IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]> { const windows = this.windowsMainService.getWindows(); const promises: Promise[] = windows.map(window => { - return new Promise((resolve, reject) => { + return new Promise((resolve) => { const remoteAuthority = window.remoteAuthority; if (remoteAuthority) { const replyChannel = `vscode:getDiagnosticInfoResponse${window.id}`; @@ -262,7 +262,7 @@ export class LaunchMainService implements ILaunchMainService { resolve({ hostName: remoteAuthority, errorMessage: `Fetching remote diagnostics for '${remoteAuthority}' timed out.` }); }, 5000); } else { - resolve(); + resolve(undefined); } }); }); diff --git a/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts b/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts index d297b5b4a3..55ef9965cf 100644 --- a/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts +++ b/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts @@ -13,7 +13,7 @@ import { handleVetos } from 'vs/platform/lifecycle/common/lifecycle'; import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { Disposable } from 'vs/base/common/lifecycle'; import { Barrier, timeout } from 'vs/base/common/async'; -import { ParsedArgs } from 'vs/platform/environment/node/argv'; +import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; export const ILifecycleMainService = createDecorator('lifecycleMainService'); @@ -86,7 +86,7 @@ export interface ILifecycleMainService { /** * Reload a window. All lifecycle event handlers are triggered. */ - reload(window: ICodeWindow, cli?: ParsedArgs): Promise; + reload(window: ICodeWindow, cli?: NativeParsedArgs): Promise; /** * Unload a window for the provided reason. All lifecycle event handlers are triggered. @@ -366,7 +366,7 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe }); } - async reload(window: ICodeWindow, cli?: ParsedArgs): Promise { + async reload(window: ICodeWindow, cli?: NativeParsedArgs): Promise { // Only reload when the window has not vetoed this const veto = await this.unload(window, UnloadReason.RELOAD); diff --git a/src/vs/platform/localizations/node/localizations.ts b/src/vs/platform/localizations/node/localizations.ts index 945f16cade..f2f7a550f3 100644 --- a/src/vs/platform/localizations/node/localizations.ts +++ b/src/vs/platform/localizations/node/localizations.ts @@ -7,8 +7,7 @@ import * as pfs from 'vs/base/node/pfs'; import { createHash } from 'crypto'; import { IExtensionManagementService, ILocalExtension, IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { Queue } from 'vs/base/common/async'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ILogService } from 'vs/platform/log/common/log'; diff --git a/src/vs/platform/menubar/electron-main/menubar.ts b/src/vs/platform/menubar/electron-main/menubar.ts index ef5ffa7ffb..d64458d5b6 100644 --- a/src/vs/platform/menubar/electron-main/menubar.ts +++ b/src/vs/platform/menubar/electron-main/menubar.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { isMacintosh, language } from 'vs/base/common/platform'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { app, shell, Menu, MenuItem, BrowserWindow, MenuItemConstructorOptions, WebContents, Event, KeyboardEvent } from 'electron'; import { getTitleBarStyle, INativeRunActionInWindowRequest, INativeRunKeybindingInWindowRequest, IWindowOpenable } from 'vs/platform/windows/common/windows'; import { OpenContext } from 'vs/platform/windows/node/window'; @@ -24,7 +24,6 @@ import { IStateService } from 'vs/platform/state/node/state'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; import { IElectronMainService } from 'vs/platform/electron/electron-main/electronMainService'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; const telemetryFrom = 'menu'; diff --git a/src/vs/platform/remote/common/remoteHosts.ts b/src/vs/platform/remote/common/remoteHosts.ts index 4d6b3e86cf..2714643727 100644 --- a/src/vs/platform/remote/common/remoteHosts.ts +++ b/src/vs/platform/remote/common/remoteHosts.ts @@ -6,10 +6,8 @@ import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; -export const REMOTE_HOST_SCHEME = Schemas.vscodeRemote; - export function getRemoteAuthority(uri: URI): string | undefined { - return uri.scheme === REMOTE_HOST_SCHEME ? uri.authority : undefined; + return uri.scheme === Schemas.vscodeRemote ? uri.authority : undefined; } export function getRemoteName(authority: string): string; diff --git a/src/vs/platform/request/electron-main/requestMainService.ts b/src/vs/platform/request/electron-main/requestMainService.ts index 83ff6c29da..f4a29f2857 100644 --- a/src/vs/platform/request/electron-main/requestMainService.ts +++ b/src/vs/platform/request/electron-main/requestMainService.ts @@ -5,7 +5,6 @@ import { IRequestOptions, IRequestContext } from 'vs/base/parts/request/common/request'; import { RequestService as NodeRequestService, IRawRequestFunction } from 'vs/platform/request/node/requestService'; -import { assign } from 'vs/base/common/objects'; import { net } from 'electron'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -16,6 +15,6 @@ function getRawRequest(options: IRequestOptions): IRawRequestFunction { export class RequestMainService extends NodeRequestService { request(options: IRequestOptions, token: CancellationToken): Promise { - return super.request(assign({}, options || {}, { getRawRequest }), token); + return super.request({ ...(options || {}), getRawRequest }, token); } } diff --git a/src/vs/platform/request/node/requestService.ts b/src/vs/platform/request/node/requestService.ts index 2c16fd1dad..1182e5fc89 100644 --- a/src/vs/platform/request/node/requestService.ts +++ b/src/vs/platform/request/node/requestService.ts @@ -9,7 +9,6 @@ import * as streams from 'vs/base/common/stream'; import { createGunzip } from 'zlib'; import { parse as parseUrl } from 'url'; import { Disposable } from 'vs/base/common/lifecycle'; -import { assign } from 'vs/base/common/objects'; import { isBoolean, isNumber } from 'vs/base/common/types'; import { canceled } from 'vs/base/common/errors'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -67,7 +66,10 @@ export class RequestService extends Disposable implements IRequestService { options.strictSSL = strictSSL; if (this.authorization) { - options.headers = assign(options.headers || {}, { 'Proxy-Authorization': this.authorization }); + options.headers = { + ...(options.headers || {}), + 'Proxy-Authorization': this.authorization + }; } return this._request(options, token); @@ -107,10 +109,11 @@ export class RequestService extends Disposable implements IRequestService { req = rawRequest(opts, (res: http.IncomingMessage) => { const followRedirects: number = isNumber(options.followRedirects) ? options.followRedirects : 3; if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && followRedirects > 0 && res.headers['location']) { - this._request(assign({}, options, { + this._request({ + ...options, url: res.headers['location'], followRedirects: followRedirects - 1 - }), token).then(c, e); + }, token).then(c, e); } else { let stream: streams.ReadableStreamEvents = res; diff --git a/src/vs/platform/state/node/stateService.ts b/src/vs/platform/state/node/stateService.ts index 88882ff091..3c069f62e0 100644 --- a/src/vs/platform/state/node/stateService.ts +++ b/src/vs/platform/state/node/stateService.ts @@ -5,8 +5,7 @@ import * as path from 'vs/base/common/path'; import * as fs from 'fs'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { writeFileSync, readFile } from 'vs/base/node/pfs'; import { isUndefined, isUndefinedOrNull } from 'vs/base/common/types'; import { IStateService } from 'vs/platform/state/node/state'; diff --git a/src/vs/platform/storage/node/storageMainService.ts b/src/vs/platform/storage/node/storageMainService.ts index cb8632a05c..4204a39cd2 100644 --- a/src/vs/platform/storage/node/storageMainService.ts +++ b/src/vs/platform/storage/node/storageMainService.ts @@ -8,7 +8,6 @@ import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { ILogService, LogLevel } from 'vs/platform/log/common/log'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { SQLiteStorageDatabase, ISQLiteStorageDatabaseLoggingOptions } from 'vs/base/parts/storage/node/storage'; import { Storage, IStorage, InMemoryStorageDatabase } from 'vs/base/parts/storage/common/storage'; import { join } from 'vs/base/common/path'; @@ -105,7 +104,7 @@ export class StorageMainService extends Disposable implements IStorageMainServic constructor( @ILogService private readonly logService: ILogService, - @IEnvironmentService private readonly environmentService: INativeEnvironmentService + @IEnvironmentService private readonly environmentService: IEnvironmentService ) { super(); diff --git a/src/vs/platform/storage/node/storageService.ts b/src/vs/platform/storage/node/storageService.ts index 3f769b6fbf..e2c1f349a8 100644 --- a/src/vs/platform/storage/node/storageService.ts +++ b/src/vs/platform/storage/node/storageService.ts @@ -13,7 +13,6 @@ import { mark } from 'vs/base/common/performance'; import { join } from 'vs/base/common/path'; import { copy, exists, mkdirp, writeFile } from 'vs/base/node/pfs'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { IWorkspaceInitializationPayload, isWorkspaceIdentifier, isSingleFolderWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces'; import { assertIsDefined } from 'vs/base/common/types'; import { RunOnceScheduler, runWhenIdle } from 'vs/base/common/async'; @@ -45,7 +44,7 @@ export class NativeStorageService extends Disposable implements IStorageService constructor( private globalStorageDatabase: IStorageDatabase, @ILogService private readonly logService: ILogService, - @IEnvironmentService private readonly environmentService: INativeEnvironmentService + @IEnvironmentService private readonly environmentService: IEnvironmentService ) { super(); diff --git a/src/vs/platform/storage/test/node/storageService.test.ts b/src/vs/platform/storage/test/node/storageService.test.ts index d2e4f0be64..1d44be8603 100644 --- a/src/vs/platform/storage/test/node/storageService.test.ts +++ b/src/vs/platform/storage/test/node/storageService.test.ts @@ -87,7 +87,7 @@ suite('StorageService', () => { class StorageTestEnvironmentService extends EnvironmentService { constructor(private workspaceStorageFolderPath: URI, private _extensionsPath: string) { - super(parseArgs(process.argv, OPTIONS), process.execPath); + super(parseArgs(process.argv, OPTIONS)); } get workspaceStorageHome(): URI { diff --git a/src/vs/platform/theme/common/theme.ts b/src/vs/platform/theme/common/theme.ts new file mode 100644 index 0000000000..09cdc2c44d --- /dev/null +++ b/src/vs/platform/theme/common/theme.ts @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * Color scheme used by the OS and by color themes. + */ +export enum ColorScheme { + DARK = 'dark', + LIGHT = 'light', + HIGH_CONTRAST = 'hc' +} diff --git a/src/vs/platform/theme/common/themeService.ts b/src/vs/platform/theme/common/themeService.ts index ced9e33067..cb819e7c16 100644 --- a/src/vs/platform/theme/common/themeService.ts +++ b/src/vs/platform/theme/common/themeService.ts @@ -10,6 +10,7 @@ import * as platform from 'vs/platform/registry/common/platform'; import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; import { Event, Emitter } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; export const IThemeService = createDecorator('themeService'); @@ -65,16 +66,10 @@ export namespace ThemeIcon { export const FileThemeIcon = { id: 'file' }; export const FolderThemeIcon = { id: 'folder' }; -// base themes -export const DARK: ThemeType = 'dark'; -export const LIGHT: ThemeType = 'light'; -export const HIGH_CONTRAST: ThemeType = 'hc'; -export type ThemeType = 'light' | 'dark' | 'hc'; - -export function getThemeTypeSelector(type: ThemeType): string { +export function getThemeTypeSelector(type: ColorScheme): string { switch (type) { - case DARK: return 'vs-dark'; - case HIGH_CONTRAST: return 'hc-black'; + case ColorScheme.DARK: return 'vs-dark'; + case ColorScheme.HIGH_CONTRAST: return 'hc-black'; default: return 'vs'; } } @@ -88,7 +83,7 @@ export interface ITokenStyle { export interface IColorTheme { - readonly type: ThemeType; + readonly type: ColorScheme; readonly label: string; diff --git a/src/vs/platform/theme/test/common/testThemeService.ts b/src/vs/platform/theme/test/common/testThemeService.ts index b901cba0db..b7dbab2bf0 100644 --- a/src/vs/platform/theme/test/common/testThemeService.ts +++ b/src/vs/platform/theme/test/common/testThemeService.ts @@ -4,14 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import { Event, Emitter } from 'vs/base/common/event'; -import { IThemeService, IColorTheme, DARK, IFileIconTheme, ITokenStyle } from 'vs/platform/theme/common/themeService'; +import { IThemeService, IColorTheme, IFileIconTheme, ITokenStyle } from 'vs/platform/theme/common/themeService'; import { Color } from 'vs/base/common/color'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; export class TestColorTheme implements IColorTheme { public readonly label = 'test'; - constructor(private colors: { [id: string]: string; } = {}, public type = DARK) { + constructor(private colors: { [id: string]: string; } = {}, public type = ColorScheme.DARK) { } getColor(color: string, useDefault?: boolean): Color | undefined { diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts index e2b42410bb..d0810b3801 100644 --- a/src/vs/platform/update/electron-main/abstractUpdateService.ts +++ b/src/vs/platform/update/electron-main/abstractUpdateService.ts @@ -9,8 +9,7 @@ import { IConfigurationService, getMigratedSettingValue } from 'vs/platform/conf import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import product from 'vs/platform/product/common/product'; import { IUpdateService, State, StateType, AvailableForDownload, UpdateType } from 'vs/platform/update/common/update'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { ILogService } from 'vs/platform/log/common/log'; import { IRequestService } from 'vs/platform/request/common/request'; import { CancellationToken } from 'vs/base/common/cancellation'; diff --git a/src/vs/platform/update/electron-main/updateService.darwin.ts b/src/vs/platform/update/electron-main/updateService.darwin.ts index 8f358ca228..459646d237 100644 --- a/src/vs/platform/update/electron-main/updateService.darwin.ts +++ b/src/vs/platform/update/electron-main/updateService.darwin.ts @@ -11,8 +11,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { State, IUpdate, StateType, UpdateType } from 'vs/platform/update/common/update'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { ILogService } from 'vs/platform/log/common/log'; import { AbstractUpdateService, createUpdateURL, UpdateNotAvailableClassification } from 'vs/platform/update/electron-main/abstractUpdateService'; import { IRequestService } from 'vs/platform/request/common/request'; diff --git a/src/vs/platform/update/electron-main/updateService.linux.ts b/src/vs/platform/update/electron-main/updateService.linux.ts index dd2fbf0fa6..59257c4614 100644 --- a/src/vs/platform/update/electron-main/updateService.linux.ts +++ b/src/vs/platform/update/electron-main/updateService.linux.ts @@ -8,8 +8,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { State, IUpdate, AvailableForDownload, UpdateType } from 'vs/platform/update/common/update'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { ILogService } from 'vs/platform/log/common/log'; import { createUpdateURL, AbstractUpdateService, UpdateNotAvailableClassification } from 'vs/platform/update/electron-main/abstractUpdateService'; import { IRequestService, asJson } from 'vs/platform/request/common/request'; diff --git a/src/vs/platform/update/electron-main/updateService.snap.ts b/src/vs/platform/update/electron-main/updateService.snap.ts index 891f08fd31..d66f029b70 100644 --- a/src/vs/platform/update/electron-main/updateService.snap.ts +++ b/src/vs/platform/update/electron-main/updateService.snap.ts @@ -7,8 +7,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { timeout } from 'vs/base/common/async'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IUpdateService, State, StateType, AvailableForDownload, UpdateType } from 'vs/platform/update/common/update'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { ILogService } from 'vs/platform/log/common/log'; import * as path from 'vs/base/common/path'; import { realpath, watch } from 'fs'; diff --git a/src/vs/platform/update/electron-main/updateService.win32.ts b/src/vs/platform/update/electron-main/updateService.win32.ts index 3455bcaafb..c5a98817a1 100644 --- a/src/vs/platform/update/electron-main/updateService.win32.ts +++ b/src/vs/platform/update/electron-main/updateService.win32.ts @@ -12,8 +12,7 @@ import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifec import product from 'vs/platform/product/common/product'; import { State, IUpdate, StateType, AvailableForDownload, UpdateType } from 'vs/platform/update/common/update'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { ILogService } from 'vs/platform/log/common/log'; import { createUpdateURL, AbstractUpdateService, UpdateNotAvailableClassification } from 'vs/platform/update/electron-main/abstractUpdateService'; import { IRequestService, asJson } from 'vs/platform/request/common/request'; diff --git a/src/vs/platform/url/common/urlIpc.ts b/src/vs/platform/url/common/urlIpc.ts index 7c71c0bc8a..85ef6a56a3 100644 --- a/src/vs/platform/url/common/urlIpc.ts +++ b/src/vs/platform/url/common/urlIpc.ts @@ -8,7 +8,6 @@ import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; import { IURLHandler, IOpenURLOptions } from 'vs/platform/url/common/url'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { first } from 'vs/base/common/arrays'; export class URLHandlerChannel implements IServerChannel { @@ -54,7 +53,7 @@ export class URLHandlerRouter implements IClientRouter { if (match) { const windowId = match[1]; const regex = new RegExp(`window:${windowId}`); - const connection = first(hub.connections, c => regex.test(c.ctx)); + const connection = hub.connections.find(c => regex.test(c.ctx)); if (connection) { return connection; diff --git a/src/vs/platform/url/electron-main/electronUrlListener.ts b/src/vs/platform/url/electron-main/electronUrlListener.ts index 11931bdc7d..ee3c0fb433 100644 --- a/src/vs/platform/url/electron-main/electronUrlListener.ts +++ b/src/vs/platform/url/electron-main/electronUrlListener.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { IURLService } from 'vs/platform/url/common/url'; import product from 'vs/platform/product/common/product'; import { app, Event as ElectronEvent } from 'electron'; diff --git a/src/vs/platform/userDataSync/common/keybindingsMerge.ts b/src/vs/platform/userDataSync/common/keybindingsMerge.ts index b361bfd6c4..258c1f69c5 100644 --- a/src/vs/platform/userDataSync/common/keybindingsMerge.ts +++ b/src/vs/platform/userDataSync/common/keybindingsMerge.ts @@ -6,7 +6,7 @@ import * as objects from 'vs/base/common/objects'; import { parse } from 'vs/base/common/json'; import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; -import { firstIndex as findFirstIndex, equals } from 'vs/base/common/arrays'; +import { equals } from 'vs/base/common/arrays'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import * as contentUtil from 'vs/platform/userDataSync/common/content'; import { IStringDictionary } from 'vs/base/common/collections'; @@ -346,7 +346,7 @@ function removeKeybindings(content: string, command: string, formattingOptions: function updateKeybindings(content: string, command: string, keybindings: IUserFriendlyKeybinding[], formattingOptions: FormattingOptions): string { const allKeybindings = parseKeybindings(content); - const location = findFirstIndex(allKeybindings, keybinding => keybinding.command === command || keybinding.command === `-${command}`); + const location = allKeybindings.findIndex(keybinding => keybinding.command === command || keybinding.command === `-${command}`); // Remove all entries with this command for (let index = allKeybindings.length - 1; index >= 0; index--) { if (allKeybindings[index].command === command || allKeybindings[index].command === `-${command}`) { diff --git a/src/vs/platform/userDataSync/common/settingsMerge.ts b/src/vs/platform/userDataSync/common/settingsMerge.ts index ff5e478f7a..e63b56568f 100644 --- a/src/vs/platform/userDataSync/common/settingsMerge.ts +++ b/src/vs/platform/userDataSync/common/settingsMerge.ts @@ -10,7 +10,7 @@ import { IStringDictionary } from 'vs/base/common/collections'; import { FormattingOptions, Edit, getEOL } from 'vs/base/common/jsonFormatter'; import * as contentUtil from 'vs/platform/userDataSync/common/content'; import { IConflictSetting, getDisallowedIgnoredSettings } from 'vs/platform/userDataSync/common/userDataSync'; -import { firstIndex, distinct } from 'vs/base/common/arrays'; +import { distinct } from 'vs/base/common/arrays'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export interface IMergeResult { @@ -320,7 +320,7 @@ interface InsertLocation { function getInsertLocation(key: string, sourceTree: INode[], targetTree: INode[]): InsertLocation { - const sourceNodeIndex = firstIndex(sourceTree, (node => node.setting?.key === key)); + const sourceNodeIndex = sourceTree.findIndex(node => node.setting?.key === key); const sourcePreviousNode: INode = sourceTree[sourceNodeIndex - 1]; if (sourcePreviousNode) { diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index 302c37cafb..53f47c0ee8 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -15,7 +15,6 @@ import { getServiceMachineId } from 'vs/platform/serviceMachineId/common/service import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { assign } from 'vs/base/common/objects'; import { generateUuid } from 'vs/base/common/uuid'; import { isWeb } from 'vs/base/common/platform'; import { Emitter, Event } from 'vs/base/common/event'; @@ -355,10 +354,12 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync this.setDonotMakeRequestsUntil(undefined); const commonHeaders = await this.commonHeadersPromise; - options.headers = assign(options.headers || {}, commonHeaders, { + options.headers = { + ...(options.headers || {}), + ...commonHeaders, 'X-Account-Type': this.authToken.type, 'authorization': `Bearer ${this.authToken.token}`, - }); + }; // Add session headers this.addSessionHeaders(options.headers); diff --git a/src/vs/platform/webview/common/webviewPortMapping.ts b/src/vs/platform/webview/common/webviewPortMapping.ts index 79d28e8706..72512c66ef 100644 --- a/src/vs/platform/webview/common/webviewPortMapping.ts +++ b/src/vs/platform/webview/common/webviewPortMapping.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { IDisposable } from 'vs/base/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { IAddress } from 'vs/platform/remote/common/remoteAgentConnection'; -import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { extractLocalHostUriMetaDataForPortMapping, ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; export interface IWebviewPortMapping { @@ -37,7 +37,7 @@ export class WebviewPortMappingManager implements IDisposable { for (const mapping of this._getMappings()) { if (mapping.webviewPort === requestLocalHostInfo.port) { const extensionLocation = this._getExtensionLocation(); - if (extensionLocation && extensionLocation.scheme === REMOTE_HOST_SCHEME) { + if (extensionLocation && extensionLocation.scheme === Schemas.vscodeRemote) { const tunnel = resolveAuthority && await this.getOrCreateTunnel(resolveAuthority, mapping.extensionHostPort); if (tunnel) { if (tunnel.tunnelLocalPort === mapping.webviewPort) { diff --git a/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts b/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts index 0f30ec4454..fb91c88131 100644 --- a/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts +++ b/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts @@ -11,7 +11,6 @@ import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; import { IRemoteConnectionData } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { IRequestService } from 'vs/platform/request/common/request'; import { loadLocalResource, webviewPartitionId, WebviewResourceResponse } from 'vs/platform/webview/common/resourceLoader'; import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; @@ -161,7 +160,7 @@ export class WebviewProtocolProvider extends Disposable { if (metadata.remoteConnectionData) { rewriteUri = (uri) => { if (metadata.remoteConnectionData) { - if (uri.scheme === Schemas.vscodeRemote || (metadata.extensionLocation?.scheme === REMOTE_HOST_SCHEME)) { + if (uri.scheme === Schemas.vscodeRemote || (metadata.extensionLocation?.scheme === Schemas.vscodeRemote)) { return URI.parse(`http://${metadata.remoteConnectionData.host}:${metadata.remoteConnectionData.port}`).with({ path: '/vscode-remote-resource', query: `tkn=${metadata.remoteConnectionData.connectionToken}&path=${encodeURIComponent(uri.path)}`, diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 35bff58a5a..be8741aaf3 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -3,11 +3,15 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { isMacintosh, isLinux, isWeb } from 'vs/base/common/platform'; +import { isMacintosh, isLinux, isWeb, IProcessEnvironment } from 'vs/base/common/platform'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; +import { LogLevel } from 'vs/platform/log/common/log'; +import { ExportData } from 'vs/base/common/performance'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; export interface IBaseOpenWindowsOptions { forceReuseWindow?: boolean; @@ -208,13 +212,41 @@ export interface IWindowConfiguration { remoteAuthority?: string; - highContrast?: boolean; + colorScheme: ColorScheme; autoDetectHighContrast?: boolean; filesToOpenOrCreate?: IPath[]; filesToDiff?: IPath[]; } +export interface INativeWindowConfiguration extends IWindowConfiguration, NativeParsedArgs { + mainPid: number; + + windowId: number; + machineId: string; + + appRoot: string; + execPath: string; + backupPath?: string; + + nodeCachedDataDir?: string; + partsSplashPath: string; + + workspace?: IWorkspaceIdentifier; + folderUri?: ISingleFolderWorkspaceIdentifier; + + isInitialStartup?: boolean; + logLevel: LogLevel; + zoomLevel?: number; + fullscreen?: boolean; + maximized?: boolean; + accessibilitySupport?: boolean; + perfEntries: ExportData; + + userEnv: IProcessEnvironment; + filesToWait?: IPathsToWaitFor; +} + /** * According to Electron docs: `scale := 1.2 ^ level`. * https://github.com/electron/electron/blob/master/docs/api/web-contents.md#contentssetzoomlevellevel diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index 13c7709fa2..4a07b080f1 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -3,9 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWindowOpenable, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; -import { INativeWindowConfiguration, OpenContext } from 'vs/platform/windows/node/window'; -import { ParsedArgs } from 'vs/platform/environment/node/argv'; +import { IWindowOpenable, IOpenEmptyWindowOptions, INativeWindowConfiguration } from 'vs/platform/windows/common/windows'; +import { OpenContext } from 'vs/platform/windows/node/window'; +import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IProcessEnvironment } from 'vs/base/common/platform'; @@ -59,7 +59,7 @@ export interface ICodeWindow extends IDisposable { addTabbedWindow(window: ICodeWindow): void; load(config: INativeWindowConfiguration, isReload?: boolean): void; - reload(configuration?: INativeWindowConfiguration, cli?: ParsedArgs): void; + reload(configuration?: INativeWindowConfiguration, cli?: NativeParsedArgs): void; focus(options?: { force: boolean }): void; close(): void; @@ -121,7 +121,7 @@ export interface IBaseOpenConfiguration { } export interface IOpenConfiguration extends IBaseOpenConfiguration { - readonly cli: ParsedArgs; + readonly cli: NativeParsedArgs; readonly userEnv?: IProcessEnvironment; readonly urisToOpen?: IWindowOpenable[]; readonly waitMarkerFileURI?: URI; diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index d93443a4c8..71e9170de6 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -10,17 +10,16 @@ import * as arrays from 'vs/base/common/arrays'; import { mixin } from 'vs/base/common/objects'; import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; import { IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { ParsedArgs } from 'vs/platform/environment/node/argv'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; +import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { IStateService } from 'vs/platform/state/node/state'; import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window'; -import { screen, BrowserWindow, MessageBoxOptions, Display, app, nativeTheme } from 'electron'; +import { screen, BrowserWindow, MessageBoxOptions, Display, app } from 'electron'; import { ILifecycleMainService, UnloadReason, LifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; -import { IWindowSettings, IPath, isFileToOpen, isWorkspaceToOpen, isFolderToOpen, IWindowOpenable, IOpenEmptyWindowOptions, IAddFoldersRequest, IPathsToWaitFor } from 'vs/platform/windows/common/windows'; -import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnWorkspace, findWindowOnExtensionDevelopmentPath, findWindowOnWorkspaceOrFolderUri, INativeWindowConfiguration, OpenContext } from 'vs/platform/windows/node/window'; +import { IWindowSettings, IPath, isFileToOpen, isWorkspaceToOpen, isFolderToOpen, IWindowOpenable, IOpenEmptyWindowOptions, IAddFoldersRequest, IPathsToWaitFor, INativeWindowConfiguration } from 'vs/platform/windows/common/windows'; +import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnWorkspace, findWindowOnExtensionDevelopmentPath, findWindowOnWorkspaceOrFolderUri, OpenContext } from 'vs/platform/windows/node/window'; import { Emitter } from 'vs/base/common/event'; import product from 'vs/platform/product/common/product'; import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IWindowState as ISingleWindowState, WindowMode, IOpenEmptyConfiguration } from 'vs/platform/windows/electron-main/windows'; @@ -64,7 +63,7 @@ type RestoreWindowsSetting = 'all' | 'folders' | 'one' | 'none'; interface IOpenBrowserWindowOptions { userEnv?: IProcessEnvironment; - cli?: ParsedArgs; + cli?: NativeParsedArgs; workspace?: IWorkspaceIdentifier; folderUri?: URI; @@ -210,17 +209,6 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic private registerListeners(): void { - // React to HC color scheme changes (Windows, macOS) - if (isWindows || isMacintosh) { - nativeTheme.on('updated', () => { - if (nativeTheme.shouldUseInvertedColorScheme || nativeTheme.shouldUseHighContrastColors) { - this.sendToAll('vscode:enterHighContrast'); - } else { - this.sendToAll('vscode:leaveHighContrast'); - } - }); - } - // When a window looses focus, save all windows state. This allows to // prevent loss of window-state data when OS is restarted without properly // shutting down the application (https://github.com/microsoft/vscode/issues/87171) @@ -899,7 +887,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic return pathsToOpen; } - private doExtractPathsFromCLI(cli: ParsedArgs): IPath[] { + private doExtractPathsFromCLI(cli: NativeParsedArgs): IPath[] { const pathsToOpen: IPathToOpen[] = []; const parseOptions: IPathParseOptions = { ignoreFileNotFound: true, gotoLineMode: cli.goto, remoteAuthority: cli.remote || undefined }; @@ -1370,7 +1358,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // For all other cases we first call into registerEmptyWindowBackupSync() to set it before // loading the window. if (options.emptyWindowBackupInfo) { - configuration.backupPath = join(this.environmentService.backupHome.fsPath, options.emptyWindowBackupInfo.backupFolder); + configuration.backupPath = join(this.environmentService.backupHome, options.emptyWindowBackupInfo.backupFolder); } let window: ICodeWindow | undefined; diff --git a/src/vs/platform/windows/node/window.ts b/src/vs/platform/windows/node/window.ts index b53e2aaa45..6fb8771c4e 100644 --- a/src/vs/platform/windows/node/window.ts +++ b/src/vs/platform/windows/node/window.ts @@ -3,15 +3,11 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWindowConfiguration, IPathsToWaitFor } from 'vs/platform/windows/common/windows'; import { URI } from 'vs/base/common/uri'; import * as platform from 'vs/base/common/platform'; import * as extpath from 'vs/base/common/extpath'; import { IWorkspaceIdentifier, IResolvedWorkspace, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; -import { LogLevel } from 'vs/platform/log/common/log'; -import { ExportData } from 'vs/base/common/performance'; -import { ParsedArgs } from 'vs/platform/environment/node/argv'; export const enum OpenContext { @@ -34,34 +30,6 @@ export const enum OpenContext { API } -export interface INativeWindowConfiguration extends IWindowConfiguration, ParsedArgs { - mainPid: number; - - windowId: number; - machineId: string; - - appRoot: string; - execPath: string; - backupPath?: string; - - nodeCachedDataDir?: string; - partsSplashPath: string; - - workspace?: IWorkspaceIdentifier; - folderUri?: ISingleFolderWorkspaceIdentifier; - - isInitialStartup?: boolean; - logLevel: LogLevel; - zoomLevel?: number; - fullscreen?: boolean; - maximized?: boolean; - accessibilitySupport?: boolean; - perfEntries: ExportData; - - userEnv: platform.IProcessEnvironment; - filesToWait?: IPathsToWaitFor; -} - export interface IWindowContext { openedWorkspace?: IWorkspaceIdentifier; openedFolderUri?: URI; @@ -75,7 +43,6 @@ export interface IBestWindowOrFolderOptions { newWindow: boolean; context: OpenContext; fileUri?: URI; - userHome?: string; codeSettingsFolder?: string; localWorkspaceResolver: (workspace: IWorkspaceIdentifier) => IResolvedWorkspace | null; } diff --git a/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts index da01dc009c..73077e435e 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts @@ -17,8 +17,7 @@ import { ThrottledDelayer } from 'vs/base/common/async'; import { isEqual, dirname, originalFSPath, basename, extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { exists } from 'vs/base/node/pfs'; import { ILifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; diff --git a/src/vs/platform/workspaces/electron-main/workspacesMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesMainService.ts index c0bef53064..31d9c80a2e 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesMainService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesMainService.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IWorkspaceIdentifier, hasWorkspaceFileExtension, UNTITLED_WORKSPACE_NAME, IResolvedWorkspace, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, IUntitledWorkspaceInfo, getStoredWorkspaceFolder, IEnterWorkspaceResult, isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { join, dirname } from 'vs/base/common/path'; import { mkdirp, writeFile, rimrafSync, readdirSync, writeFileSync } from 'vs/base/node/pfs'; import { readFileSync, existsSync, mkdirSync } from 'fs'; diff --git a/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts b/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts index eab2908958..64d2a47b2e 100644 --- a/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts +++ b/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts @@ -138,7 +138,7 @@ suite('WorkspacesMainService', () => { return service.createUntitledWorkspaceSync(folders.map((folder, index) => ({ uri: URI.file(folder), name: names ? names[index] : undefined } as IWorkspaceFolderCreationData))); } - const environmentService = new TestEnvironmentService(parseArgs(process.argv, OPTIONS), process.execPath); + const environmentService = new TestEnvironmentService(parseArgs(process.argv, OPTIONS)); const logService = new NullLogService(); let service: WorkspacesMainService; diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 989ce0e673..d9f3fcb79c 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -1160,7 +1160,7 @@ declare module 'vscode' { revealRange(range: Range, revealType?: TextEditorRevealType): void; /** - * ~~Show the text editor.~~ + * Show the text editor. * * @deprecated Use [window.showTextDocument](#window.showTextDocument) instead. * @@ -1170,7 +1170,7 @@ declare module 'vscode' { show(column?: ViewColumn): void; /** - * ~~Hide the text editor.~~ + * Hide the text editor. * * @deprecated Use the command `workbench.action.closeActiveEditor` instead. * This method shows unexpected behavior and will be removed in the next major update. @@ -1475,7 +1475,8 @@ declare module 'vscode' { * A function that represents an event to which you subscribe by calling it with * a listener function as argument. * - * @sample `item.onDidChange(function(event) { console.log("Event happened: " + event); });` + * @example + * item.onDidChange(function(event) { console.log("Event happened: " + event); }); */ export interface Event { @@ -1920,8 +1921,11 @@ declare module 'vscode' { * the [language](#TextDocument.languageId), the [scheme](#Uri.scheme) of * its resource, or a glob-pattern that is applied to the [path](#TextDocument.fileName). * - * @sample A language filter that applies to typescript files on disk: `{ language: 'typescript', scheme: 'file' }` - * @sample A language filter that applies to all package.json paths: `{ language: 'json', scheme: 'untitled', pattern: '**​/package.json' }` + * @example A language filter that applies to typescript files on disk + * { language: 'typescript', scheme: 'file' } + * + * @example A language filter that applies to all package.json paths + * { language: 'json', scheme: 'untitled', pattern: '**​/package.json' } */ export interface DocumentFilter { @@ -1951,7 +1955,8 @@ declare module 'vscode' { * a feature works without further context, e.g. without the need to resolve related * 'files'. * - * @sample `let sel:DocumentSelector = { scheme: 'file', language: 'typescript' }`; + * @example + * let sel:DocumentSelector = { scheme: 'file', language: 'typescript' }; */ export type DocumentSelector = DocumentFilter | string | Array; @@ -2498,9 +2503,9 @@ declare module 'vscode' { } /** - * ~~MarkedString can be used to render human-readable text. It is either a markdown string + * MarkedString can be used to render human-readable text. It is either a markdown string * or a code-block that provides a language and a code snippet. Note that - * markdown strings will be sanitized - that means html will be escaped.~~ + * markdown strings will be sanitized - that means html will be escaped. * * @deprecated This type is deprecated, please use [`MarkdownString`](#MarkdownString) instead. */ @@ -2753,7 +2758,7 @@ declare module 'vscode' { constructor(name: string, kind: SymbolKind, containerName: string, location: Location); /** - * ~~Creates a new symbol information object.~~ + * Creates a new symbol information object. * * @deprecated Please use the constructor taking a [location](#Location) object. * @@ -3820,6 +3825,13 @@ declare module 'vscode' { * A string that should be used when comparing this item * with other items. When `falsy` the [label](#CompletionItem.label) * is used. + * + * Note that `sortText` is only used for the initial ordering of completion + * items. When having a leading word (prefix) ordering is based on how + * well completion match that prefix and the initial ordering is only used + * when completions match equal. The prefix is defined by the + * [`range`](#CompletionItem.range)-property and can therefore be different + * for each completion. */ sortText?: string; @@ -3827,6 +3839,10 @@ declare module 'vscode' { * A string that should be used when filtering a set of * completion items. When `falsy` the [label](#CompletionItem.label) * is used. + * + * Note that the filter text is matched against the leading word (prefix) which is defined + * by the [`range`](#CompletionItem.range)-property. + * prefix. */ filterText?: string; @@ -3874,12 +3890,12 @@ declare module 'vscode' { /** * @deprecated Use `CompletionItem.insertText` and `CompletionItem.range` instead. * - * ~~An [edit](#TextEdit) which is applied to a document when selecting + * An [edit](#TextEdit) which is applied to a document when selecting * this completion. When an edit is provided the value of - * [insertText](#CompletionItem.insertText) is ignored.~~ + * [insertText](#CompletionItem.insertText) is ignored. * - * ~~The [range](#Range) of the edit must be single-line and on the same - * line completions were [requested](#CompletionItemProvider.provideCompletionItems) at.~~ + * The [range](#Range) of the edit must be single-line and on the same + * line completions were [requested](#CompletionItemProvider.provideCompletionItems) at. */ textEdit?: TextEdit; @@ -5220,7 +5236,7 @@ declare module 'vscode' { show(preserveFocus?: boolean): void; /** - * ~~Reveal this channel in the UI.~~ + * Reveal this channel in the UI. * * @deprecated Use the overload with just one parameter (`show(preserveFocus?: boolean): void`). * @@ -6179,7 +6195,7 @@ declare module 'vscode' { constructor(taskDefinition: TaskDefinition, scope: WorkspaceFolder | TaskScope.Global | TaskScope.Workspace, name: string, source: string, execution?: ProcessExecution | ShellExecution | CustomExecution, problemMatchers?: string | string[]); /** - * ~~Creates a new task.~~ + * Creates a new task. * * @deprecated Use the new constructors that allow specifying a scope for the task. * @@ -6266,7 +6282,7 @@ declare module 'vscode' { * @param token A cancellation token. * @return an array of tasks */ - provideTasks(token?: CancellationToken): ProviderResult; + provideTasks(token: CancellationToken): ProviderResult; /** * Resolves a task that has no [`execution`](#Task.execution) set. Tasks are @@ -6281,7 +6297,7 @@ declare module 'vscode' { * @param token A cancellation token. * @return The resolved task */ - resolveTask(task: T, token?: CancellationToken): ProviderResult; + resolveTask(task: T, token: CancellationToken): ProviderResult; } /** @@ -7093,8 +7109,10 @@ declare module 'vscode' { * VS Code will save off the state from `setState` of all webviews that have a serializer. When the * webview first becomes visible after the restart, this state is passed to `deserializeWebviewPanel`. * The extension can then restore the old `WebviewPanel` from this state. + * + * @param T Type of the webview's state. */ - interface WebviewPanelSerializer { + interface WebviewPanelSerializer { /** * Restore a webview panel from its serialized `state`. * @@ -7106,7 +7124,7 @@ declare module 'vscode' { * * @return Thenable indicating that the webview has been fully restored. */ - deserializeWebviewPanel(webviewPanel: WebviewPanel, state: any): Thenable; + deserializeWebviewPanel(webviewPanel: WebviewPanel, state: T): Thenable; } /** @@ -8134,8 +8152,8 @@ declare module 'vscode' { export function setStatusBarMessage(text: string): Disposable; /** - * ~~Show progress in the Source Control viewlet while running the given callback and while - * its returned promise isn't resolve or rejected.~~ + * Show progress in the Source Control viewlet while running the given callback and while + * its returned promise isn't resolve or rejected. * * @deprecated Use `withProgress` instead. * @@ -9571,8 +9589,8 @@ declare module 'vscode' { export const fs: FileSystem; /** - * ~~The folder that is open in the editor. `undefined` when no folder - * has been opened.~~ + * The folder that is open in the editor. `undefined` when no folder + * has been opened. * * @deprecated Use [`workspaceFolders`](#workspace.workspaceFolders) instead. */ @@ -9713,7 +9731,9 @@ declare module 'vscode' { /** * Find files across all [workspace folders](#workspace.workspaceFolders) in the workspace. * - * @sample `findFiles('**​/*.js', '**​/node_modules/**', 10)` + * @example + * findFiles('**​/*.js', '**​/node_modules/**', 10) + * * @param include A [glob pattern](#GlobPattern) that defines the files to search for. The glob pattern * will be matched against the file paths of resulting matches relative to their workspace. Use a [relative pattern](#RelativePattern) * to restrict the search results to a [workspace folder](#WorkspaceFolder). @@ -9952,7 +9972,7 @@ declare module 'vscode' { export const onDidChangeConfiguration: Event; /** - * ~~Register a task provider.~~ + * Register a task provider. * * @deprecated Use the corresponding function on the `tasks` namespace instead * @@ -10578,6 +10598,26 @@ declare module 'vscode' { * resource state. */ readonly decorations?: SourceControlResourceDecorations; + + /** + * Context value of the resource state. This can be used to contribute resource specific actions. + * For example, if a resource is given a context value as `diffable`. When contributing actions to `scm/resourceState/context` + * using `menus` extension point, you can specify context value for key `scmResourceState` in `when` expressions, like `scmResourceState == diffable`. + * ``` + * "contributes": { + * "menus": { + * "scm/resourceState/context": [ + * { + * "command": "extension.diff", + * "when": "scmResourceState == diffable" + * } + * ] + * } + * } + * ``` + * This will show action `extension.diff` only for resources with `contextValue` is `diffable`. + */ + readonly contextValue?: string; } /** @@ -10691,8 +10731,8 @@ declare module 'vscode' { export namespace scm { /** - * ~~The [input box](#SourceControlInputBox) for the last source control - * created by the extension.~~ + * The [input box](#SourceControlInputBox) for the last source control + * created by the extension. * * @deprecated Use SourceControl.inputBox instead */ diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index a2b91feab3..7c451c1ffc 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -717,16 +717,69 @@ declare module 'vscode' { //#region file-decorations: https://github.com/microsoft/vscode/issues/54938 + // TODO@jrieken FileDecoration, FileDecorationProvider etc. + // TODO@jrieken Add selector notion to limit decorations to a view. + // TODO@jrieken Rename `Decoration.letter` to `short` so that it could be used for coverage et al. + export class Decoration { + + /** + * A letter that represents this decoration. + */ letter?: string; + + /** + * The human-readable title for this decoration. + */ title?: string; + + /** + * The color of this decoration. + */ color?: ThemeColor; + + /** + * The priority of this decoration. + */ priority?: number; + + /** + * A flag expressing that this decoration should be + * propagted to its parents. + */ bubble?: boolean; + + /** + * Creates a new decoration. + * + * @param letter A letter that represents the decoration. + * @param title The title of the decoration. + * @param color The color of the decoration. + */ + constructor(letter?: string, title?: string, color?: ThemeColor); } + /** + * The decoration provider interfaces defines the contract between extensions and + * file decorations. + */ export interface DecorationProvider { + + /** + * An event to signal decorations for one or many files have changed. + * + * @see [EventEmitter](#EventEmitter + */ onDidChangeDecorations: Event; + + /** + * Provide decorations for a given uri. + * + * + * @param uri The uri of the file to provide a decoration for. + * @param token A cancellation token. + * @returns A decoration or a thenable that resolves to such. + */ provideDecoration(uri: Uri, token: CancellationToken): ProviderResult; } @@ -1260,6 +1313,7 @@ declare module 'vscode' { } export interface NotebookCell { + readonly index: number; readonly notebook: NotebookDocument; readonly uri: Uri; readonly cellKind: CellKind; @@ -1322,7 +1376,6 @@ declare module 'vscode' { readonly isUntitled: boolean; readonly cells: ReadonlyArray; languages: string[]; - displayOrder?: GlobPattern[]; metadata: NotebookDocumentMetadata; } @@ -1346,21 +1399,17 @@ declare module 'vscode' { } export interface WorkspaceEdit { - replaceCells(uri: Uri, start: number, end: number, cells: NotebookCellData[], metadata?: WorkspaceEditEntryMetadata): void; - replaceCellOutput(uri: Uri, index: number, outputs: CellOutput[], metadata?: WorkspaceEditEntryMetadata): void; - replaceCellMetadata(uri: Uri, index: number, cellMetadata: NotebookCellMetadata, metadata?: WorkspaceEditEntryMetadata): void; + replaceNotebookMetadata(uri: Uri, value: NotebookDocumentMetadata): void; + replaceNotebookCells(uri: Uri, start: number, end: number, cells: NotebookCellData[], metadata?: WorkspaceEditEntryMetadata): void; + replaceNotebookCellOutput(uri: Uri, index: number, outputs: CellOutput[], metadata?: WorkspaceEditEntryMetadata): void; + replaceNotebookCellMetadata(uri: Uri, index: number, cellMetadata: NotebookCellMetadata, metadata?: WorkspaceEditEntryMetadata): void; } - export interface NotebookEditorCellEdit { - + export interface NotebookEditorEdit { + replaceMetadata(value: NotebookDocumentMetadata): void; replaceCells(start: number, end: number, cells: NotebookCellData[]): void; - replaceOutput(index: number, outputs: CellOutput[]): void; - replaceMetadata(index: number, metadata: NotebookCellMetadata): void; - - /** @deprecated */ - insert(index: number, content: string | string[], language: string, type: CellKind, outputs: CellOutput[], metadata: NotebookCellMetadata | undefined): void; - /** @deprecated */ - delete(index: number): void; + replaceCellOutput(index: number, outputs: CellOutput[]): void; + replaceCellMetadata(index: number, metadata: NotebookCellMetadata): void; } export interface NotebookCellRange { @@ -1444,7 +1493,17 @@ declare module 'vscode' { */ asWebviewUri(localResource: Uri): Uri; - edit(callback: (editBuilder: NotebookEditorCellEdit) => void): Thenable; + /** + * Perform an edit on the notebook associated with this notebook editor. + * + * The given callback-function is invoked with an [edit-builder](#NotebookEditorEdit) which must + * be used to make edits. Note that the edit-builder is only valid while the + * callback executes. + * + * @param callback A function which can create edits using an [edit-builder](#NotebookEditorEdit). + * @return A promise that resolves with a value indicating if the edits could be applied. + */ + edit(callback: (editBuilder: NotebookEditorEdit) => void): Thenable; revealRange(range: NotebookCellRange, revealType?: NotebookEditorRevealType): void; } @@ -1459,6 +1518,10 @@ declare module 'vscode' { outputId: string; } + export interface NotebookDocumentMetadataChangeEvent { + readonly document: NotebookDocument; + } + export interface NotebookCellsChangeData { readonly start: number; readonly deletedCount: number; @@ -1736,6 +1799,7 @@ declare module 'vscode' { export const onDidChangeActiveNotebookEditor: Event; export const onDidChangeNotebookEditorSelection: Event; export const onDidChangeNotebookEditorVisibleRanges: Event; + export const onDidChangeNotebookDocumentMetadata: Event; export const onDidChangeNotebookCells: Event; export const onDidChangeCellOutputs: Event; export const onDidChangeCellLanguage: Event; @@ -1976,32 +2040,6 @@ declare module 'vscode' { //#endregion - //#region Support `scmResourceState` in `when` clauses #86180 https://github.com/microsoft/vscode/issues/86180 - - export interface SourceControlResourceState { - /** - * Context value of the resource state. This can be used to contribute resource specific actions. - * For example, if a resource is given a context value as `diffable`. When contributing actions to `scm/resourceState/context` - * using `menus` extension point, you can specify context value for key `scmResourceState` in `when` expressions, like `scmResourceState == diffable`. - * ``` - * "contributes": { - * "menus": { - * "scm/resourceState/context": [ - * { - * "command": "extension.diff", - * "when": "scmResourceState == diffable" - * } - * ] - * } - * } - * ``` - * This will show action `extension.diff` only for resources with `contextValue` is `diffable`. - */ - readonly contextValue?: string; - } - - //#endregion - //#region https://github.com/microsoft/vscode/issues/104436 export enum ExtensionRuntime { @@ -2058,6 +2096,11 @@ declare module 'vscode' { */ title?: string; + /** + * Human-readable string which is rendered less prominently in the title. + */ + description?: string; + /** * Event fired when the view is disposed. * @@ -2086,8 +2129,22 @@ declare module 'vscode' { * Note that hiding a view using the context menu instead disposes of the view and fires `onDidDispose`. */ readonly onDidChangeVisibility: Event; + + /** + * Reveal the view in the UI. + * + * If the view is collapsed, this will expand it. + * + * @param preserveFocus When `true` the view will not take focus. + */ + show(preserveFocus?: boolean): void; } + /** + * Additional information the webview view being resolved. + * + * @param T Type of the webview's state. + */ interface WebviewViewResolveContext { /** * Persisted state from the webview content. @@ -2177,4 +2234,26 @@ declare module 'vscode' { }): Disposable; } //#endregion + + //#region + + export interface FileSystem { + /** + * Check if a given file system supports writing files. + * + * Keep in mind that just because a file system supports writing, that does + * not mean that writes will always succeed. There may be permissions issues + * or other errors that prevent writing a file. + * + * @param scheme The scheme of the filesystem, for example `file` or `git`. + * + * @return `true` if the file system supports writing, `false` if it does not + * support writing (i.e. it is readonly), and `undefined` if VS Code does not + * know about the filesystem. + */ + isWritableFileSystem(scheme: string): boolean | undefined; + } + + + //#endregion } diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts index dd43907ea2..caf27de61e 100644 --- a/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -15,6 +15,7 @@ import { TokenClassificationExtensionPoints } from 'vs/workbench/services/themes import { LanguageConfigurationFileHandler } from 'vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint'; // --- mainThread participants +import './mainThreadBulkEdits'; import './mainThreadCodeInsets'; import './mainThreadClipboard'; import './mainThreadCommands'; diff --git a/src/vs/workbench/api/browser/mainThreadBulkEdits.ts b/src/vs/workbench/api/browser/mainThreadBulkEdits.ts new file mode 100644 index 0000000000..1ae1eec9ca --- /dev/null +++ b/src/vs/workbench/api/browser/mainThreadBulkEdits.ts @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IBulkEditService, ResourceEdit, ResourceFileEdit, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; +import { IExtHostContext, IWorkspaceEditDto, WorkspaceEditType, MainThreadBulkEditsShape, MainContext } from 'vs/workbench/api/common/extHost.protocol'; +import { revive } from 'vs/base/common/marshalling'; +import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits'; +import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; + +function reviveWorkspaceEditDto2(data: IWorkspaceEditDto | undefined): ResourceEdit[] { + if (!data?.edits) { + return []; + } + + const result: ResourceEdit[] = []; + for (let edit of revive(data).edits) { + if (edit._type === WorkspaceEditType.File) { + result.push(new ResourceFileEdit(edit.oldUri, edit.newUri, edit.options, edit.metadata)); + } else if (edit._type === WorkspaceEditType.Text) { + result.push(new ResourceTextEdit(edit.resource, edit.edit, edit.modelVersionId, edit.metadata)); + } else if (edit._type === WorkspaceEditType.Cell) { + result.push(new ResourceNotebookCellEdit(edit.resource, edit.edit, edit.notebookVersionId, edit.metadata)); + } + } + return result; +} + +@extHostNamedCustomer(MainContext.MainThreadBulkEdits) +export class MainThreadBulkEdits implements MainThreadBulkEditsShape { + + constructor( + _extHostContext: IExtHostContext, + @IBulkEditService private readonly _bulkEditService: IBulkEditService, + ) { } + + dispose(): void { } + + $tryApplyWorkspaceEdit(dto: IWorkspaceEditDto): Promise { + const edits = reviveWorkspaceEditDto2(dto); + return this._bulkEditService.apply(edits).then(() => true, _err => false); + } +} diff --git a/src/vs/workbench/api/browser/mainThreadCustomEditors.ts b/src/vs/workbench/api/browser/mainThreadCustomEditors.ts index aa79137986..e506f900a1 100644 --- a/src/vs/workbench/api/browser/mainThreadCustomEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadCustomEditors.ts @@ -29,12 +29,13 @@ import { CustomDocumentBackupData } from 'vs/workbench/contrib/customEditor/brow import { ICustomEditorModel, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { CustomTextEditorModel } from 'vs/workbench/contrib/customEditor/common/customTextEditorModel'; import { WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview'; -import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; -import { IWebviewWorkbenchService } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; +import { WebviewInput } from 'vs/workbench/contrib/webviewPanel/browser/webviewEditorInput'; +import { IWebviewWorkbenchService } from 'vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { IWorkingCopy, IWorkingCopyBackup, IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; @@ -153,7 +154,7 @@ export class MainThreadCustomEditors extends Disposable implements extHostProtoc return; } - webviewInput.webview.onDispose(() => { + webviewInput.webview.onDidDispose(() => { // If the model is still dirty, make sure we have time to save it if (modelRef.object.isDirty()) { const sub = modelRef.object.onDidChangeDirty(() => { @@ -314,6 +315,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod @IUndoRedoService private readonly _undoService: IUndoRedoService, @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, @IWorkingCopyService workingCopyService: IWorkingCopyService, + @IPathService private readonly _pathService: IPathService ) { super(); @@ -535,7 +537,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod } const remoteAuthority = this._environmentService.configuration.remoteAuthority; - const localResource = toLocalResource(this._editorResource, remoteAuthority); + const localResource = toLocalResource(this._editorResource, remoteAuthority, this._pathService.defaultUriScheme); return this._fileDialogService.pickFileToSave(localResource, options?.availableFileSystems); } diff --git a/src/vs/workbench/api/browser/mainThreadDocuments.ts b/src/vs/workbench/api/browser/mainThreadDocuments.ts index a7b355063b..b7dc972b38 100644 --- a/src/vs/workbench/api/browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadDocuments.ts @@ -20,6 +20,7 @@ import { toLocalResource, extUri, IExtUri } from 'vs/base/common/resources'; import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { Emitter } from 'vs/base/common/event'; +import { IPathService } from 'vs/workbench/services/path/common/pathService'; export class BoundModelReferenceCollection { @@ -126,7 +127,8 @@ export class MainThreadDocuments extends Disposable implements MainThreadDocumen @ITextModelService textModelResolverService: ITextModelService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IUriIdentityService uriIdentityService: IUriIdentityService, - @IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService + @IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService, + @IPathService private readonly _pathService: IPathService ) { super(); this._modelService = modelService; @@ -271,7 +273,7 @@ export class MainThreadDocuments extends Disposable implements MainThreadDocumen } private _handleUntitledScheme(uri: URI): Promise { - const asLocalUri = toLocalResource(uri, this._environmentService.configuration.remoteAuthority); + const asLocalUri = toLocalResource(uri, this._environmentService.configuration.remoteAuthority, this._pathService.defaultUriScheme); return this._fileService.resolve(asLocalUri).then(stats => { // don't create a new file ontop of an existing file return Promise.reject(new Error('file already exists')); diff --git a/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts index 5620fb2e25..f58ee6c1a3 100644 --- a/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts @@ -30,6 +30,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { IPathService } from 'vs/workbench/services/path/common/pathService'; namespace delta { @@ -334,10 +335,11 @@ export class MainThreadDocumentsAndEditors { @IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService, @IUriIdentityService uriIdentityService: IUriIdentityService, @IClipboardService private readonly _clipboardService: IClipboardService, + @IPathService pathService: IPathService ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocumentsAndEditors); - this._mainThreadDocuments = this._toDispose.add(new MainThreadDocuments(this, extHostContext, this._modelService, this._textFileService, fileService, textModelResolverService, environmentService, uriIdentityService, workingCopyFileService)); + this._mainThreadDocuments = this._toDispose.add(new MainThreadDocuments(this, extHostContext, this._modelService, this._textFileService, fileService, textModelResolverService, environmentService, uriIdentityService, workingCopyFileService, pathService)); extHostContext.set(MainContext.MainThreadDocuments, this._mainThreadDocuments); const mainThreadTextEditors = this._toDispose.add(new MainThreadTextEditors(this, extHostContext, codeEditorService, bulkEditService, this._editorService, this._editorGroupService)); diff --git a/src/vs/workbench/api/browser/mainThreadEditors.ts b/src/vs/workbench/api/browser/mainThreadEditors.ts index 943bb4e0b4..fb7df2d5d1 100644 --- a/src/vs/workbench/api/browser/mainThreadEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadEditors.ts @@ -44,7 +44,7 @@ function reviveWorkspaceEditDto2(data: IWorkspaceEditDto | undefined): ResourceE } else if (edit._type === WorkspaceEditType.Text) { result.push(new ResourceTextEdit(edit.resource, edit.edit, edit.modelVersionId, edit.metadata)); } else if (edit._type === WorkspaceEditType.Cell) { - result.push(new ResourceNotebookCellEdit(edit.resource, edit.edit, edit.modelVersionId, edit.metadata)); + result.push(new ResourceNotebookCellEdit(edit.resource, edit.edit, edit.notebookVersionId, edit.metadata)); } } return result; diff --git a/src/vs/workbench/api/browser/mainThreadFileSystem.ts b/src/vs/workbench/api/browser/mainThreadFileSystem.ts index 6ed0a49032..965b9af159 100644 --- a/src/vs/workbench/api/browser/mainThreadFileSystem.ts +++ b/src/vs/workbench/api/browser/mainThreadFileSystem.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; -import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { FileWriteOptions, FileSystemProviderCapabilities, IFileChange, IFileService, IStat, IWatchOptions, FileType, FileOverwriteOptions, FileDeleteOptions, FileOpenOptions, IFileStat, FileOperationError, FileOperationResult, FileSystemProviderErrorCode, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithFileFolderCopyCapability } from 'vs/platform/files/common/files'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; @@ -16,15 +16,22 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape { private readonly _proxy: ExtHostFileSystemShape; private readonly _fileProvider = new Map(); + private readonly _disposables = new DisposableStore(); constructor( extHostContext: IExtHostContext, @IFileService private readonly _fileService: IFileService, ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostFileSystem); + + const infoProxy = extHostContext.getProxy(ExtHostContext.ExtHostFileSystemInfo); + + this._disposables.add(_fileService.onDidChangeFileSystemProviderRegistrations(e => infoProxy.$acceptProviderInfos(e.scheme, e.provider?.capabilities ?? null))); + this._disposables.add(_fileService.onDidChangeFileSystemProviderCapabilities(e => infoProxy.$acceptProviderInfos(e.scheme, e.provider.capabilities))); } dispose(): void { + this._disposables.dispose(); dispose(this._fileProvider.values()); this._fileProvider.clear(); } @@ -34,7 +41,7 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape { } $unregisterProvider(handle: number): void { - dispose(this._fileProvider.get(handle)); + this._fileProvider.get(handle)?.dispose(); this._fileProvider.delete(handle); } diff --git a/src/vs/workbench/api/browser/mainThreadMessageService.ts b/src/vs/workbench/api/browser/mainThreadMessageService.ts index 3098d9a34c..196f40dba0 100644 --- a/src/vs/workbench/api/browser/mainThreadMessageService.ts +++ b/src/vs/workbench/api/browser/mainThreadMessageService.ts @@ -39,9 +39,9 @@ export class MainThreadMessageService implements MainThreadMessageServiceShape { } } - private _showMessage(severity: Severity, message: string, commands: { title: string; isCloseAffordance: boolean; handle: number; }[], extension: IExtensionDescription | undefined): Promise { + private _showMessage(severity: Severity, message: string, commands: { title: string; isCloseAffordance: boolean; handle: number; }[], extension: IExtensionDescription | undefined): Promise { - return new Promise(resolve => { + return new Promise(resolve => { const primaryActions: MessageItemAction[] = []; diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index f0364693a3..32409954f4 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -7,18 +7,22 @@ import * as DOM from 'vs/base/browser/dom'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter } from 'vs/base/common/event'; import { combinedDisposable, Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { ResourceMap } from 'vs/base/common/map'; +import { Schemas } from 'vs/base/common/network'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService'; -import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, CellKind, DisplayOrderKey, ICellEditOperation, ICellRange, IEditor, INotebookDocumentFilter, NotebookCellMetadata, NotebookCellOutputsSplice, NotebookDocumentMetadata, NOTEBOOK_DISPLAY_ORDER, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, DisplayOrderKey, ICellEditOperation, ICellRange, IEditor, IMainCellDto, INotebookDocumentFilter, NotebookCellOutputsSplice, NotebookCellsChangeType, NOTEBOOK_DISPLAY_ORDER, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IMainNotebookController, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, INotebookCellStatusBarEntryDto, INotebookDocumentsAndEditorsDelta, MainContext, MainThreadNotebookShape, NotebookEditorRevealType, NotebookExtensionDescription } from '../common/extHost.protocol'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, INotebookCellStatusBarEntryDto, INotebookDocumentsAndEditorsDelta, INotebookModelAddedData, MainContext, MainThreadNotebookShape, NotebookEditorRevealType, NotebookExtensionDescription } from '../common/extHost.protocol'; class DocumentAndEditorState { static ofSets(before: Set, after: Set): { removed: T[], added: T[] } { @@ -58,7 +62,7 @@ class DocumentAndEditorState { const apiEditors = []; for (let id in after.textEditors) { const editor = after.textEditors.get(id)!; - apiEditors.push({ id, documentUri: editor.uri!, selections: editor!.textModel!.selections, visibleRanges: editor.visibleRanges }); + apiEditors.push({ id, documentUri: editor.uri!, selections: editor!.getSelectionHandles(), visibleRanges: editor.visibleRanges }); } return { @@ -72,7 +76,7 @@ class DocumentAndEditorState { const addedAPIEditors = editorDelta.added.map(add => ({ id: add.getId(), documentUri: add.uri!, - selections: add.textModel!.selections || [], + selections: add.getSelectionHandles(), visibleRanges: add.visibleRanges })); @@ -84,10 +88,9 @@ class DocumentAndEditorState { const visibleEditorDelta = DocumentAndEditorState.ofMaps(before.visibleEditors, after.visibleEditors); return { - addedDocuments: documentDelta.added.map(e => { + addedDocuments: documentDelta.added.map((e: NotebookTextModel): INotebookModelAddedData => { return { viewType: e.viewType, - handle: e.handle, uri: e.uri, metadata: e.metadata, versionId: e.versionId, @@ -129,13 +132,13 @@ class DocumentAndEditorState { @extHostNamedCustomer(MainContext.MainThreadNotebook) export class MainThreadNotebooks extends Disposable implements MainThreadNotebookShape { - private readonly _notebookProviders = new Map(); + private readonly _notebookProviders = new Map(); private readonly _notebookKernelProviders = new Map, provider: IDisposable }>(); private readonly _proxy: ExtHostNotebookShape; private _toDisposeOnEditorRemove = new Map(); private _currentState?: DocumentAndEditorState; private _editorEventListenersMapping: Map = new Map(); - private _documentEventListenersMapping: Map = new Map(); + private _documentEventListenersMapping: ResourceMap = new ResourceMap(); private readonly _cellStatusBarEntries: Map = new Map(); constructor( @@ -145,29 +148,21 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo @IEditorService private readonly editorService: IEditorService, @IAccessibilityService private readonly accessibilityService: IAccessibilityService, @ILogService private readonly logService: ILogService, - @INotebookCellStatusBarService private readonly cellStatusBarService: INotebookCellStatusBarService + @INotebookCellStatusBarService private readonly cellStatusBarService: INotebookCellStatusBarService, + @IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService, ) { super(); this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebook); this.registerListeners(); } - async $tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[]): Promise { + async $tryApplyEdits(_viewType: string, resource: UriComponents, modelVersionId: number, cellEdits: ICellEditOperation[]): Promise { const textModel = this._notebookService.getNotebookTextModel(URI.from(resource)); - if (textModel) { - this._notebookService.transformEditsOutputs(textModel, edits); - return textModel.applyEdit(modelVersionId, edits, true); + if (!textModel) { + return false; } - - return false; - } - - async removeNotebookTextModel(uri: URI): Promise { - // TODO@rebornix, remove cell should use emitDelta as well to ensure document/editor events are sent together - this._proxy.$acceptDocumentAndEditorsDelta({ removedDocuments: [uri] }); - let textModelDisposableStore = this._documentEventListenersMapping.get(uri.toString()); - textModelDisposableStore?.dispose(); - this._documentEventListenersMapping.delete(URI.from(uri).toString()); + this._notebookService.transformEditsOutputs(textModel, cellEdits); + return textModel.applyEdits(modelVersionId, cellEdits, true, undefined, () => undefined); } private _isDeltaEmpty(delta: INotebookDocumentsAndEditorsDelta) { @@ -231,7 +226,12 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo if (!this._editorEventListenersMapping.has(editor.getId())) { const disposableStore = new DisposableStore(); disposableStore.add(editor.onDidChangeVisibleRanges(() => { - this._proxy.$acceptEditorPropertiesChanged(editor.getId(), { visibleRanges: { ranges: editor.visibleRanges } }); + this._proxy.$acceptEditorPropertiesChanged(editor.getId(), { visibleRanges: { ranges: editor.visibleRanges }, selections: null }); + })); + + disposableStore.add(editor.onDidChangeSelection(() => { + const selectionHandles = editor.getSelectionHandles(); + this._proxy.$acceptEditorPropertiesChanged(editor.getId(), { visibleRanges: null, selections: { selections: selectionHandles } }); })); this._editorEventListenersMapping.set(editor.getId(), disposableStore); @@ -256,40 +256,79 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo notebookEditorAddedHandler(editor); }); - const notebookDocumentAddedHandler = (doc: URI) => { - if (!this._editorEventListenersMapping.has(doc.toString())) { - const disposableStore = new DisposableStore(); - const textModel = this._notebookService.getNotebookTextModel(doc); - disposableStore.add(textModel!.onDidModelChangeProxy(e => { - this._proxy.$acceptModelChanged(textModel!.uri, e, textModel!.isDirty); - this._proxy.$acceptDocumentPropertiesChanged(doc, { selections: { selections: textModel!.selections }, metadata: null }); - })); - disposableStore.add(textModel!.onDidSelectionChange(e => { - const selectionsChange = e ? { selections: e } : null; - this._proxy.$acceptDocumentPropertiesChanged(doc, { selections: selectionsChange, metadata: null }); - })); + const cellToDto = (cell: NotebookCellTextModel): IMainCellDto => { + return { + handle: cell.handle, + uri: cell.uri, + source: cell.textBuffer.getLinesContent(), + eol: cell.textBuffer.getEOL(), + language: cell.language, + cellKind: cell.cellKind, + outputs: cell.outputs, + metadata: cell.metadata + }; + }; - this._editorEventListenersMapping.set(textModel!.uri.toString(), disposableStore); + + const notebookDocumentAddedHandler = (textModel: NotebookTextModel) => { + if (!this._documentEventListenersMapping.has(textModel.uri)) { + const disposableStore = new DisposableStore(); + disposableStore.add(textModel!.onDidChangeContent(event => { + const dto = event.rawEvents.map(e => { + const data = + e.kind === NotebookCellsChangeType.ModelChange || e.kind === NotebookCellsChangeType.Initialize + ? { + kind: e.kind, + versionId: event.versionId, + changes: e.changes.map(diff => [diff[0], diff[1], diff[2].map(cell => cellToDto(cell as NotebookCellTextModel))] as [number, number, IMainCellDto[]]) + } + : ( + e.kind === NotebookCellsChangeType.Move + ? { + kind: e.kind, + index: e.index, + length: e.length, + newIdx: e.newIdx, + versionId: event.versionId, + cells: e.cells.map(cell => cellToDto(cell as NotebookCellTextModel)) + } + : e + ); + + return data; + }); + + /** + * TODO@rebornix, @jrieken + * When a document is modified, it will trigger onDidChangeContent events. + * The first event listener is this one, which doesn't know if the text model is dirty or not. It can ask `workingCopyService` but get the wrong result + * The second event listener is `NotebookEditorModel`, which will then set `isDirty` to `true`. + * Since `e.transient` decides if the model should be dirty or not, we will use the same logic here. + */ + const hasNonTransientEvent = event.rawEvents.find(e => !e.transient); + this._proxy.$acceptModelChanged(textModel.uri, { + rawEvents: dto, + versionId: event.versionId + }, !!hasNonTransientEvent); + + const hasDocumentMetadataChangeEvent = event.rawEvents.find(e => e.kind === NotebookCellsChangeType.ChangeDocumentMetadata); + if (!!hasDocumentMetadataChangeEvent) { + this._proxy.$acceptDocumentPropertiesChanged(textModel.uri, { metadata: textModel.metadata }); + } + })); + this._documentEventListenersMapping.set(textModel!.uri, disposableStore); } }; - this._register(this._notebookService.onNotebookDocumentAdd((documents) => { - documents.forEach(doc => { - notebookDocumentAddedHandler(doc); - }); + this._notebookService.listNotebookDocuments().forEach(notebookDocumentAddedHandler); + this._register(this._notebookService.onDidAddNotebookDocument(document => { + notebookDocumentAddedHandler(document); this._updateState(); })); - this._notebookService.listNotebookDocuments().forEach((doc) => { - notebookDocumentAddedHandler(doc.uri); - }); - - this._register(this._notebookService.onNotebookDocumentRemove((documents) => { - documents.forEach(doc => { - this._documentEventListenersMapping.get(doc.toString())?.dispose(); - this._documentEventListenersMapping.delete(doc.toString()); - }); - + this._register(this._notebookService.onDidRemoveNotebookDocument(uri => { + this._documentEventListenersMapping.get(uri)?.dispose(); + this._documentEventListenersMapping.delete(uri); this._updateState(); })); @@ -404,16 +443,12 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo // } } - async $registerNotebookProvider(_extension: NotebookExtensionDescription, _viewType: string, _supportBackup: boolean, options: { transientOutputs: boolean; transientMetadata: TransientMetadata }): Promise { + async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, supportBackup: boolean, options: { transientOutputs: boolean; transientMetadata: TransientMetadata }): Promise { const controller: IMainNotebookController = { - supportBackup: _supportBackup, - options: options, + supportBackup, + options, reloadNotebook: async (mainthreadTextModel: NotebookTextModel) => { - const data = await this._proxy.$resolveNotebookData(_viewType, mainthreadTextModel.uri); - if (!data) { - return; - } - + const data = await this._proxy.$resolveNotebookData(viewType, mainthreadTextModel.uri); mainthreadTextModel.updateLanguages(data.languages); mainthreadTextModel.metadata = data.metadata; mainthreadTextModel.transientOptions = options; @@ -425,31 +460,17 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo this._notebookService.transformEditsOutputs(mainthreadTextModel, edits); await new Promise(resolve => { DOM.scheduleAtNextAnimationFrame(() => { - const ret = mainthreadTextModel!.applyEdit(mainthreadTextModel!.versionId, edits, true); + const ret = mainthreadTextModel!.applyEdits(mainthreadTextModel!.versionId, edits, true, undefined, () => undefined); resolve(ret); }); }); }, - createNotebook: async (textModel: NotebookTextModel, backupId?: string) => { - // open notebook document - const data = await this._proxy.$resolveNotebookData(textModel.viewType, textModel.uri, backupId); - if (!data) { - return; - } - - textModel.updateLanguages(data.languages); - textModel.metadata = data.metadata; - textModel.transientOptions = options; - - if (data.cells.length) { - textModel.initialize(data!.cells); - } else { - const mainCell = textModel.createCellTextModel('', textModel.resolvedLanguages.length ? textModel.resolvedLanguages[0] : '', CellKind.Code, [], undefined); - textModel.insertTemplateCell(mainCell); - } - - this._proxy.$acceptDocumentPropertiesChanged(textModel.uri, { selections: null, metadata: textModel.metadata }); - return; + resolveNotebookDocument: async (viewType: string, uri: URI, backupId?: string) => { + const data = await this._proxy.$resolveNotebookData(viewType, uri, backupId); + return { + data, + transientOptions: options + }; }, resolveNotebookEditor: async (viewType: string, uri: URI, editorId: string) => { await this._proxy.$resolveNotebookEditor(viewType, uri, editorId); @@ -457,34 +478,28 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo onDidReceiveMessage: (editorId: string, rendererType: string | undefined, message: unknown) => { this._proxy.$onDidReceiveMessage(editorId, rendererType, message); }, - removeNotebookDocument: async (uri: URI) => { - return this.removeNotebookTextModel(uri); - }, save: async (uri: URI, token: CancellationToken) => { - return this._proxy.$saveNotebook(_viewType, uri, token); + return this._proxy.$saveNotebook(viewType, uri, token); }, saveAs: async (uri: URI, target: URI, token: CancellationToken) => { - return this._proxy.$saveNotebookAs(_viewType, uri, target, token); + return this._proxy.$saveNotebookAs(viewType, uri, target, token); }, backup: async (uri: URI, token: CancellationToken) => { - return this._proxy.$backup(_viewType, uri, token); + return this._proxy.$backup(viewType, uri, token); } }; - this._notebookProviders.set(_viewType, controller); - this._notebookService.registerNotebookController(_viewType, _extension, controller); + const disposable = this._notebookService.registerNotebookController(viewType, extension, controller); + this._notebookProviders.set(viewType, { controller, disposable }); return; } - async $onNotebookChange(viewType: string, uri: UriComponents): Promise { - const textModel = this._notebookService.getNotebookTextModel(URI.from(uri)); - textModel?.handleUnknownChange(); - } - async $unregisterNotebookProvider(viewType: string): Promise { - this._notebookProviders.delete(viewType); - this._notebookService.unregisterNotebookProvider(viewType); - return; + const entry = this._notebookProviders.get(viewType); + if (entry) { + entry.disposable.dispose(); + this._notebookProviders.delete(viewType); + } } async $registerNotebookKernelProvider(extension: NotebookExtensionDescription, handle: number, documentFilter: INotebookDocumentFilter): Promise { @@ -547,26 +562,28 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo textModel?.updateLanguages(languages); } - async $updateNotebookMetadata(viewType: string, resource: UriComponents, metadata: NotebookDocumentMetadata): Promise { - this.logService.debug('MainThreadNotebooks#updateNotebookMetadata', resource.path, metadata); - const textModel = this._notebookService.getNotebookTextModel(URI.from(resource)); - textModel?.updateNotebookMetadata(metadata); - } - - async $updateNotebookCellMetadata(viewType: string, resource: UriComponents, handle: number, metadata: NotebookCellMetadata): Promise { - this.logService.debug('MainThreadNotebooks#updateNotebookCellMetadata', resource.path, handle, metadata); - const textModel = this._notebookService.getNotebookTextModel(URI.from(resource)); - textModel?.changeCellMetadata(handle, metadata, true); - } - async $spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[]): Promise { this.logService.debug('MainThreadNotebooks#spliceNotebookCellOutputs', resource.path, cellHandle); const textModel = this._notebookService.getNotebookTextModel(URI.from(resource)); - if (textModel) { - this._notebookService.transformSpliceOutputs(textModel, splices); - textModel.spliceNotebookCellOutputs(cellHandle, splices); + if (!textModel) { + return; } + + this._notebookService.transformSpliceOutputs(textModel, splices); + const cell = textModel.cells.find(cell => cell.handle === cellHandle); + + if (!cell) { + return; + } + + textModel.applyEdits(textModel.versionId, [ + { + editType: CellEditType.OutputsSplice, + index: textModel.cells.indexOf(cell), + splices + } + ], true, undefined, () => undefined); } async $postMessage(editorId: string, forRendererId: string | undefined, value: any): Promise { @@ -579,21 +596,30 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo return false; } - $onDidEdit(resource: UriComponents, viewType: string, editId: number, label: string | undefined): void { + $onUndoableContentChange(resource: UriComponents, viewType: string, editId: number, label: string | undefined): void { const textModel = this._notebookService.getNotebookTextModel(URI.from(resource)); if (textModel) { - textModel.handleEdit(label, () => { - return this._proxy.$undoNotebook(textModel.viewType, textModel.uri, editId, textModel.isDirty); + textModel.handleUnknownUndoableEdit(label, () => { + const isDirty = this._workingCopyService.isDirty(textModel.uri.with({ scheme: Schemas.vscodeNotebook })); + return this._proxy.$undoNotebook(textModel.viewType, textModel.uri, editId, isDirty); }, () => { - return this._proxy.$redoNotebook(textModel.viewType, textModel.uri, editId, textModel.isDirty); + const isDirty = this._workingCopyService.isDirty(textModel.uri.with({ scheme: Schemas.vscodeNotebook })); + return this._proxy.$redoNotebook(textModel.viewType, textModel.uri, editId, isDirty); }); } } $onContentChange(resource: UriComponents, viewType: string): void { const textModel = this._notebookService.getNotebookTextModel(URI.from(resource)); - textModel?.handleUnknownChange(); + + if (textModel) { + textModel.applyEdits(textModel.versionId, [ + { + editType: CellEditType.Unknown + } + ], true, undefined, () => undefined); + } } async $tryRevealRange(id: string, range: ICellRange, revealType: NotebookEditorRevealType) { @@ -640,4 +666,3 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo } } } - diff --git a/src/vs/workbench/api/browser/mainThreadProgress.ts b/src/vs/workbench/api/browser/mainThreadProgress.ts index f84e42ed8b..21a6b900e0 100644 --- a/src/vs/workbench/api/browser/mainThreadProgress.ts +++ b/src/vs/workbench/api/browser/mainThreadProgress.ts @@ -73,7 +73,7 @@ export class MainThreadProgress implements MainThreadProgressShape { private _createTask(handle: number) { return (progress: IProgress) => { - return new Promise(resolve => { + return new Promise(resolve => { this._progress.set(handle, { resolve, progress }); }); }; diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index aaad238856..c19473dc9e 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -5,7 +5,6 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; -import { assign } from 'vs/base/common/objects'; import { IDisposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle'; import { ISCMService, ISCMRepository, ISCMProvider, ISCMResource, ISCMResourceGroup, ISCMResourceDecorations, IInputValidation } from 'vs/workbench/contrib/scm/common/scm'; import { ExtHostContext, MainThreadSCMShape, ExtHostSCMShape, SCMProviderFeatures, SCMRawResourceSplices, SCMGroupFeatures, MainContext, IExtHostContext } from '../common/extHost.protocol'; @@ -49,7 +48,7 @@ class MainThreadSCMResourceGroup implements ISCMResourceGroup { } $updateGroup(features: SCMGroupFeatures): void { - this.features = assign(this.features, features); + this.features = { ...this.features, ...features }; this._onDidChange.fire(); } @@ -139,7 +138,7 @@ class MainThreadSCMProvider implements ISCMProvider { ) { } $updateSourceControl(features: SCMProviderFeatures): void { - this.features = assign(this.features, features); + this.features = { ...this.features, ...features }; this._onDidChange.fire(); if (typeof features.commitTemplate !== 'undefined') { diff --git a/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts b/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts index e17b2c194a..ef6b2a82eb 100644 --- a/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts +++ b/src/vs/workbench/api/browser/mainThreadWebviewPanels.ts @@ -13,8 +13,8 @@ import { editorGroupToViewColumn, EditorViewColumn, viewColumnToEditorGroup } fr import { IEditorInput } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { WebviewIcons } from 'vs/workbench/contrib/webview/browser/webview'; -import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; -import { ICreateWebViewShowOptions, IWebviewWorkbenchService, WebviewInputOptions } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; +import { WebviewInput } from 'vs/workbench/contrib/webviewPanel/browser/webviewEditorInput'; +import { ICreateWebViewShowOptions, IWebviewWorkbenchService, WebviewInputOptions } from 'vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -140,7 +140,7 @@ export class MainThreadWebviewPanels extends Disposable implements extHostProtoc this._webviewInputs.add(handle, input); this._mainThreadWebviews.addWebview(handle, input.webview); - input.webview.onDispose(() => { + input.webview.onDidDispose(() => { this._proxy.$onDidDisposeWebviewPanel(handle).finally(() => { this._webviewInputs.delete(handle); }); diff --git a/src/vs/workbench/api/browser/mainThreadWebviewViews.ts b/src/vs/workbench/api/browser/mainThreadWebviewViews.ts index 29e507a070..8392ffb7ca 100644 --- a/src/vs/workbench/api/browser/mainThreadWebviewViews.ts +++ b/src/vs/workbench/api/browser/mainThreadWebviewViews.ts @@ -29,13 +29,20 @@ export class MainThreadWebviewsViews extends Disposable implements extHostProtoc } public $setWebviewViewTitle(handle: extHostProtocol.WebviewHandle, value: string | undefined): void { - const webviewView = this._webviewViews.get(handle); - if (!webviewView) { - throw new Error('unknown webview view'); - } + const webviewView = this.getWebviewView(handle); webviewView.title = value; } + public $setWebviewViewDescription(handle: extHostProtocol.WebviewHandle, value: string | undefined): void { + const webviewView = this.getWebviewView(handle); + webviewView.description = value; + } + + public $show(handle: extHostProtocol.WebviewHandle, preserveFocus: boolean): void { + const webviewView = this.getWebviewView(handle); + webviewView.show(preserveFocus); + } + public $registerWebviewViewProvider(viewType: string, options?: { retainContextWhenHidden?: boolean }): void { if (this._webviewViewProviders.has(viewType)) { throw new Error(`View provider for ${viewType} already registered`); @@ -71,7 +78,7 @@ export class MainThreadWebviewsViews extends Disposable implements extHostProtoc }); try { - await this._proxy.$resolveWebviewView(handle, viewType, state, cancellation); + await this._proxy.$resolveWebviewView(handle, viewType, webviewView.title, state, cancellation); } catch (error) { onUnexpectedError(error); webviewView.webview.html = this.mainThreadWebviews.getWebviewResolvedFailedContent(viewType); @@ -89,5 +96,13 @@ export class MainThreadWebviewsViews extends Disposable implements extHostProtoc provider.dispose(); this._webviewViewProviders.delete(viewType); } + + private getWebviewView(handle: string): WebviewView { + const webviewView = this._webviewViews.get(handle); + if (!webviewView) { + throw new Error('unknown webview view'); + } + return webviewView; + } } diff --git a/src/vs/workbench/api/browser/mainThreadWebviews.ts b/src/vs/workbench/api/browser/mainThreadWebviews.ts index 0dc4fa8566..984d730c3d 100644 --- a/src/vs/workbench/api/browser/mainThreadWebviews.ts +++ b/src/vs/workbench/api/browser/mainThreadWebviews.ts @@ -15,7 +15,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IProductService } from 'vs/platform/product/common/productService'; import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol'; import { Webview, WebviewExtensionDescription, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; -import { WebviewInputOptions } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; +import { WebviewInputOptions } from 'vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService'; export class MainThreadWebviews extends Disposable implements extHostProtocol.MainThreadWebviewsShape { @@ -69,7 +69,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma disposables.add(webview.onMessage((message: any) => { this._proxy.$onMessage(handle, message); })); disposables.add(webview.onMissingCsp((extension: ExtensionIdentifier) => this._proxy.$onMissingCsp(handle, extension.value))); - disposables.add(webview.onDispose(() => { + disposables.add(webview.onDidDispose(() => { disposables.dispose(); this._webviews.delete(handle); })); diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index e5c3ca3502..44d8683d08 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -108,9 +108,23 @@ enum InitialVisibility { const viewDescriptor: IJSONSchema = { type: 'object', + required: ['id', 'name'], + defaultSnippets: [{ body: { id: '${1:id}', name: '${2:name}' } }], properties: { + type: { + markdownDescription: localize('vscode.extension.contributes.view.type', "Type of the the view. This can either be `tree` for a tree view based view or `webview` for a webview based view. The default is `tree`."), + type: 'string', + enum: [ + 'tree', + 'webview', + ], + markdownEnumDescriptions: [ + localize('vscode.extension.contributes.view.tree', "The view is backed by a `TreeView` created by `createTreeView`."), + localize('vscode.extension.contributes.view.webview', "The view is backed by a `WebviewView` registered by `registerWebviewViewProvider`."), + ] + }, id: { - description: localize('vscode.extension.contributes.view.id', 'Identifier of the view. This should be unique across all views. It is recommended to include your extension id as part of the view id. Use this to register a data provider through `vscode.window.registerTreeDataProviderForView` API. Also to trigger activating your extension by registering `onView:${id}` event to `activationEvents`.'), + markdownDescription: localize('vscode.extension.contributes.view.id', 'Identifier of the view. This should be unique across all views. It is recommended to include your extension id as part of the view id. Use this to register a data provider through `vscode.window.registerTreeDataProviderForView` API. Also to trigger activating your extension by registering `onView:${id}` event to `activationEvents`.'), type: 'string' }, name: { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index cde8d5a432..baad27b312 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -67,7 +67,6 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; -import { find } from 'vs/base/common/arrays'; import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; import { ExtHostTheming } from 'vs/workbench/api/common/extHostTheming'; import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService'; @@ -80,6 +79,8 @@ import { IExtHostConsumerFileSystem } from 'vs/workbench/api/common/extHostFileS import { ExtHostWebviewViews } from 'vs/workbench/api/common/extHostWebviewView'; import { ExtHostCustomEditors } from 'vs/workbench/api/common/extHostCustomEditors'; import { ExtHostWebviewPanels } from 'vs/workbench/api/common/extHostWebviewPanels'; +import { ExtHostBulkEdits } from 'vs/workbench/api/common/extHostBulkEdits'; +import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; export interface IExtensionApiFactory { (extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode; @@ -92,6 +93,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // services const initData = accessor.get(IExtHostInitDataService); + const extHostFileSystemInfo = accessor.get(IExtHostFileSystemInfo); const extHostConsumerFileSystem = accessor.get(IExtHostConsumerFileSystem); const extensionService = accessor.get(IExtHostExtensionService); const extHostWorkspace = accessor.get(IExtHostWorkspace); @@ -106,6 +108,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostWindow = accessor.get(IExtHostWindow); // register addressable instances + rpcProtocol.set(ExtHostContext.ExtHostFileSystemInfo, extHostFileSystemInfo); rpcProtocol.set(ExtHostContext.ExtHostLogService, extHostLogService); rpcProtocol.set(ExtHostContext.ExtHostWorkspace, extHostWorkspace); rpcProtocol.set(ExtHostContext.ExtHostConfiguration, extHostConfiguration); @@ -128,14 +131,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostUrls = rpcProtocol.set(ExtHostContext.ExtHostUrls, new ExtHostUrls(rpcProtocol)); const extHostDocuments = rpcProtocol.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors)); const extHostDocumentContentProviders = rpcProtocol.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(rpcProtocol, extHostDocumentsAndEditors, extHostLogService)); - const extHostDocumentSaveParticipant = rpcProtocol.set(ExtHostContext.ExtHostDocumentSaveParticipant, new ExtHostDocumentSaveParticipant(extHostLogService, extHostDocuments, rpcProtocol.getProxy(MainContext.MainThreadTextEditors))); + const extHostDocumentSaveParticipant = rpcProtocol.set(ExtHostContext.ExtHostDocumentSaveParticipant, new ExtHostDocumentSaveParticipant(extHostLogService, extHostDocuments, rpcProtocol.getProxy(MainContext.MainThreadBulkEdits))); const extHostNotebook = rpcProtocol.set(ExtHostContext.ExtHostNotebook, new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors, initData.environment, extHostLogService, extensionStoragePaths)); - const extHostEditors = rpcProtocol.set(ExtHostContext.ExtHostEditors, new ExtHostEditors(rpcProtocol, extHostDocumentsAndEditors, extHostNotebook)); + const extHostEditors = rpcProtocol.set(ExtHostContext.ExtHostEditors, new ExtHostEditors(rpcProtocol, extHostDocumentsAndEditors)); const extHostTreeViews = rpcProtocol.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(rpcProtocol.getProxy(MainContext.MainThreadTreeViews), extHostCommands, extHostLogService)); const extHostEditorInsets = rpcProtocol.set(ExtHostContext.ExtHostEditorInsets, new ExtHostEditorInsets(rpcProtocol.getProxy(MainContext.MainThreadEditorInsets), extHostEditors, initData.environment)); const extHostDiagnostics = rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, new ExtHostDiagnostics(rpcProtocol, extHostLogService)); const extHostLanguageFeatures = rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(rpcProtocol, uriTransformer, extHostDocuments, extHostCommands, extHostDiagnostics, extHostLogService, extHostApiDeprecation)); - const extHostFileSystem = rpcProtocol.set(ExtHostContext.ExtHostFileSystem, new ExtHostFileSystem(rpcProtocol, extHostLanguageFeatures)); + const extHostFileSystem = rpcProtocol.set(ExtHostContext.ExtHostFileSystem, new ExtHostFileSystem(rpcProtocol, extHostLanguageFeatures, extHostFileSystemInfo)); const extHostFileSystemEvent = rpcProtocol.set(ExtHostContext.ExtHostFileSystemEventService, new ExtHostFileSystemEventService(rpcProtocol, extHostLogService, extHostDocumentsAndEditors)); const extHostQuickOpen = rpcProtocol.set(ExtHostContext.ExtHostQuickOpen, new ExtHostQuickOpen(rpcProtocol, extHostWorkspace, extHostCommands)); const extHostSCM = rpcProtocol.set(ExtHostContext.ExtHostSCM, new ExtHostSCM(rpcProtocol, extHostCommands, extHostLogService)); @@ -153,10 +156,11 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // Check that no named customers are missing // {{SQL CARBON EDIT}} filter out the services we don't expose const filtered: ProxyIdentifier[] = [ExtHostContext.ExtHostDebugService, ExtHostContext.ExtHostTask]; - const expected: ProxyIdentifier[] = values(ExtHostContext).filter(v => !find(filtered, x => x === v)); + const expected: ProxyIdentifier[] = values(ExtHostContext).filter(v => !filtered.find(x => x === v)); rpcProtocol.assertRegistered(expected); // Other instances + const extHostBulkEdits = new ExtHostBulkEdits(rpcProtocol, extHostDocumentsAndEditors, extHostNotebook); const extHostClipboard = new ExtHostClipboard(rpcProtocol); const extHostMessageService = new ExtHostMessageService(rpcProtocol, extHostLogService); const extHostDialogs = new ExtHostDialogs(rpcProtocol); @@ -694,7 +698,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostWorkspace.saveAll(includeUntitled); }, applyEdit(edit: vscode.WorkspaceEdit): Thenable { - return extHostEditors.applyWorkspaceEdit(edit); + return extHostBulkEdits.applyWorkspaceEdit(edit); }, createFileSystemWatcher: (pattern, ignoreCreate, ignoreChange, ignoreDelete): vscode.FileSystemWatcher => { return extHostFileSystemEvent.createFileSystemWatcher(typeConverters.GlobPattern.from(pattern), ignoreCreate, ignoreChange, ignoreDelete); @@ -991,6 +995,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension); return extHostNotebook.onDidChangeActiveNotebookEditor(listener, thisArgs, disposables); }, + onDidChangeNotebookDocumentMetadata(listener, thisArgs?, disposables?) { + checkProposedApiEnabled(extension); + return extHostNotebook.onDidChangeNotebookDocumentMetadata(listener, thisArgs, disposables); + }, onDidChangeNotebookCells(listener, thisArgs?, disposables?) { checkProposedApiEnabled(extension); return extHostNotebook.onDidChangeNotebookCells(listener, thisArgs, disposables); diff --git a/src/vs/workbench/api/common/extHost.common.services.ts b/src/vs/workbench/api/common/extHost.common.services.ts index c1dcba3c5c..a7d79d2d30 100644 --- a/src/vs/workbench/api/common/extHost.common.services.ts +++ b/src/vs/workbench/api/common/extHost.common.services.ts @@ -19,7 +19,8 @@ import { IExtHostStorage, ExtHostStorage } from 'vs/workbench/api/common/extHost import { IExtHostTunnelService, ExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService'; import { IExtHostApiDeprecationService, ExtHostApiDeprecationService, } from 'vs/workbench/api/common/extHostApiDeprecationService'; import { IExtHostWindow, ExtHostWindow } from 'vs/workbench/api/common/extHostWindow'; -import { ExtHostConsumerFileSystem, IExtHostConsumerFileSystem } from 'vs/workbench/api/common/extHostFileSystemConsumer'; +import { IExtHostConsumerFileSystem, ExtHostConsumerFileSystem } from 'vs/workbench/api/common/extHostFileSystemConsumer'; +import { IExtHostFileSystemInfo, ExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; registerSingleton(IExtensionStoragePaths, ExtensionStoragePaths); registerSingleton(IExtHostApiDeprecationService, ExtHostApiDeprecationService); @@ -29,6 +30,7 @@ registerSingleton(IExtHostConsumerFileSystem, ExtHostConsumerFileSystem); // registerSingleton(IExtHostDebugService, WorkerExtHostDebugService); registerSingleton(IExtHostDecorations, ExtHostDecorations); registerSingleton(IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors); +registerSingleton(IExtHostFileSystemInfo, ExtHostFileSystemInfo); registerSingleton(IExtHostOutputService, ExtHostOutputService); registerSingleton(IExtHostSearch, ExtHostSearch); registerSingleton(IExtHostStorage, ExtHostStorage); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 5ca5c95b07..062986c6ec 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -51,7 +51,7 @@ import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; import { TunnelOptions } from 'vs/platform/remote/common/tunnel'; import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline'; import { revive } from 'vs/base/common/marshalling'; -import { IProcessedOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent, NotebookDataDto, IMainCellDto, INotebookDocumentFilter, INotebookKernelInfoDto2, TransientMetadata, INotebookCellStatusBarEntry, ICellRange } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { IProcessedOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEventDto, NotebookDataDto, IMainCellDto, INotebookDocumentFilter, INotebookKernelInfoDto2, TransientMetadata, INotebookCellStatusBarEntry, ICellRange } from 'vs/workbench/contrib/notebook/common/notebookCommon'; 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'; @@ -267,6 +267,10 @@ export interface ITextDocumentShowOptions { selection?: IRange; } +export interface MainThreadBulkEditsShape extends IDisposable { + $tryApplyWorkspaceEdit(workspaceEditDto: IWorkspaceEditDto): Promise; +} + export interface MainThreadTextEditorsShape extends IDisposable { $tryShowTextDocument(resource: UriComponents, options: ITextDocumentShowOptions): Promise; $registerTextEditorDecorationType(key: string, options: editorCommon.IDecorationRenderOptions): void; @@ -279,7 +283,6 @@ export interface MainThreadTextEditorsShape extends IDisposable { $tryRevealRange(id: string, range: IRange, revealType: TextEditorRevealType): Promise; $trySetSelections(id: string, selections: ISelection[]): Promise; $tryApplyEdits(id: string, modelVersionId: number, edits: ISingleEditOperation[], opts: IApplyEditsOptions): Promise; - $tryApplyWorkspaceEdit(workspaceEditDto: IWorkspaceEditDto): Promise; $tryInsertSnippet(id: string, template: string, selections: readonly IRange[], opts: IUndoStopOptions): Promise; $getDiffInformation(id: string): Promise; } @@ -644,6 +647,9 @@ export interface MainThreadWebviewViewsShape extends IDisposable { $unregisterWebviewViewProvider(viewType: string): void; $setWebviewViewTitle(handle: WebviewHandle, value: string | undefined): void; + $setWebviewViewDescription(handle: WebviewHandle, value: string | undefined): void; + + $show(handle: WebviewHandle, preserveFocus: boolean): void; } export interface WebviewPanelViewStateData { @@ -684,7 +690,7 @@ export interface ExtHostCustomEditorsShape { } export interface ExtHostWebviewViewsShape { - $resolveWebviewView(webviewHandle: WebviewHandle, viewType: string, state: any, cancellation: CancellationToken): Promise; + $resolveWebviewView(webviewHandle: WebviewHandle, viewType: string, title: string | undefined, state: any, cancellation: CancellationToken): Promise; $onDidChangeWebviewViewVisibility(webviewHandle: WebviewHandle, visible: boolean): void; @@ -734,20 +740,17 @@ export type INotebookCellStatusBarEntryDto = Dto; export interface MainThreadNotebookShape extends IDisposable { $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, supportBackup: boolean, options: { transientOutputs: boolean; transientMetadata: TransientMetadata }): Promise; - $onNotebookChange(viewType: string, resource: UriComponents): Promise; $unregisterNotebookProvider(viewType: string): Promise; $registerNotebookKernelProvider(extension: NotebookExtensionDescription, handle: number, documentFilter: INotebookDocumentFilter): Promise; $unregisterNotebookKernelProvider(handle: number): Promise; $onNotebookKernelChange(handle: number, uri: UriComponents | undefined): void; $tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[]): Promise; $updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise; - $updateNotebookMetadata(viewType: string, resource: UriComponents, metadata: NotebookDocumentMetadata): Promise; - $updateNotebookCellMetadata(viewType: string, resource: UriComponents, handle: number, metadata: NotebookCellMetadata | undefined): Promise; $spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[]): Promise; $postMessage(editorId: string, forRendererId: string | undefined, value: any): Promise; $setStatusBarEntry(id: number, statusBarEntry: INotebookCellStatusBarEntryDto): Promise; $tryRevealRange(id: string, range: ICellRange, revealType: NotebookEditorRevealType): Promise; - $onDidEdit(resource: UriComponents, viewType: string, editId: number, label: string | undefined): void; + $onUndoableContentChange(resource: UriComponents, viewType: string, editId: number, label: string | undefined): void; $onContentChange(resource: UriComponents, viewType: string): void; } @@ -1037,6 +1040,10 @@ export interface ExtHostWorkspaceShape { $handleTextSearchResult(result: search.IRawFileMatch2, requestId: number): void; } +export interface ExtHostFileSystemInfoShape { + $acceptProviderInfos(scheme: string, capabilities: number | null): void; +} + export interface ExtHostFileSystemShape { $stat(handle: number, resource: UriComponents): Promise; $readdir(handle: number, resource: UriComponents): Promise<[string, files.FileType][]>; @@ -1277,7 +1284,7 @@ export interface IWorkspaceCellEditDto { _type: WorkspaceEditType.Cell; resource: UriComponents; edit: ICellEditOperation; - modelVersionId?: number; + notebookVersionId?: number; metadata?: IWorkspaceEditEntryMetadataDto; } @@ -1643,16 +1650,15 @@ export interface INotebookVisibleRangesEvent { export interface INotebookEditorPropertiesChangeData { visibleRanges: INotebookVisibleRangesEvent | null; + selections: INotebookSelectionChangeEvent | null; } export interface INotebookDocumentPropertiesChangeData { metadata: NotebookDocumentMetadata | null; - selections: INotebookSelectionChangeEvent | null; } export interface INotebookModelAddedData { uri: UriComponents; - handle: number; versionId: number; cells: IMainCellDto[], viewType: string; @@ -1677,7 +1683,7 @@ export interface INotebookDocumentsAndEditorsDelta { } export interface ExtHostNotebookShape { - $resolveNotebookData(viewType: string, uri: UriComponents, backupId?: string): Promise; + $resolveNotebookData(viewType: string, uri: UriComponents, backupId?: string): Promise; $resolveNotebookEditor(viewType: string, uri: UriComponents, editorId: string): Promise; $provideNotebookKernels(handle: number, uri: UriComponents, token: CancellationToken): Promise; $resolveNotebookKernel(handle: number, editorId: string, uri: UriComponents, kernelId: string, token: CancellationToken): Promise; @@ -1690,7 +1696,7 @@ export interface ExtHostNotebookShape { $acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void; $acceptNotebookActiveKernelChange(event: { uri: UriComponents, providerHandle: number | undefined, kernelId: string | undefined }): void; $onDidReceiveMessage(editorId: string, rendererId: string | undefined, message: unknown): void; - $acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent, isDirty: boolean): void; + $acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEventDto, isDirty: boolean): void; $acceptModelSaved(uriComponents: UriComponents): void; $acceptEditorPropertiesChanged(id: string, data: INotebookEditorPropertiesChangeData): void; $acceptDocumentPropertiesChanged(uriComponents: UriComponents, data: INotebookDocumentPropertiesChangeData): void; @@ -1727,6 +1733,7 @@ export interface ExtHostTimelineShape { export const MainContext = { MainThreadAuthentication: createMainId('MainThreadAuthentication'), + MainThreadBulkEdits: createMainId('MainThreadBulkEdits'), MainThreadClipboard: createMainId('MainThreadClipboard'), MainThreadCommands: createMainId('MainThreadCommands'), MainThreadComments: createMainId('MainThreadComments'), @@ -1787,6 +1794,7 @@ export const ExtHostContext = { ExtHostEditors: createExtId('ExtHostEditors'), ExtHostTreeViews: createExtId('ExtHostTreeViews'), ExtHostFileSystem: createExtId('ExtHostFileSystem'), + ExtHostFileSystemInfo: createExtId('ExtHostFileSystemInfo'), ExtHostFileSystemEventService: createExtId('ExtHostFileSystemEventService'), ExtHostLanguageFeatures: createExtId('ExtHostLanguageFeatures'), ExtHostQuickOpen: createExtId('ExtHostQuickOpen'), diff --git a/src/vs/workbench/api/common/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index 50074d4c16..297f3082a2 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -228,10 +228,15 @@ const newCommands: ApiCommand[] = [ } return typeConverters.WorkspaceEdit.to(value); }) + ), + // --- links + new ApiCommand( + 'vscode.executeLinkProvider', '_executeLinkProvider', 'Execute document link provider.', + [ApiCommandArgument.Uri, new ApiCommandArgument('linkResolveCount', '(optional) Number of links that should be resolved, only when links are unresolved.', v => typeof v === 'number' || typeof v === 'undefined', v => v)], + new ApiCommandResult('A promise that resolves to an array of DocumentLink-instances.', value => value.map(typeConverters.DocumentLink.to)) ) ]; - //#endregion @@ -289,13 +294,6 @@ export class ExtHostApiCommands { returns: 'A promise that resolves to an array of CodeLens-instances.' }); - this._register('vscode.executeLinkProvider', this._executeDocumentLinkProvider, { - description: 'Execute document link provider.', - args: [ - { name: 'uri', description: 'Uri of a text document', constraint: URI } - ], - returns: 'A promise that resolves to an array of DocumentLink-instances.' - }); this._register('vscode.executeDocumentColorProvider', this._executeDocumentColorProvider, { description: 'Execute document color provider.', args: [ @@ -478,12 +476,6 @@ export class ExtHostApiCommands { typeConverters.Range.to(item.range), item.command ? this._commands.converter.fromInternal(item.command) : undefined); })); - - } - - private _executeDocumentLinkProvider(resource: URI): Promise { - return this._commands.executeCommand('_executeLinkProvider', resource) - .then(tryMapWith(typeConverters.DocumentLink.to)); } } diff --git a/src/vs/workbench/api/common/extHostBulkEdits.ts b/src/vs/workbench/api/common/extHostBulkEdits.ts new file mode 100644 index 0000000000..8901d541ad --- /dev/null +++ b/src/vs/workbench/api/common/extHostBulkEdits.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { MainContext, MainThreadBulkEditsShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { WorkspaceEdit } from 'vs/workbench/api/common/extHostTypeConverters'; +import type * as vscode from 'vscode'; + +export class ExtHostBulkEdits { + + private readonly _proxy: MainThreadBulkEditsShape; + + constructor( + @IExtHostRpcService extHostRpc: IExtHostRpcService, + private readonly _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors, + private readonly _extHostNotebooks: ExtHostNotebookController, + ) { + this._proxy = extHostRpc.getProxy(MainContext.MainThreadBulkEdits); + } + + applyWorkspaceEdit(edit: vscode.WorkspaceEdit): Promise { + const dto = WorkspaceEdit.from(edit, this._extHostDocumentsAndEditors, this._extHostNotebooks); + return this._proxy.$tryApplyWorkspaceEdit(dto); + } +} diff --git a/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts b/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts index 1d92976fc3..2433a3b575 100644 --- a/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts +++ b/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts @@ -6,7 +6,7 @@ import { Event } from 'vs/base/common/event'; import { URI, UriComponents } from 'vs/base/common/uri'; import { illegalState } from 'vs/base/common/errors'; -import { ExtHostDocumentSaveParticipantShape, MainThreadTextEditorsShape, IWorkspaceEditDto, WorkspaceEditType } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostDocumentSaveParticipantShape, IWorkspaceEditDto, WorkspaceEditType, MainThreadBulkEditsShape } from 'vs/workbench/api/common/extHost.protocol'; import { TextEdit } from 'vs/workbench/api/common/extHostTypes'; import { Range, TextDocumentSaveReason, EndOfLine } from 'vs/workbench/api/common/extHostTypeConverters'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; @@ -26,7 +26,7 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic constructor( private readonly _logService: ILogService, private readonly _documents: ExtHostDocuments, - private readonly _mainThreadEditors: MainThreadTextEditorsShape, + private readonly _mainThreadBulkEdits: MainThreadBulkEditsShape, private readonly _thresholds: { timeout: number; errors: number; } = { timeout: 1500, errors: 3 } ) { // @@ -165,7 +165,7 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic } if (version === document.version) { - return this._mainThreadEditors.$tryApplyWorkspaceEdit(dto); + return this._mainThreadBulkEdits.$tryApplyWorkspaceEdit(dto); } return Promise.reject(new Error('concurrent_edits')); diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index 55e35709f0..1f2cd31cbc 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -747,7 +747,7 @@ export abstract class AbstractExtHostExtensionService extends Disposable impleme protected abstract _beforeAlmostReadyToRunExtensions(): Promise; protected abstract _getEntryPoint(extensionDescription: IExtensionDescription): string | undefined; protected abstract _loadCommonJSModule(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise; - public abstract async $setRemoteEnvironment(env: { [key: string]: string | null }): Promise; + public abstract $setRemoteEnvironment(env: { [key: string]: string | null }): Promise; } diff --git a/src/vs/workbench/api/common/extHostFileSystem.ts b/src/vs/workbench/api/common/extHostFileSystem.ts index 4ff2f6b4e6..1a30c1272d 100644 --- a/src/vs/workbench/api/common/extHostFileSystem.ts +++ b/src/vs/workbench/api/common/extHostFileSystem.ts @@ -7,15 +7,15 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { MainContext, IMainContext, ExtHostFileSystemShape, MainThreadFileSystemShape, IFileChangeDto } from './extHost.protocol'; import type * as vscode from 'vscode'; import * as files from 'vs/platform/files/common/files'; -import { IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { FileChangeType } from 'vs/workbench/api/common/extHostTypes'; import * as typeConverter from 'vs/workbench/api/common/extHostTypeConverters'; import { ExtHostLanguageFeatures } from 'vs/workbench/api/common/extHostLanguageFeatures'; -import { Schemas } from 'vs/base/common/network'; import { State, StateMachine, LinkComputer, Edge } from 'vs/editor/common/modes/linkComputer'; import { commonPrefixLength } from 'vs/base/common/strings'; import { CharCode } from 'vs/base/common/charCode'; import { VSBuffer } from 'vs/base/common/buffer'; +import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; class FsLinkProvider { @@ -113,21 +113,18 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { private readonly _proxy: MainThreadFileSystemShape; private readonly _linkProvider = new FsLinkProvider(); private readonly _fsProvider = new Map(); - private readonly _usedSchemes = new Set(); + private readonly _registeredSchemes = new Set(); private readonly _watches = new Map(); private _linkProviderRegistration?: IDisposable; private _handlePool: number = 0; - constructor(mainContext: IMainContext, private _extHostLanguageFeatures: ExtHostLanguageFeatures) { + constructor(mainContext: IMainContext, private _extHostLanguageFeatures: ExtHostLanguageFeatures, private _extHostFileSystemInfo: IExtHostFileSystemInfo) { this._proxy = mainContext.getProxy(MainContext.MainThreadFileSystem); - - // register used schemes - Object.keys(Schemas).forEach(scheme => this._usedSchemes.add(scheme)); } dispose(): void { - dispose(this._linkProviderRegistration); + this._linkProviderRegistration?.dispose(); } private _registerLinkProviderIfNotYetRegistered(): void { @@ -138,7 +135,7 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { registerFileSystemProvider(scheme: string, provider: vscode.FileSystemProvider, options: { isCaseSensitive?: boolean, isReadonly?: boolean } = {}) { - if (this._usedSchemes.has(scheme)) { + if (this._registeredSchemes.has(scheme) || !this._extHostFileSystemInfo.isFreeScheme(scheme)) { throw new Error(`a provider for the scheme '${scheme}' is already registered`); } @@ -147,7 +144,7 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { const handle = this._handlePool++; this._linkProvider.add(scheme); - this._usedSchemes.add(scheme); + this._registeredSchemes.add(scheme); this._fsProvider.set(handle, provider); let capabilities = files.FileSystemProviderCapabilities.FileReadWrite; @@ -198,7 +195,7 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { return toDisposable(() => { subscription.dispose(); this._linkProvider.delete(scheme); - this._usedSchemes.delete(scheme); + this._registeredSchemes.delete(scheme); this._fsProvider.delete(handle); this._proxy.$unregisterProvider(handle); }); diff --git a/src/vs/workbench/api/common/extHostFileSystemConsumer.ts b/src/vs/workbench/api/common/extHostFileSystemConsumer.ts index 5b9fb91234..06d27b1797 100644 --- a/src/vs/workbench/api/common/extHostFileSystemConsumer.ts +++ b/src/vs/workbench/api/common/extHostFileSystemConsumer.ts @@ -10,6 +10,7 @@ import { FileSystemError } from 'vs/workbench/api/common/extHostTypes'; import { VSBuffer } from 'vs/base/common/buffer'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; export class ExtHostConsumerFileSystem implements vscode.FileSystem { @@ -17,7 +18,10 @@ export class ExtHostConsumerFileSystem implements vscode.FileSystem { private readonly _proxy: MainThreadFileSystemShape; - constructor(@IExtHostRpcService extHostRpc: IExtHostRpcService) { + constructor( + @IExtHostRpcService extHostRpc: IExtHostRpcService, + @IExtHostFileSystemInfo private readonly _fileSystemInfo: IExtHostFileSystemInfo, + ) { this._proxy = extHostRpc.getProxy(MainContext.MainThreadFileSystem); } @@ -45,6 +49,14 @@ export class ExtHostConsumerFileSystem implements vscode.FileSystem { copy(source: vscode.Uri, destination: vscode.Uri, options?: { overwrite?: boolean; }): Promise { return this._proxy.$copy(source, destination, { ...{ overwrite: false }, ...options }).catch(ExtHostConsumerFileSystem._handleError); } + isWritableFileSystem(scheme: string): boolean | undefined { + const capabilities = this._fileSystemInfo.getCapabilities(scheme); + if (typeof capabilities === 'number') { + return !(capabilities & files.FileSystemProviderCapabilities.Readonly); + } + return undefined; + } + private static _handleError(err: any): never { // generic error if (!(err instanceof Error)) { diff --git a/src/vs/workbench/api/common/extHostFileSystemEventService.ts b/src/vs/workbench/api/common/extHostFileSystemEventService.ts index 189d6afb80..393cbd1b6f 100644 --- a/src/vs/workbench/api/common/extHostFileSystemEventService.ts +++ b/src/vs/workbench/api/common/extHostFileSystemEventService.ts @@ -8,7 +8,7 @@ import { IRelativePattern, parse } from 'vs/base/common/glob'; import { URI } from 'vs/base/common/uri'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import type * as vscode from 'vscode'; -import { ExtHostFileSystemEventServiceShape, FileSystemEvents, IMainContext, MainContext, MainThreadTextEditorsShape, SourceTargetPair, IWorkspaceEditDto } from './extHost.protocol'; +import { ExtHostFileSystemEventServiceShape, FileSystemEvents, IMainContext, MainContext, SourceTargetPair, IWorkspaceEditDto, MainThreadBulkEditsShape } from './extHost.protocol'; import * as typeConverter from './extHostTypeConverters'; import { Disposable, WorkspaceEdit } from './extHostTypes'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; @@ -123,7 +123,7 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ mainContext: IMainContext, private readonly _logService: ILogService, private readonly _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors, - private readonly _mainThreadTextEditors: MainThreadTextEditorsShape = mainContext.getProxy(MainContext.MainThreadTextEditors) + private readonly _mainThreadBulkEdits: MainThreadBulkEditsShape = mainContext.getProxy(MainContext.MainThreadBulkEdits) ) { // } @@ -222,7 +222,7 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ let { edits } = typeConverter.WorkspaceEdit.from(edit, this._extHostDocumentsAndEditors); dto.edits = dto.edits.concat(edits); } - return this._mainThreadTextEditors.$tryApplyWorkspaceEdit(dto); + return this._mainThreadBulkEdits.$tryApplyWorkspaceEdit(dto); } } } diff --git a/src/vs/workbench/api/common/extHostFileSystemInfo.ts b/src/vs/workbench/api/common/extHostFileSystemInfo.ts new file mode 100644 index 0000000000..7fc2df12f1 --- /dev/null +++ b/src/vs/workbench/api/common/extHostFileSystemInfo.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 { Schemas } from 'vs/base/common/network'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { ExtHostFileSystemInfoShape } from 'vs/workbench/api/common/extHost.protocol'; + +export class ExtHostFileSystemInfo implements ExtHostFileSystemInfoShape { + + declare readonly _serviceBrand: undefined; + + private readonly _systemSchemes = new Set(Object.keys(Schemas)); + private readonly _providerInfo = new Map(); + + $acceptProviderInfos(scheme: string, capabilities: number | null): void { + if (capabilities === null) { + this._providerInfo.delete(scheme); + } else { + this._providerInfo.set(scheme, capabilities); + } + } + + isFreeScheme(scheme: string): boolean { + return !this._providerInfo.has(scheme) && !this._systemSchemes.has(scheme); + } + + getCapabilities(scheme: string): number | undefined { + return this._providerInfo.get(scheme); + } +} + +export interface IExtHostFileSystemInfo extends ExtHostFileSystemInfo { } +export const IExtHostFileSystemInfo = createDecorator('IExtHostFileSystemInfo'); diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index afec4f351d..4bf13cd40a 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -25,7 +25,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IURITransformer } from 'vs/base/common/uriIpc'; -import { DisposableStore, dispose } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { VSBuffer } from 'vs/base/common/buffer'; import { encodeSemanticTokensDto } from 'vs/workbench/api/common/shared/semanticTokensDto'; import { IdGenerator } from 'vs/base/common/idGenerator'; @@ -177,7 +177,7 @@ class CodeLensAdapter { } releaseCodeLenses(cachedId: number): void { - dispose(this._disposables.get(cachedId)); + this._disposables.get(cachedId)?.dispose(); this._disposables.delete(cachedId); this._cache.delete(cachedId); } @@ -455,7 +455,7 @@ class CodeActionAdapter { } public releaseCodeActions(cachedId: number): void { - dispose(this._disposables.get(cachedId)); + this._disposables.get(cachedId)?.dispose(); this._disposables.delete(cachedId); this._cache.delete(cachedId); } @@ -938,7 +938,7 @@ class SuggestAdapter { } releaseCompletionItems(id: number): any { - dispose(this._disposables.get(id)); + this._disposables.get(id)?.dispose(); this._disposables.delete(id); this._cache.delete(id); } diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index 67661fbf85..7d3804108a 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -4,588 +4,24 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { readonly } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; -import { hash } from 'vs/base/common/hash'; -import { Disposable, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { Schemas } from 'vs/base/common/network'; -import { joinPath } from 'vs/base/common/resources'; -import { ISplice } from 'vs/base/common/sequence'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import * as UUID from 'vs/base/common/uuid'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { CellKind, ExtHostNotebookShape, ICommandDto, IMainContext, IModelAddedData, INotebookDocumentPropertiesChangeData, INotebookDocumentsAndEditorsDelta, INotebookEditorPropertiesChangeData, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostNotebookShape, ICommandDto, IMainContext, IModelAddedData, INotebookDocumentPropertiesChangeData, INotebookDocumentsAndEditorsDelta, INotebookEditorPropertiesChangeData, MainContext, MainThreadBulkEditsShape, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol'; import { ILogService } from 'vs/platform/log/common/log'; import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; -import { ExtHostDocumentsAndEditors, IExtHostModelAddedData } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview'; -import { addIdToOutput, CellEditType, CellOutputKind, CellStatusbarAlignment, CellUri, diff, ICellEditOperation, ICellReplaceEdit, IMainCellDto, INotebookCellStatusBarEntry, INotebookDisplayOrder, INotebookEditData, INotebookKernelInfoDto2, IProcessedOutput, NotebookCellMetadata, NotebookCellsChangedEvent, NotebookCellsChangeType, NotebookCellsSplice2, NotebookDataDto, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { addIdToOutput, CellStatusbarAlignment, CellUri, INotebookCellStatusBarEntry, INotebookDisplayOrder, INotebookKernelInfoDto2, NotebookCellMetadata, NotebookCellsChangedEventDto, NotebookCellsChangeType, NotebookDataDto, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import * as vscode from 'vscode'; -import { Cache } from './cache'; import { ResourceMap } from 'vs/base/common/map'; - -interface IObservable { - proxy: T; - onDidChange: Event; -} - -function getObservable(obj: T): IObservable { - const onDidChange = new Emitter(); - const proxy = new Proxy(obj, { - set(target: T, p: PropertyKey, value: any, _receiver: any): boolean { - target[p as keyof T] = value; - onDidChange.fire(); - return true; - } - }); - - return { - proxy, - onDidChange: onDidChange.event - }; -} - -interface INotebookEventEmitter { - emitModelChange(events: vscode.NotebookCellsChangeEvent): void; - emitCellOutputsChange(event: vscode.NotebookCellOutputsChangeEvent): void; - emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void; - emitCellMetadataChange(event: vscode.NotebookCellMetadataChangeEvent): void; -} - -export class ExtHostCell extends Disposable { - - public static asModelAddData(notebook: vscode.NotebookDocument, cell: IMainCellDto): IExtHostModelAddedData { - return { - EOL: cell.eol, - lines: cell.source, - modeId: cell.language, - uri: cell.uri, - isDirty: false, - versionId: 1, - notebook - }; - } - - private _onDidDispose = new Emitter(); - readonly onDidDispose: Event = this._onDidDispose.event; - - private _onDidChangeOutputs = new Emitter[]>(); - readonly onDidChangeOutputs: Event[]> = this._onDidChangeOutputs.event; - - private _outputs: any[]; - private _outputMapping = new WeakMap(); - - private _metadata: vscode.NotebookCellMetadata; - private _metadataChangeListener: IDisposable; - - readonly handle: number; - readonly uri: URI; - readonly cellKind: CellKind; - - private _cell: vscode.NotebookCell | undefined; - - constructor( - private readonly _proxy: MainThreadNotebookShape, - private readonly _notebook: ExtHostNotebookDocument, - private readonly _extHostDocument: ExtHostDocumentsAndEditors, - private readonly _cellData: IMainCellDto, - ) { - super(); - - this.handle = _cellData.handle; - this.uri = URI.revive(_cellData.uri); - this.cellKind = _cellData.cellKind; - - this._outputs = _cellData.outputs; - for (const output of this._outputs) { - this._outputMapping.set(output, output.outputId); - delete output.outputId; - } - - const observableMetadata = getObservable(_cellData.metadata ?? {}); - this._metadata = observableMetadata.proxy; - this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => { - this._updateMetadata(); - })); - } - - get cell(): vscode.NotebookCell { - if (!this._cell) { - const that = this; - const document = this._extHostDocument.getDocument(this.uri)!.document; - this._cell = Object.freeze({ - notebook: that._notebook.notebookDocument, - uri: that.uri, - cellKind: this._cellData.cellKind, - document, - language: document.languageId, - get outputs() { return that._outputs; }, - set outputs(value) { that._updateOutputs(value); }, - get metadata() { return that._metadata; }, - set metadata(value) { - that.setMetadata(value); - that._updateMetadata(); - }, - }); - } - return this._cell; - } - - dispose() { - super.dispose(); - this._onDidDispose.fire(); - } - - setOutputs(newOutputs: vscode.CellOutput[]): void { - this._outputs = newOutputs; - } - - private _updateOutputs(newOutputs: vscode.CellOutput[]) { - const rawDiffs = diff(this._outputs || [], newOutputs || [], (a) => { - return this._outputMapping.has(a); - }); - - const transformedDiffs: ISplice[] = rawDiffs.map(diff => { - for (let i = diff.start; i < diff.start + diff.deleteCount; i++) { - this._outputMapping.delete(this._outputs[i]); - } - - return { - deleteCount: diff.deleteCount, - start: diff.start, - toInsert: diff.toInsert.map((output): IProcessedOutput => { - if (output.outputKind === CellOutputKind.Rich) { - const uuid = UUID.generateUuid(); - this._outputMapping.set(output, uuid); - return { ...output, outputId: uuid }; - } - - this._outputMapping.set(output, undefined); - return output; - }) - }; - }); - - this._outputs = newOutputs; - this._onDidChangeOutputs.fire(transformedDiffs); - } - - setMetadata(newMetadata: vscode.NotebookCellMetadata): void { - // Don't apply metadata defaults here, 'undefined' means 'inherit from document metadata' - this._metadataChangeListener.dispose(); - const observableMetadata = getObservable(newMetadata); - this._metadata = observableMetadata.proxy; - this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => { - this._updateMetadata(); - })); - } - - private _updateMetadata(): Promise { - return this._proxy.$updateNotebookCellMetadata(this._notebook.notebookDocument.viewType, this._notebook.uri, this.handle, this._metadata); - } -} - -class RawContentChangeEvent { - - constructor(readonly start: number, readonly deletedCount: number, readonly deletedItems: ExtHostCell[], readonly items: ExtHostCell[]) { } - - static asApiEvent(event: RawContentChangeEvent): vscode.NotebookCellsChangeData { - return Object.freeze({ - start: event.start, - deletedCount: event.deletedCount, - deletedItems: event.deletedItems.map(data => data.cell), - items: event.items.map(data => data.cell) - }); - } -} - -export class ExtHostNotebookDocument extends Disposable { - - private static _handlePool: number = 0; - readonly handle = ExtHostNotebookDocument._handlePool++; - - private _cells: ExtHostCell[] = []; - - private _cellDisposableMapping = new Map(); - - private _notebook: vscode.NotebookDocument | undefined; - - private _metadata: Required = notebookDocumentMetadataDefaults; - private _metadataChangeListener: IDisposable; - private _displayOrder: string[] = []; - private _versionId = 0; - private _isDirty: boolean = false; - private _backupCounter = 1; - private _backup?: vscode.NotebookDocumentBackup; - private _disposed = false; - private _languages: string[] = []; - - private readonly _edits = new Cache('notebook documents'); - - constructor( - private readonly _proxy: MainThreadNotebookShape, - private readonly _documentsAndEditors: ExtHostDocumentsAndEditors, - private readonly _emitter: INotebookEventEmitter, - private readonly _viewType: string, - public readonly uri: URI, - public readonly renderingHandler: ExtHostNotebookOutputRenderingHandler, - private readonly _storagePath: URI | undefined - ) { - super(); - - const observableMetadata = getObservable(notebookDocumentMetadataDefaults); - this._metadata = observableMetadata.proxy; - this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => { - this._tryUpdateMetadata(); - })); - } - - dispose() { - this._disposed = true; - super.dispose(); - dispose(this._cellDisposableMapping.values()); - } - - private _updateMetadata(newMetadata: Required) { - this._metadataChangeListener.dispose(); - newMetadata = { - ...notebookDocumentMetadataDefaults, - ...newMetadata - }; - if (this._metadataChangeListener) { - this._metadataChangeListener.dispose(); - } - - const observableMetadata = getObservable(newMetadata); - this._metadata = observableMetadata.proxy; - this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => { - this._tryUpdateMetadata(); - })); - - this._tryUpdateMetadata(); - } - - private _tryUpdateMetadata() { - this._proxy.$updateNotebookMetadata(this._viewType, this.uri, this._metadata); - } - get notebookDocument(): vscode.NotebookDocument { - if (!this._notebook) { - const that = this; - this._notebook = Object.freeze({ - get uri() { return that.uri; }, - get version() { return that._versionId; }, - get fileName() { return that.uri.fsPath; }, - get viewType() { return that._viewType; }, - get isDirty() { return that._isDirty; }, - get isUntitled() { return that.uri.scheme === Schemas.untitled; }, - get cells(): ReadonlyArray { return that._cells.map(cell => cell.cell); }, - get languages() { return that._languages; }, - set languages(value: string[]) { that._trySetLanguages(value); }, - get displayOrder() { return that._displayOrder; }, - set displayOrder(value: string[]) { that._displayOrder = value; }, - get metadata() { return that._metadata; }, - set metadata(value: Required) { that._updateMetadata(value); }, - }); - } - return this._notebook; - } - - private _trySetLanguages(newLanguages: string[]) { - this._languages = newLanguages; - this._proxy.$updateNotebookLanguages(this._viewType, this.uri, this._languages); - } - - getNewBackupUri(): URI { - if (!this._storagePath) { - throw new Error('Backup requires a valid storage path'); - } - const fileName = hashPath(this.uri) + (this._backupCounter++); - return joinPath(this._storagePath, fileName); - } - - updateBackup(backup: vscode.NotebookDocumentBackup): void { - this._backup?.delete(); - this._backup = backup; - } - - disposeBackup(): void { - this._backup?.delete(); - this._backup = undefined; - } - - acceptModelChanged(event: NotebookCellsChangedEvent, isDirty: boolean): void { - this._versionId = event.versionId; - this._isDirty = isDirty; - if (event.kind === NotebookCellsChangeType.Initialize) { - this._spliceNotebookCells(event.changes, true); - } if (event.kind === NotebookCellsChangeType.ModelChange) { - this._spliceNotebookCells(event.changes, false); - } else if (event.kind === NotebookCellsChangeType.Move) { - this._moveCell(event.index, event.newIdx); - } else if (event.kind === NotebookCellsChangeType.Output) { - this._setCellOutputs(event.index, event.outputs); - } 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); - } else if (event.kind === NotebookCellsChangeType.ChangeMetadata) { - this._changeCellMetadata(event.index, event.metadata); - } - } - - private _spliceNotebookCells(splices: NotebookCellsSplice2[], initialization: boolean): void { - if (this._disposed) { - return; - } - - const contentChangeEvents: RawContentChangeEvent[] = []; - const addedCellDocuments: IExtHostModelAddedData[] = []; - const removedCellDocuments: URI[] = []; - - splices.reverse().forEach(splice => { - const cellDtos = splice[2]; - const newCells = cellDtos.map(cell => { - - const extCell = new ExtHostCell(this._proxy, this, this._documentsAndEditors, cell); - - if (!initialization) { - addedCellDocuments.push(ExtHostCell.asModelAddData(this.notebookDocument, cell)); - } - - if (!this._cellDisposableMapping.has(extCell.handle)) { - const store = new DisposableStore(); - store.add(extCell); - this._cellDisposableMapping.set(extCell.handle, store); - } - - const store = this._cellDisposableMapping.get(extCell.handle)!; - - store.add(extCell.onDidChangeOutputs((diffs) => { - this.eventuallyUpdateCellOutputs(extCell, diffs); - })); - - return extCell; - }); - - for (let j = splice[0]; j < splice[0] + splice[1]; j++) { - this._cellDisposableMapping.get(this._cells[j].handle)?.dispose(); - this._cellDisposableMapping.delete(this._cells[j].handle); - } - - const deletedItems = this._cells.splice(splice[0], splice[1], ...newCells); - for (let cell of deletedItems) { - removedCellDocuments.push(cell.uri); - } - - contentChangeEvents.push(new RawContentChangeEvent(splice[0], splice[1], deletedItems, newCells)); - }); - - this._documentsAndEditors.acceptDocumentsAndEditorsDelta({ - addedDocuments: addedCellDocuments, - removedDocuments: removedCellDocuments - }); - - if (!initialization) { - this._emitter.emitModelChange({ - document: this.notebookDocument, - changes: contentChangeEvents.map(RawContentChangeEvent.asApiEvent) - }); - } - } - - private _moveCell(index: number, newIdx: number): void { - const cells = this._cells.splice(index, 1); - this._cells.splice(newIdx, 0, ...cells); - const changes: vscode.NotebookCellsChangeData[] = [{ - start: index, - deletedCount: 1, - deletedItems: cells.map(data => data.cell), - items: [] - }, { - start: newIdx, - deletedCount: 0, - deletedItems: [], - items: cells.map(data => data.cell) - }]; - this._emitter.emitModelChange({ - document: this.notebookDocument, - changes - }); - } - - private _setCellOutputs(index: number, outputs: IProcessedOutput[]): void { - const cell = this._cells[index]; - cell.setOutputs(outputs); - this._emitter.emitCellOutputsChange({ document: this.notebookDocument, cells: [cell.cell] }); - } - - private _clearCellOutputs(index: number): void { - const cell = this._cells[index].cell; - cell.outputs = []; - const event: vscode.NotebookCellOutputsChangeEvent = { document: this.notebookDocument, cells: [cell] }; - this._emitter.emitCellOutputsChange(event); - } - - private _clearAllCellOutputs(): void { - const modifedCells: vscode.NotebookCell[] = []; - this._cells.forEach(({ cell }) => { - if (cell.outputs.length !== 0) { - cell.outputs = []; - modifedCells.push(cell); - } - }); - const event: vscode.NotebookCellOutputsChangeEvent = { document: this.notebookDocument, cells: modifedCells }; - this._emitter.emitCellOutputsChange(event); - } - - private _changeCellLanguage(index: number, language: string): void { - const cell = this._cells[index]; - const event: vscode.NotebookCellLanguageChangeEvent = { document: this.notebookDocument, cell: cell.cell, language }; - this._emitter.emitCellLanguageChange(event); - } - - private _changeCellMetadata(index: number, newMetadata: NotebookCellMetadata | undefined): void { - const cell = this._cells[index]; - cell.setMetadata(newMetadata || {}); - const event: vscode.NotebookCellMetadataChangeEvent = { document: this.notebookDocument, cell: cell.cell }; - this._emitter.emitCellMetadataChange(event); - } - - async eventuallyUpdateCellOutputs(cell: ExtHostCell, diffs: ISplice[]) { - const outputDtos: NotebookCellOutputsSplice[] = diffs.map(diff => { - const outputs = diff.toInsert; - return [diff.start, diff.deleteCount, outputs]; - }); - - if (!outputDtos.length) { - return; - } - - await this._proxy.$spliceNotebookCellOutputs(this._viewType, this.uri, cell.handle, outputDtos); - this._emitter.emitCellOutputsChange({ - document: this.notebookDocument, - cells: [cell.cell] - }); - } - - getCell(cellHandle: number): ExtHostCell | undefined { - return this._cells.find(cell => cell.handle === cellHandle); - } - - - addEdit(item: vscode.NotebookDocumentEditEvent): number { - return this._edits.add([item]); - } - - async undo(editId: number, isDirty: boolean): Promise { - await this.getEdit(editId).undo(); - // if (!isDirty) { - // this.disposeBackup(); - // } - } - - async redo(editId: number, isDirty: boolean): Promise { - await this.getEdit(editId).redo(); - // if (!isDirty) { - // this.disposeBackup(); - // } - } - - private getEdit(editId: number): vscode.NotebookDocumentEditEvent { - const edit = this._edits.get(editId, 0); - if (!edit) { - throw new Error('No edit found'); - } - - return edit; - } - - disposeEdits(editIds: number[]): void { - for (const id of editIds) { - this._edits.delete(id); - } - } -} - -export class NotebookEditorCellEditBuilder implements vscode.NotebookEditorCellEdit { - - private readonly _documentVersionId: number; - private readonly _collectedEdits: ICellEditOperation[] = []; - private _finalized: boolean = false; - - constructor(documentVersionId: number) { - this._documentVersionId = documentVersionId; - } - - finalize(): INotebookEditData { - this._finalized = true; - return { - documentVersionId: this._documentVersionId, - edits: this._collectedEdits, - }; - } - - private _throwIfFinalized() { - if (this._finalized) { - throw new Error('Edit is only valid while callback runs'); - } - } - - replaceMetadata(index: number, metadata: vscode.NotebookCellMetadata): void { - this._throwIfFinalized(); - this._collectedEdits.push({ - editType: CellEditType.Metadata, - index, - metadata - }); - } - - replaceOutput(index: number, outputs: vscode.CellOutput[]): void { - this._throwIfFinalized(); - this._collectedEdits.push({ - editType: CellEditType.Output, - index, - outputs: outputs.map(output => addIdToOutput(output)) - }); - } - - replaceCells(from: number, to: number, cells: vscode.NotebookCellData[]): void { - this._throwIfFinalized(); - - this._collectedEdits.push({ - editType: CellEditType.Replace, - index: from, - count: to - from, - cells: cells.map(data => { - return { - ...data, - outputs: data.outputs.map(output => addIdToOutput(output)), - }; - }) - }); - } - - insert(index: number, content: string | string[], language: string, type: CellKind, outputs: vscode.CellOutput[], metadata: vscode.NotebookCellMetadata | undefined): void { - this._throwIfFinalized(); - this.replaceCells(index, index, [{ - language, - outputs, - metadata, - cellKind: type, - source: Array.isArray(content) ? content.join('\n') : content, - }]); - } - - delete(index: number): void { - this._throwIfFinalized(); - this.replaceCells(index, 1, []); - } -} +import { ExtHostCell, ExtHostNotebookDocument } from './extHostNotebookDocument'; +import { ExtHostNotebookEditor } from './extHostNotebookEditor'; class ExtHostWebviewCommWrapper extends Disposable { private readonly _onDidReceiveDocumentMessage = new Emitter(); @@ -632,152 +68,7 @@ class ExtHostWebviewCommWrapper extends Disposable { } } -export class ExtHostNotebookEditor extends Disposable implements vscode.NotebookEditor { - private _viewColumn: vscode.ViewColumn | undefined; - selection?: vscode.NotebookCell; - - private _visibleRanges: vscode.NotebookCellRange[] = []; - - get visibleRanges() { - return this._visibleRanges; - } - - set visibleRanges(_range: vscode.NotebookCellRange[]) { - throw readonly('visibleRanges'); - } - - _acceptVisibleRanges(value: vscode.NotebookCellRange[]): void { - this._visibleRanges = value; - } - - private _active: boolean = false; - get active(): boolean { - return this._active; - } - - set active(_state: boolean) { - throw readonly('active'); - } - - private _visible: boolean = false; - get visible(): boolean { - return this._visible; - } - - set visible(_state: boolean) { - throw readonly('visible'); - } - - _acceptVisibility(value: boolean) { - this._visible = value; - } - - _acceptActive(value: boolean) { - this._active = value; - } - - private _kernel?: vscode.NotebookKernel; - - get kernel() { - return this._kernel; - } - - set kernel(_kernel: vscode.NotebookKernel | undefined) { - throw readonly('kernel'); - } - - private _onDidDispose = new Emitter(); - readonly onDidDispose: Event = this._onDidDispose.event; - private _onDidReceiveMessage = new Emitter(); - onDidReceiveMessage: vscode.Event = this._onDidReceiveMessage.event; - - constructor( - private readonly viewType: string, - readonly id: string, - public uri: URI, - private _proxy: MainThreadNotebookShape, - private _webComm: vscode.NotebookCommunication, - public readonly notebookData: ExtHostNotebookDocument, - ) { - super(); - this._register(this._webComm.onDidReceiveMessage(e => { - this._onDidReceiveMessage.fire(e); - })); - } - - get document(): vscode.NotebookDocument { - return this.notebookData.notebookDocument; - } - - edit(callback: (editBuilder: NotebookEditorCellEditBuilder) => void): Thenable { - const edit = new NotebookEditorCellEditBuilder(this.document.version); - callback(edit); - return this._applyEdit(edit.finalize()); - } - - private _applyEdit(editData: INotebookEditData): Promise { - - // return when there is nothing to do - if (editData.edits.length === 0) { - return Promise.resolve(true); - } - - const compressedEdits: ICellEditOperation[] = []; - let compressedEditsIndex = -1; - - for (let i = 0; i < editData.edits.length; i++) { - if (compressedEditsIndex < 0) { - compressedEdits.push(editData.edits[i]); - compressedEditsIndex++; - continue; - } - - const prevIndex = compressedEditsIndex; - const prev = compressedEdits[prevIndex]; - - if (prev.editType === CellEditType.Replace && editData.edits[i].editType === CellEditType.Replace) { - if (prev.index === editData.edits[i].index) { - prev.cells.push(...(editData.edits[i] as ICellReplaceEdit).cells); - prev.count += (editData.edits[i] as ICellReplaceEdit).count; - continue; - } - } - - compressedEdits.push(editData.edits[i]); - compressedEditsIndex++; - } - - return this._proxy.$tryApplyEdits(this.viewType, this.uri, editData.documentVersionId, compressedEdits); - } - - revealRange(range: vscode.NotebookCellRange, revealType?: extHostTypes.NotebookEditorRevealType) { - this._proxy.$tryRevealRange(this.id, range, revealType || extHostTypes.NotebookEditorRevealType.Default); - } - - get viewColumn(): vscode.ViewColumn | undefined { - return this._viewColumn; - } - - set viewColumn(value) { - throw readonly('viewColumn'); - } - - updateActiveKernel(kernel?: vscode.NotebookKernel) { - this._kernel = kernel; - } - async postMessage(message: any): Promise { - return this._webComm.postMessage(message); - } - - asWebviewUri(localResource: vscode.Uri): vscode.Uri { - return this._webComm.asWebviewUri(localResource); - } - dispose() { - this._onDidDispose.fire(); - super.dispose(); - } -} export interface ExtHostNotebookOutputRenderingHandler { outputDisplayOrder: INotebookDisplayOrder | undefined; @@ -900,11 +191,11 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN private static _notebookKernelProviderHandlePool: number = 0; private readonly _proxy: MainThreadNotebookShape; + private readonly _mainThreadBulkEdits: MainThreadBulkEditsShape; private readonly _notebookContentProviders = new Map(); private readonly _notebookKernels = new Map(); private readonly _notebookKernelProviders = new Map(); private readonly _documents = new ResourceMap(); - private readonly _unInitializedDocuments = new ResourceMap(); private readonly _editors = new Map(); private readonly _webviewComm = new Map(); private readonly _commandsConverter: CommandsConverter; @@ -912,6 +203,8 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN readonly onDidChangeNotebookEditorSelection = this._onDidChangeNotebookEditorSelection.event; private readonly _onDidChangeNotebookEditorVisibleRanges = new Emitter(); readonly onDidChangeNotebookEditorVisibleRanges = this._onDidChangeNotebookEditorVisibleRanges.event; + private readonly _onDidChangeNotebookDocumentMetadata = new Emitter(); + readonly onDidChangeNotebookDocumentMetadata = this._onDidChangeNotebookDocumentMetadata.event; private readonly _onDidChangeNotebookCells = new Emitter(); readonly onDidChangeNotebookCells = this._onDidChangeNotebookCells.event; private readonly _onDidChangeCellOutputs = new Emitter(); @@ -956,22 +249,20 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN private readonly _extensionStoragePaths: IExtensionStoragePaths, ) { this._proxy = mainContext.getProxy(MainContext.MainThreadNotebook); + this._mainThreadBulkEdits = mainContext.getProxy(MainContext.MainThreadBulkEdits); this._commandsConverter = commands.converter; commands.registerArgumentProcessor({ // Serialized INotebookCellActionContext processArgument: (arg) => { if (arg && arg.$mid === 12) { - const documentHandle = arg.notebookEditor?.notebookHandle; + const notebookUri = arg.notebookEditor?.notebookUri; const cellHandle = arg.cell.handle; - for (const value of this._editors) { - if (value[1].editor.notebookData.handle === documentHandle) { - const cell = value[1].editor.notebookData.getCell(cellHandle); - if (cell) { - return cell.cell; - } - } + const data = this._documents.get(notebookUri); + const cell = data?.getCell(cellHandle); + if (cell) { + return cell.cell; } } return arg; @@ -1013,7 +304,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN if (isEditEvent(e)) { const editId = document.addEdit(e); - this._proxy.$onDidEdit(e.document.uri, viewType, editId, e.label); + this._proxy.$onUndoableContentChange(e.document.uri, viewType, editId, e.label); } else { this._proxy.$onContentChange(e.document.uri, viewType); } @@ -1079,49 +370,24 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN }); } - async $resolveNotebookData(viewType: string, uri: UriComponents, backupId?: string): Promise { + async $resolveNotebookData(viewType: string, uri: UriComponents, backupId?: string): Promise { const provider = this._notebookContentProviders.get(viewType); - const revivedUri = URI.revive(uri); if (!provider) { - return undefined; // {{SQL CARBON EDIT}} strict-null-checks + throw new Error(`NO provider for '${viewType}'`); } - const storageRoot = this._extensionStoragePaths.workspaceValue(provider.extension) ?? this._extensionStoragePaths.globalValue(provider.extension); - let document = this._documents.get(revivedUri); - - if (!document) { - const that = this; - document = this._unInitializedDocuments.get(revivedUri) ?? new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, { - emitModelChange(event: vscode.NotebookCellsChangeEvent): void { - that._onDidChangeNotebookCells.fire(event); - }, - emitCellOutputsChange(event: vscode.NotebookCellOutputsChangeEvent): void { - that._onDidChangeCellOutputs.fire(event); - }, - emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void { - that._onDidChangeCellLanguage.fire(event); - }, - emitCellMetadataChange(event: vscode.NotebookCellMetadataChangeEvent): void { - that._onDidChangeCellMetadata.fire(event); - }, - }, viewType, revivedUri, this, storageRoot); - this._unInitializedDocuments.set(revivedUri, document); - } - - const rawCells = await provider.provider.openNotebook(URI.revive(uri), { backupId }); - const dto = { + const data = await provider.provider.openNotebook(URI.revive(uri), { backupId }); + return { metadata: { ...notebookDocumentMetadataDefaults, - ...rawCells.metadata + ...data.metadata }, - languages: rawCells.languages, - cells: rawCells.cells.map(cell => ({ + languages: data.languages, + cells: data.cells.map(cell => ({ ...cell, outputs: cell.outputs.map(o => addIdToOutput(o)) })), }; - - return dto; } async $resolveNotebookEditor(viewType: string, uri: UriComponents, editorId: string): Promise { @@ -1254,7 +520,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN const kernel = event.kernelId ? adapter.getKernel(event.kernelId) : undefined; this._editors.forEach(editor => { if (editor.editor.notebookData === document) { - editor.editor.updateActiveKernel(kernel); + editor.editor._acceptKernel(kernel); } }); this._onDidChangeActiveNotebookKernel.fire({ document: document.notebookDocument, kernel }); @@ -1262,12 +528,12 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN } } - // TODO: remove document - editor one on one mapping + // TODO@rebornix: remove document - editor one on one mapping private _getEditorFromURI(uriComponents: UriComponents) { const uriStr = URI.revive(uriComponents).toString(); let editor: { editor: ExtHostNotebookEditor; } | undefined; this._editors.forEach(e => { - if (e.editor.uri.toString() === uriStr) { + if (e.editor.document.uri.toString() === uriStr) { editor = e; } }); @@ -1279,7 +545,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN this._webviewComm.get(editorId)?.onDidReceiveMessage(forRendererType, message); } - $acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent, isDirty: boolean): void { + $acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEventDto, isDirty: boolean): void { const document = this._documents.get(URI.revive(uriComponents)); if (document) { document.acceptModelChanged(event, isDirty); @@ -1315,15 +581,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN visibleRanges: editor.editor.visibleRanges }); } - } - - $acceptDocumentPropertiesChanged(uriComponents: UriComponents, data: INotebookDocumentPropertiesChangeData): void { - this.logService.debug('ExtHostNotebook#$acceptDocumentPropertiesChanged', uriComponents.path, data); - const editor = this._getEditorFromURI(uriComponents); - - if (!editor) { - return; - } if (data.selections) { if (data.selections.selections.length) { @@ -1338,13 +595,18 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN selection: editor.editor.selection }); } + } + $acceptDocumentPropertiesChanged(uriComponents: UriComponents, data: INotebookDocumentPropertiesChangeData): void { + this.logService.debug('ExtHostNotebook#$acceptDocumentPropertiesChanged', uriComponents.path, data); + const editor = this._getEditorFromURI(uriComponents); + + if (!editor) { + return; + } if (data.metadata) { - editor.editor.notebookData.notebookDocument.metadata = { - ...notebookDocumentMetadataDefaults, - ...data.metadata - }; + editor.editor.notebookData.acceptDocumentPropertiesChanged(data); } } @@ -1358,9 +620,8 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN } const editor = new ExtHostNotebookEditor( - document.notebookDocument.viewType, editorId, - revivedUri, + document.notebookDocument.viewType, this._proxy, webComm.contentProviderComm, document @@ -1395,7 +656,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN } for (const e of this._editors.values()) { - if (e.editor.uri.toString() === revivedUri.toString()) { + if (e.editor.document.uri.toString() === revivedUri.toString()) { e.editor.dispose(); this._editors.delete(e.editor.id); editorChanged = true; @@ -1409,64 +670,62 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN const addedCellDocuments: IModelAddedData[] = []; for (const modelData of delta.addedDocuments) { - const revivedUri = URI.revive(modelData.uri); + const uri = URI.revive(modelData.uri); const viewType = modelData.viewType; const entry = this._notebookContentProviders.get(viewType); const storageRoot = entry && (this._extensionStoragePaths.workspaceValue(entry.extension) ?? this._extensionStoragePaths.globalValue(entry.extension)); + if (this._documents.has(uri)) { + throw new Error(`adding EXISTING notebook ${uri}`); + } + const that = this; - if (!this._documents.has(revivedUri)) { - const that = this; + const document = new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, this._mainThreadBulkEdits, { + emitModelChange(event: vscode.NotebookCellsChangeEvent): void { + that._onDidChangeNotebookCells.fire(event); + }, + emitCellOutputsChange(event: vscode.NotebookCellOutputsChangeEvent): void { + that._onDidChangeCellOutputs.fire(event); + }, + emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void { + that._onDidChangeCellLanguage.fire(event); + }, + emitCellMetadataChange(event: vscode.NotebookCellMetadataChangeEvent): void { + that._onDidChangeCellMetadata.fire(event); + }, + emitDocumentMetadataChange(event: vscode.NotebookDocumentMetadataChangeEvent): void { + that._onDidChangeNotebookDocumentMetadata.fire(event); + } + }, viewType, { ...notebookDocumentMetadataDefaults, ...modelData.metadata }, uri, storageRoot); - const document = this._unInitializedDocuments.get(revivedUri) ?? new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, { - emitModelChange(event: vscode.NotebookCellsChangeEvent): void { - that._onDidChangeNotebookCells.fire(event); - }, - emitCellOutputsChange(event: vscode.NotebookCellOutputsChangeEvent): void { - that._onDidChangeCellOutputs.fire(event); - }, - emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void { - that._onDidChangeCellLanguage.fire(event); - }, - emitCellMetadataChange(event: vscode.NotebookCellMetadataChangeEvent): void { - that._onDidChangeCellMetadata.fire(event); + document.acceptModelChanged({ + versionId: modelData.versionId, + rawEvents: [ + { + kind: NotebookCellsChangeType.Initialize, + changes: [[ + 0, + 0, + modelData.cells + ]] } - }, viewType, revivedUri, this, storageRoot); + ] + }, false); - this._unInitializedDocuments.delete(revivedUri); - if (modelData.metadata) { - document.notebookDocument.metadata = { - ...notebookDocumentMetadataDefaults, - ...modelData.metadata - }; - } + // add cell document as vscode.TextDocument + addedCellDocuments.push(...modelData.cells.map(cell => ExtHostCell.asModelAddData(document.notebookDocument, cell))); - document.acceptModelChanged({ - kind: NotebookCellsChangeType.Initialize, - versionId: modelData.versionId, - changes: [[ - 0, - 0, - modelData.cells - ]] - }, false); + this._documents.get(uri)?.dispose(); + this._documents.set(uri, document); - // add cell document as vscode.TextDocument - addedCellDocuments.push(...modelData.cells.map(cell => ExtHostCell.asModelAddData(document.notebookDocument, cell))); - - this._documents.get(revivedUri)?.dispose(); - this._documents.set(revivedUri, document); - - // create editor if populated - if (modelData.attachedEditor) { - this._createExtHostEditor(document, modelData.attachedEditor.id, modelData.attachedEditor.selections, modelData.attachedEditor.visibleRanges); - editorChanged = true; - } + // create editor if populated + if (modelData.attachedEditor) { + this._createExtHostEditor(document, modelData.attachedEditor.id, modelData.attachedEditor.selections, modelData.attachedEditor.visibleRanges); + editorChanged = true; } this._documentsAndEditors.$acceptDocumentsAndEditorsDelta({ addedDocuments: addedCellDocuments }); - const document = this._documents.get(revivedUri)!; this._onDidOpenNotebookDocument.fire(document.notebookDocument); } } @@ -1566,11 +825,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN } } -function hashPath(resource: URI): string { - const str = resource.scheme === Schemas.file || resource.scheme === Schemas.untitled ? resource.fsPath : resource.toString(); - return hash(str) + ''; -} - function isEditEvent(e: vscode.NotebookDocumentEditEvent | vscode.NotebookDocumentContentChangeEvent): e is vscode.NotebookDocumentEditEvent { return typeof (e as vscode.NotebookDocumentEditEvent).undo === 'function' && typeof (e as vscode.NotebookDocumentEditEvent).redo === 'function'; diff --git a/src/vs/workbench/api/common/extHostNotebookDocument.ts b/src/vs/workbench/api/common/extHostNotebookDocument.ts new file mode 100644 index 0000000000..d5e9e23b07 --- /dev/null +++ b/src/vs/workbench/api/common/extHostNotebookDocument.ts @@ -0,0 +1,527 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from 'vs/base/common/event'; +import { hash } from 'vs/base/common/hash'; +import { Disposable, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; +import { joinPath } from 'vs/base/common/resources'; +import { ISplice } from 'vs/base/common/sequence'; +import { URI } from 'vs/base/common/uri'; +import * as UUID from 'vs/base/common/uuid'; +import { CellKind, INotebookDocumentPropertiesChangeData, IWorkspaceCellEditDto, MainThreadBulkEditsShape, MainThreadNotebookShape, NotebookCellOutputsSplice, WorkspaceEditType } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostDocumentsAndEditors, IExtHostModelAddedData } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { CellEditType, CellOutputKind, diff, IMainCellDto, IProcessedOutput, NotebookCellMetadata, NotebookCellsChangedEventDto, NotebookCellsChangeType, NotebookCellsSplice2, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import * as vscode from 'vscode'; +import { Cache } from './cache'; + + +interface IObservable { + proxy: T; + onDidChange: Event; +} + +function getObservable(obj: T): IObservable { + const onDidChange = new Emitter(); + const proxy = new Proxy(obj, { + set(target: T, p: PropertyKey, value: any, _receiver: any): boolean { + target[p as keyof T] = value; + onDidChange.fire(); + return true; + } + }); + + return { + proxy, + onDidChange: onDidChange.event + }; +} + +class RawContentChangeEvent { + + constructor(readonly start: number, readonly deletedCount: number, readonly deletedItems: ExtHostCell[], readonly items: ExtHostCell[]) { } + + static asApiEvent(event: RawContentChangeEvent): vscode.NotebookCellsChangeData { + return Object.freeze({ + start: event.start, + deletedCount: event.deletedCount, + deletedItems: event.deletedItems.map(data => data.cell), + items: event.items.map(data => data.cell) + }); + } +} + +export class ExtHostCell extends Disposable { + + static asModelAddData(notebook: vscode.NotebookDocument, cell: IMainCellDto): IExtHostModelAddedData { + return { + EOL: cell.eol, + lines: cell.source, + modeId: cell.language, + uri: cell.uri, + isDirty: false, + versionId: 1, + notebook + }; + } + + private _onDidDispose = new Emitter(); + readonly onDidDispose: Event = this._onDidDispose.event; + + private _onDidChangeOutputs = new Emitter[]>(); + readonly onDidChangeOutputs: Event[]> = this._onDidChangeOutputs.event; + + private _outputs: any[]; + private _outputMapping = new WeakMap(); + + private _metadata: vscode.NotebookCellMetadata; + private _metadataChangeListener: IDisposable; + + readonly handle: number; + readonly uri: URI; + readonly cellKind: CellKind; + + private _cell: vscode.NotebookCell | undefined; + + constructor( + private readonly _mainThreadBulkEdits: MainThreadBulkEditsShape, + private readonly _notebook: ExtHostNotebookDocument, + private readonly _extHostDocument: ExtHostDocumentsAndEditors, + private readonly _cellData: IMainCellDto, + ) { + super(); + + this.handle = _cellData.handle; + this.uri = URI.revive(_cellData.uri); + this.cellKind = _cellData.cellKind; + + this._outputs = _cellData.outputs; + for (const output of this._outputs) { + this._outputMapping.set(output, output.outputId); + delete output.outputId; + } + + const observableMetadata = getObservable(_cellData.metadata ?? {}); + this._metadata = observableMetadata.proxy; + this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => { + this._updateMetadata(); + })); + } + + get cell(): vscode.NotebookCell { + if (!this._cell) { + const that = this; + const document = this._extHostDocument.getDocument(this.uri)!.document; + this._cell = Object.freeze({ + get index() { return that._notebook.getCellIndex(that); }, + notebook: that._notebook.notebookDocument, + uri: that.uri, + cellKind: this._cellData.cellKind, + document, + get language() { return document.languageId; }, + get outputs() { return that._outputs; }, + set outputs(value) { that._updateOutputs(value); }, + get metadata() { return that._metadata; }, + set metadata(value) { + that.setMetadata(value); + that._updateMetadata(); + }, + }); + } + return this._cell; + } + + dispose() { + super.dispose(); + this._onDidDispose.fire(); + } + + setOutputs(newOutputs: vscode.CellOutput[]): void { + this._outputs = newOutputs; + } + + private _updateOutputs(newOutputs: vscode.CellOutput[]) { + const rawDiffs = diff(this._outputs || [], newOutputs || [], (a) => { + return this._outputMapping.has(a); + }); + + const transformedDiffs: ISplice[] = rawDiffs.map(diff => { + for (let i = diff.start; i < diff.start + diff.deleteCount; i++) { + this._outputMapping.delete(this._outputs[i]); + } + + return { + deleteCount: diff.deleteCount, + start: diff.start, + toInsert: diff.toInsert.map((output): IProcessedOutput => { + if (output.outputKind === CellOutputKind.Rich) { + const uuid = UUID.generateUuid(); + this._outputMapping.set(output, uuid); + return { ...output, outputId: uuid }; + } + + this._outputMapping.set(output, undefined); + return output; + }) + }; + }); + + this._outputs = newOutputs; + this._onDidChangeOutputs.fire(transformedDiffs); + } + + setMetadata(newMetadata: vscode.NotebookCellMetadata): void { + // Don't apply metadata defaults here, 'undefined' means 'inherit from document metadata' + this._metadataChangeListener.dispose(); + const observableMetadata = getObservable(newMetadata); + this._metadata = observableMetadata.proxy; + this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => { + this._updateMetadata(); + })); + } + + private _updateMetadata(): Promise { + const index = this._notebook.notebookDocument.cells.indexOf(this.cell); + const edit: IWorkspaceCellEditDto = { + _type: WorkspaceEditType.Cell, + metadata: undefined, + resource: this._notebook.uri, + notebookVersionId: this._notebook.notebookDocument.version, + edit: { editType: CellEditType.Metadata, index, metadata: this._metadata } + }; + + return this._mainThreadBulkEdits.$tryApplyWorkspaceEdit({ edits: [edit] }); + } +} + +export interface INotebookEventEmitter { + emitModelChange(events: vscode.NotebookCellsChangeEvent): void; + emitDocumentMetadataChange(event: vscode.NotebookDocumentMetadataChangeEvent): void; + emitCellOutputsChange(event: vscode.NotebookCellOutputsChangeEvent): void; + emitCellLanguageChange(event: vscode.NotebookCellLanguageChangeEvent): void; + emitCellMetadataChange(event: vscode.NotebookCellMetadataChangeEvent): void; +} + +function hashPath(resource: URI): string { + const str = resource.scheme === Schemas.file || resource.scheme === Schemas.untitled ? resource.fsPath : resource.toString(); + return hash(str) + ''; +} + +export class ExtHostNotebookDocument extends Disposable { + + private static _handlePool: number = 0; + readonly handle = ExtHostNotebookDocument._handlePool++; + + private _cells: ExtHostCell[] = []; + + private _cellDisposableMapping = new Map(); + + private _notebook: vscode.NotebookDocument | undefined; + private _metadata: Required; + private _metadataChangeListener: IDisposable; + private _versionId = 0; + private _isDirty: boolean = false; + private _backupCounter = 1; + private _backup?: vscode.NotebookDocumentBackup; + private _disposed = false; + private _languages: string[] = []; + + private readonly _edits = new Cache('notebook documents'); + + constructor( + private readonly _proxy: MainThreadNotebookShape, + private readonly _documentsAndEditors: ExtHostDocumentsAndEditors, + private readonly _mainThreadBulkEdits: MainThreadBulkEditsShape, + private readonly _emitter: INotebookEventEmitter, + private readonly _viewType: string, + metadata: Required, + public readonly uri: URI, + private readonly _storagePath: URI | undefined + ) { + super(); + + const observableMetadata = getObservable(metadata); + this._metadata = observableMetadata.proxy; + this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => { + this._tryUpdateMetadata(); + })); + } + + dispose() { + this._disposed = true; + super.dispose(); + dispose(this._cellDisposableMapping.values()); + } + + private _updateMetadata(newMetadata: Required) { + this._metadataChangeListener.dispose(); + newMetadata = { + ...notebookDocumentMetadataDefaults, + ...newMetadata + }; + if (this._metadataChangeListener) { + this._metadataChangeListener.dispose(); + } + + const observableMetadata = getObservable(newMetadata); + this._metadata = observableMetadata.proxy; + this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => { + this._tryUpdateMetadata(); + })); + + this._tryUpdateMetadata(); + } + + private _tryUpdateMetadata() { + const edit: IWorkspaceCellEditDto = { + _type: WorkspaceEditType.Cell, + metadata: undefined, + edit: { editType: CellEditType.DocumentMetadata, metadata: this._metadata }, + resource: this.uri, + notebookVersionId: this.notebookDocument.version, + }; + + return this._mainThreadBulkEdits.$tryApplyWorkspaceEdit({ edits: [edit] }); + } + + get notebookDocument(): vscode.NotebookDocument { + if (!this._notebook) { + const that = this; + this._notebook = Object.freeze({ + get uri() { return that.uri; }, + get version() { return that._versionId; }, + get fileName() { return that.uri.fsPath; }, + get viewType() { return that._viewType; }, + get isDirty() { return that._isDirty; }, + get isUntitled() { return that.uri.scheme === Schemas.untitled; }, + get cells(): ReadonlyArray { return that._cells.map(cell => cell.cell); }, + get languages() { return that._languages; }, + set languages(value: string[]) { that._trySetLanguages(value); }, + get metadata() { return that._metadata; }, + set metadata(value: Required) { that._updateMetadata(value); }, + }); + } + return this._notebook; + } + + private _trySetLanguages(newLanguages: string[]) { + this._languages = newLanguages; + this._proxy.$updateNotebookLanguages(this._viewType, this.uri, this._languages); + } + + getNewBackupUri(): URI { + if (!this._storagePath) { + throw new Error('Backup requires a valid storage path'); + } + const fileName = hashPath(this.uri) + (this._backupCounter++); + return joinPath(this._storagePath, fileName); + } + + updateBackup(backup: vscode.NotebookDocumentBackup): void { + this._backup?.delete(); + this._backup = backup; + } + + disposeBackup(): void { + this._backup?.delete(); + this._backup = undefined; + } + + acceptDocumentPropertiesChanged(data: INotebookDocumentPropertiesChangeData) { + const newMetadata = { + ...notebookDocumentMetadataDefaults, + ...data.metadata + }; + + if (this._metadataChangeListener) { + this._metadataChangeListener.dispose(); + } + + const observableMetadata = getObservable(newMetadata); + this._metadata = observableMetadata.proxy; + this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => { + this._tryUpdateMetadata(); + })); + + this._emitter.emitDocumentMetadataChange({ document: this.notebookDocument }); + } + + acceptModelChanged(event: NotebookCellsChangedEventDto, isDirty: boolean): void { + this._versionId = event.versionId; + this._isDirty = isDirty; + event.rawEvents.forEach(e => { + if (e.kind === NotebookCellsChangeType.Initialize) { + this._spliceNotebookCells(e.changes, true); + } if (e.kind === NotebookCellsChangeType.ModelChange) { + this._spliceNotebookCells(e.changes, false); + } else if (e.kind === NotebookCellsChangeType.Move) { + this._moveCell(e.index, e.newIdx); + } else if (e.kind === NotebookCellsChangeType.Output) { + this._setCellOutputs(e.index, e.outputs); + } else if (e.kind === NotebookCellsChangeType.ChangeLanguage) { + this._changeCellLanguage(e.index, e.language); + } else if (e.kind === NotebookCellsChangeType.ChangeCellMetadata) { + this._changeCellMetadata(e.index, e.metadata); + } + }); + } + + private _spliceNotebookCells(splices: NotebookCellsSplice2[], initialization: boolean): void { + if (this._disposed) { + return; + } + + const contentChangeEvents: RawContentChangeEvent[] = []; + const addedCellDocuments: IExtHostModelAddedData[] = []; + const removedCellDocuments: URI[] = []; + + splices.reverse().forEach(splice => { + const cellDtos = splice[2]; + const newCells = cellDtos.map(cell => { + + const extCell = new ExtHostCell(this._mainThreadBulkEdits, this, this._documentsAndEditors, cell); + + if (!initialization) { + addedCellDocuments.push(ExtHostCell.asModelAddData(this.notebookDocument, cell)); + } + + if (!this._cellDisposableMapping.has(extCell.handle)) { + const store = new DisposableStore(); + store.add(extCell); + this._cellDisposableMapping.set(extCell.handle, store); + } + + const store = this._cellDisposableMapping.get(extCell.handle)!; + + store.add(extCell.onDidChangeOutputs((diffs) => { + this.eventuallyUpdateCellOutputs(extCell, diffs); + })); + + return extCell; + }); + + for (let j = splice[0]; j < splice[0] + splice[1]; j++) { + this._cellDisposableMapping.get(this._cells[j].handle)?.dispose(); + this._cellDisposableMapping.delete(this._cells[j].handle); + } + + const deletedItems = this._cells.splice(splice[0], splice[1], ...newCells); + for (let cell of deletedItems) { + removedCellDocuments.push(cell.uri); + } + + contentChangeEvents.push(new RawContentChangeEvent(splice[0], splice[1], deletedItems, newCells)); + }); + + this._documentsAndEditors.acceptDocumentsAndEditorsDelta({ + addedDocuments: addedCellDocuments, + removedDocuments: removedCellDocuments + }); + + if (!initialization) { + this._emitter.emitModelChange({ + document: this.notebookDocument, + changes: contentChangeEvents.map(RawContentChangeEvent.asApiEvent) + }); + } + } + + private _moveCell(index: number, newIdx: number): void { + const cells = this._cells.splice(index, 1); + this._cells.splice(newIdx, 0, ...cells); + const changes: vscode.NotebookCellsChangeData[] = [{ + start: index, + deletedCount: 1, + deletedItems: cells.map(data => data.cell), + items: [] + }, { + start: newIdx, + deletedCount: 0, + deletedItems: [], + items: cells.map(data => data.cell) + }]; + this._emitter.emitModelChange({ + document: this.notebookDocument, + changes + }); + } + + private _setCellOutputs(index: number, outputs: IProcessedOutput[]): void { + const cell = this._cells[index]; + cell.setOutputs(outputs); + this._emitter.emitCellOutputsChange({ document: this.notebookDocument, cells: [cell.cell] }); + } + + private _changeCellLanguage(index: number, language: string): void { + const cell = this._cells[index]; + const event: vscode.NotebookCellLanguageChangeEvent = { document: this.notebookDocument, cell: cell.cell, language }; + this._emitter.emitCellLanguageChange(event); + } + + private _changeCellMetadata(index: number, newMetadata: NotebookCellMetadata | undefined): void { + const cell = this._cells[index]; + cell.setMetadata(newMetadata || {}); + const event: vscode.NotebookCellMetadataChangeEvent = { document: this.notebookDocument, cell: cell.cell }; + this._emitter.emitCellMetadataChange(event); + } + + async eventuallyUpdateCellOutputs(cell: ExtHostCell, diffs: ISplice[]) { + const outputDtos: NotebookCellOutputsSplice[] = diffs.map(diff => { + const outputs = diff.toInsert; + return [diff.start, diff.deleteCount, outputs]; + }); + + if (!outputDtos.length) { + return; + } + + await this._proxy.$spliceNotebookCellOutputs(this._viewType, this.uri, cell.handle, outputDtos); + this._emitter.emitCellOutputsChange({ + document: this.notebookDocument, + cells: [cell.cell] + }); + } + + getCell(cellHandle: number): ExtHostCell | undefined { + return this._cells.find(cell => cell.handle === cellHandle); + } + + getCellIndex(cell: ExtHostCell): number { + return this._cells.indexOf(cell); + } + + addEdit(item: vscode.NotebookDocumentEditEvent): number { + return this._edits.add([item]); + } + + async undo(editId: number, isDirty: boolean): Promise { + await this.getEdit(editId).undo(); + // if (!isDirty) { + // this.disposeBackup(); + // } + } + + async redo(editId: number, isDirty: boolean): Promise { + await this.getEdit(editId).redo(); + // if (!isDirty) { + // this.disposeBackup(); + // } + } + + private getEdit(editId: number): vscode.NotebookDocumentEditEvent { + const edit = this._edits.get(editId, 0); + if (!edit) { + throw new Error('No edit found'); + } + + return edit; + } + + disposeEdits(editIds: number[]): void { + for (const id of editIds) { + this._edits.delete(id); + } + } +} diff --git a/src/vs/workbench/api/common/extHostNotebookEditor.ts b/src/vs/workbench/api/common/extHostNotebookEditor.ts new file mode 100644 index 0000000000..af139a08e6 --- /dev/null +++ b/src/vs/workbench/api/common/extHostNotebookEditor.ts @@ -0,0 +1,230 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { readonly } from 'vs/base/common/errors'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol'; +import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; +import { addIdToOutput, CellEditType, ICellEditOperation, ICellReplaceEdit, INotebookEditData, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import * as vscode from 'vscode'; +import { ExtHostNotebookDocument } from './extHostNotebookDocument'; + +class NotebookEditorCellEditBuilder implements vscode.NotebookEditorEdit { + + private readonly _documentVersionId: number; + + private _finalized: boolean = false; + private _collectedEdits: ICellEditOperation[] = []; + + constructor(documentVersionId: number) { + this._documentVersionId = documentVersionId; + } + + finalize(): INotebookEditData { + this._finalized = true; + return { + documentVersionId: this._documentVersionId, + cellEdits: this._collectedEdits + }; + } + + private _throwIfFinalized() { + if (this._finalized) { + throw new Error('Edit is only valid while callback runs'); + } + } + + replaceMetadata(value: vscode.NotebookDocumentMetadata): void { + this._throwIfFinalized(); + this._collectedEdits.push({ + editType: CellEditType.DocumentMetadata, + metadata: { ...notebookDocumentMetadataDefaults, ...value } + }); + } + + replaceCellMetadata(index: number, metadata: vscode.NotebookCellMetadata): void { + this._throwIfFinalized(); + this._collectedEdits.push({ + editType: CellEditType.Metadata, + index, + metadata + }); + } + + replaceCellOutput(index: number, outputs: vscode.CellOutput[]): void { + this._throwIfFinalized(); + this._collectedEdits.push({ + editType: CellEditType.Output, + index, + outputs: outputs.map(output => addIdToOutput(output)) + }); + } + + replaceCells(from: number, to: number, cells: vscode.NotebookCellData[]): void { + this._throwIfFinalized(); + this._collectedEdits.push({ + editType: CellEditType.Replace, + index: from, + count: to - from, + cells: cells.map(data => { + return { + ...data, + outputs: data.outputs.map(output => addIdToOutput(output)), + }; + }) + }); + } +} + +export class ExtHostNotebookEditor extends Disposable implements vscode.NotebookEditor { + + //TODO@rebornix noop setter? + selection?: vscode.NotebookCell; + + private _visibleRanges: vscode.NotebookCellRange[] = []; + private _viewColumn?: vscode.ViewColumn; + private _active: boolean = false; + private _visible: boolean = false; + private _kernel?: vscode.NotebookKernel; + + private _onDidDispose = new Emitter(); + private _onDidReceiveMessage = new Emitter(); + + readonly onDidDispose: Event = this._onDidDispose.event; + readonly onDidReceiveMessage: vscode.Event = this._onDidReceiveMessage.event; + + constructor( + readonly id: string, + private readonly _viewType: string, + private readonly _proxy: MainThreadNotebookShape, + private readonly _webComm: vscode.NotebookCommunication, + readonly notebookData: ExtHostNotebookDocument, + ) { + super(); + this._register(this._webComm.onDidReceiveMessage(e => { + this._onDidReceiveMessage.fire(e); + })); + } + + get viewColumn(): vscode.ViewColumn | undefined { + return this._viewColumn; + } + + set viewColumn(_value) { + throw readonly('viewColumn'); + } + + get kernel() { + return this._kernel; + } + + set kernel(_kernel: vscode.NotebookKernel | undefined) { + throw readonly('kernel'); + } + + _acceptKernel(kernel?: vscode.NotebookKernel) { + this._kernel = kernel; + } + + get visible(): boolean { + return this._visible; + } + + set visible(_state: boolean) { + throw readonly('visible'); + } + + _acceptVisibility(value: boolean) { + this._visible = value; + } + + get visibleRanges() { + return this._visibleRanges; + } + + set visibleRanges(_range: vscode.NotebookCellRange[]) { + throw readonly('visibleRanges'); + } + + _acceptVisibleRanges(value: vscode.NotebookCellRange[]): void { + this._visibleRanges = value; + } + + get active(): boolean { + return this._active; + } + + set active(_state: boolean) { + throw readonly('active'); + } + + _acceptActive(value: boolean) { + this._active = value; + } + + get document(): vscode.NotebookDocument { + return this.notebookData.notebookDocument; + } + + edit(callback: (editBuilder: NotebookEditorCellEditBuilder) => void): Thenable { + const edit = new NotebookEditorCellEditBuilder(this.document.version); + callback(edit); + return this._applyEdit(edit.finalize()); + } + + private _applyEdit(editData: INotebookEditData): Promise { + + // return when there is nothing to do + if (editData.cellEdits.length === 0) { + return Promise.resolve(true); + } + + const compressedEdits: ICellEditOperation[] = []; + let compressedEditsIndex = -1; + + for (let i = 0; i < editData.cellEdits.length; i++) { + if (compressedEditsIndex < 0) { + compressedEdits.push(editData.cellEdits[i]); + compressedEditsIndex++; + continue; + } + + const prevIndex = compressedEditsIndex; + const prev = compressedEdits[prevIndex]; + + if (prev.editType === CellEditType.Replace && editData.cellEdits[i].editType === CellEditType.Replace) { + const edit = editData.cellEdits[i]; + if ((edit.editType !== CellEditType.DocumentMetadata && edit.editType !== CellEditType.Unknown) && prev.index === edit.index) { + prev.cells.push(...(editData.cellEdits[i] as ICellReplaceEdit).cells); + prev.count += (editData.cellEdits[i] as ICellReplaceEdit).count; + continue; + } + } + + compressedEdits.push(editData.cellEdits[i]); + compressedEditsIndex++; + } + + return this._proxy.$tryApplyEdits(this._viewType, this.document.uri, editData.documentVersionId, compressedEdits); + } + + revealRange(range: vscode.NotebookCellRange, revealType?: extHostTypes.NotebookEditorRevealType) { + this._proxy.$tryRevealRange(this.id, range, revealType || extHostTypes.NotebookEditorRevealType.Default); + } + + async postMessage(message: any): Promise { + return this._webComm.postMessage(message); + } + + asWebviewUri(localResource: vscode.Uri): vscode.Uri { + return this._webComm.asWebviewUri(localResource); + } + + dispose() { + this._onDidDispose.fire(); + super.dispose(); + } +} diff --git a/src/vs/workbench/api/common/extHostTask.ts b/src/vs/workbench/api/common/extHostTask.ts index 3251137a22..e129d9a0d9 100644 --- a/src/vs/workbench/api/common/extHostTask.ts +++ b/src/vs/workbench/api/common/extHostTask.ts @@ -456,7 +456,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape, IExtHostTask }); } - public abstract async executeTask(extension: IExtensionDescription, task: vscode.Task): Promise; + public abstract executeTask(extension: IExtensionDescription, task: vscode.Task): Promise; public get taskExecutions(): vscode.TaskExecution[] { const result: vscode.TaskExecution[] = []; @@ -561,7 +561,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape, IExtHostTask }); } - protected abstract async resolveTaskInternal(resolvedTaskDTO: tasks.TaskDTO): Promise; + protected abstract resolveTaskInternal(resolvedTaskDTO: tasks.TaskDTO): Promise; public async $resolveTask(handle: number, taskDTO: tasks.TaskDTO): Promise { const handler = this._handlers.get(handle); @@ -601,7 +601,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape, IExtHostTask return await this.resolveTaskInternal(resolvedTaskDTO); } - public abstract async $resolveVariables(uriComponents: UriComponents, toResolve: { process?: { name: string; cwd?: string; path?: string }, variables: string[] }): Promise<{ process?: string, variables: { [key: string]: string; } }>; + public abstract $resolveVariables(uriComponents: UriComponents, toResolve: { process?: { name: string; cwd?: string; path?: string }, variables: string[] }): Promise<{ process?: string, variables: { [key: string]: string; } }>; public abstract $getDefaultShellAndArgs(): Promise<{ shell: string, args: string[] | string | undefined }>; diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 3ecf46e591..f8d60a19e6 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -120,7 +120,7 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi ) { super(proxy, id); this._creationOptions = Object.freeze(this._creationOptions); - this._pidPromise = new Promise(c => this._pidPromiseComplete = c); + this._pidPromise = new Promise(c => this._pidPromiseComplete = c); } public async create( @@ -316,7 +316,7 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ protected _terminalProcesses: { [id: number]: ITerminalChildProcess } = {}; protected _terminalProcessDisposables: { [id: number]: IDisposable } = {}; protected _extensionTerminalAwaitingStart: { [id: number]: { initialDimensions: ITerminalDimensionsDto | undefined } | undefined } = {}; - protected _getTerminalPromises: { [id: number]: Promise } = {}; + protected _getTerminalPromises: { [id: number]: Promise } = {}; protected _environmentVariableCollections: Map = new Map(); private readonly _bufferer: TerminalDataBufferer; @@ -670,7 +670,7 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ return this._getTerminalPromises[id]; } - private _createGetTerminalPromise(id: number, retries: number = 5): Promise { + private _createGetTerminalPromise(id: number, retries: number = 5): Promise { return new Promise(c => { if (retries === 0) { c(undefined); diff --git a/src/vs/workbench/api/common/extHostTextEditors.ts b/src/vs/workbench/api/common/extHostTextEditors.ts index c0379f40bf..a6777aa213 100644 --- a/src/vs/workbench/api/common/extHostTextEditors.ts +++ b/src/vs/workbench/api/common/extHostTextEditors.ts @@ -7,7 +7,6 @@ import { Emitter, Event } from 'vs/base/common/event'; import * as arrays from 'vs/base/common/arrays'; import { ExtHostEditorsShape, IEditorPropertiesChangeData, IMainContext, ITextDocumentShowOptions, ITextEditorPositionData, MainContext, MainThreadTextEditorsShape } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; -import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; import { ExtHostTextEditor, TextEditorDecorationType } from 'vs/workbench/api/common/extHostTextEditor'; import * as TypeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import { TextEditorSelectionChangeKind } from 'vs/workbench/api/common/extHostTypes'; @@ -34,7 +33,6 @@ export class ExtHostEditors implements ExtHostEditorsShape { constructor( mainContext: IMainContext, private readonly _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors, - private readonly _extHostNotebooks: ExtHostNotebookController, ) { this._proxy = mainContext.getProxy(MainContext.MainThreadTextEditors); @@ -92,11 +90,6 @@ export class ExtHostEditors implements ExtHostEditorsShape { return new TextEditorDecorationType(this._proxy, options); } - applyWorkspaceEdit(edit: vscode.WorkspaceEdit): Promise { - const dto = TypeConverters.WorkspaceEdit.from(edit, this._extHostDocumentsAndEditors, this._extHostNotebooks); - return this._proxy.$tryApplyWorkspaceEdit(dto); - } - // --- called from main thread $acceptEditorPropertiesChanged(id: string, data: IEditorPropertiesChangeData): void { diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 046ffba3ce..e605e91dff 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -537,10 +537,11 @@ export namespace WorkspaceEdit { } else if (entry._type === types.FileEditType.Cell) { result.edits.push({ _type: extHostProtocol.WorkspaceEditType.Cell, + metadata: entry.metadata, resource: entry.uri, edit: entry.edit, - metadata: entry.metadata, - modelVersionId: notebooks?.lookupNotebookDocument(entry.uri)?.notebookDocument.version + notebookMetadata: entry.notebookMetadata, + notebookVersionId: notebooks?.lookupNotebookDocument(entry.uri)?.notebookDocument.version }); } } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 3049fd0883..ba82c58390 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -19,7 +19,7 @@ import { addIdToOutput, CellEditType, ICellEditOperation } from 'vs/workbench/co import type * as vscode from 'vscode'; function es5ClassCompat(target: Function): any { - ///@ts-expect-error + // @ts-ignore - {{SQL CARBON EDIT}} function _() { return Reflect.construct(target, arguments, this.constructor); } Object.defineProperty(_, 'name', Object.getOwnPropertyDescriptor(target, 'name')!); Object.setPrototypeOf(_, target); @@ -601,7 +601,8 @@ export interface IFileTextEdit { export interface IFileCellEdit { _type: FileEditType.Cell; uri: URI; - edit: ICellEditOperation; + edit?: ICellEditOperation; + notebookMetadata?: vscode.NotebookDocumentMetadata; metadata?: vscode.WorkspaceEditEntryMetadata; } @@ -629,17 +630,21 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit { this._edits.push({ _type: FileEditType.File, from: uri, to: undefined, options, metadata }); } - // --- cell + // --- notebook - replaceCells(uri: URI, start: number, end: number, cells: vscode.NotebookCellData[], metadata?: vscode.WorkspaceEditEntryMetadata): void { + replaceNotebookMetadata(uri: URI, value: vscode.NotebookDocumentMetadata, metadata?: vscode.WorkspaceEditEntryMetadata): void { + this._edits.push({ _type: FileEditType.Cell, metadata, uri, notebookMetadata: value }); + } + + replaceNotebookCells(uri: URI, start: number, end: number, cells: vscode.NotebookCellData[], metadata?: vscode.WorkspaceEditEntryMetadata): void { this._edits.push({ _type: FileEditType.Cell, metadata, uri, edit: { editType: CellEditType.Replace, index: start, count: end - start, cells: cells.map(cell => ({ ...cell, outputs: cell.outputs.map(output => addIdToOutput(output)) })) } }); } - replaceCellOutput(uri: URI, index: number, outputs: vscode.CellOutput[], metadata?: vscode.WorkspaceEditEntryMetadata): void { + replaceNotebookCellOutput(uri: URI, index: number, outputs: vscode.CellOutput[], metadata?: vscode.WorkspaceEditEntryMetadata): void { this._edits.push({ _type: FileEditType.Cell, metadata, uri, edit: { editType: CellEditType.Output, index, outputs: outputs.map(output => addIdToOutput(output)) } }); } - replaceCellMetadata(uri: URI, index: number, cellMetadata: vscode.NotebookCellMetadata, metadata?: vscode.WorkspaceEditEntryMetadata): void { + replaceNotebookCellMetadata(uri: URI, index: number, cellMetadata: vscode.NotebookCellMetadata, metadata?: vscode.WorkspaceEditEntryMetadata): void { this._edits.push({ _type: FileEditType.Cell, metadata, uri, edit: { editType: CellEditType.Metadata, index, metadata: cellMetadata } }); } diff --git a/src/vs/workbench/api/common/extHostWebviewView.ts b/src/vs/workbench/api/common/extHostWebviewView.ts index b167784c0a..69949e2603 100644 --- a/src/vs/workbench/api/common/extHostWebviewView.ts +++ b/src/vs/workbench/api/common/extHostWebviewView.ts @@ -23,17 +23,20 @@ class ExtHostWebviewView extends Disposable implements vscode.WebviewView { #isDisposed = false; #isVisible: boolean; #title: string | undefined; + #description: string | undefined; constructor( handle: extHostProtocol.WebviewHandle, proxy: extHostProtocol.MainThreadWebviewViewsShape, viewType: string, + title: string | undefined, webview: ExtHostWebview, isVisible: boolean, ) { super(); this.#viewType = viewType; + this.#title = title; this.#handle = handle; this.#proxy = proxy; this.#webview = webview; @@ -70,6 +73,19 @@ class ExtHostWebviewView extends Disposable implements vscode.WebviewView { } } + public get description(): string | undefined { + this.assertNotDisposed(); + return this.#description; + } + + public set description(value: string | undefined) { + this.assertNotDisposed(); + if (this.#description !== value) { + this.#description = value; + this.#proxy.$setWebviewViewDescription(this.#handle, value); + } + } + public get visible(): boolean { return this.#isVisible; } public get webview(): vscode.Webview { return this.#webview; } @@ -85,6 +101,11 @@ class ExtHostWebviewView extends Disposable implements vscode.WebviewView { this.#onDidChangeVisibility.fire(); } + public show(preserveFocus?: boolean): void { + this.assertNotDisposed(); + this.#proxy.$show(this.#handle, !!preserveFocus); + } + private assertNotDisposed() { if (this.#isDisposed) { throw new Error('Webview is disposed'); @@ -134,6 +155,7 @@ export class ExtHostWebviewViews implements extHostProtocol.ExtHostWebviewViewsS async $resolveWebviewView( webviewHandle: string, viewType: string, + title: string | undefined, state: any, cancellation: CancellationToken, ): Promise { @@ -145,7 +167,7 @@ export class ExtHostWebviewViews implements extHostProtocol.ExtHostWebviewViewsS const { provider, extension } = entry; const webview = this._extHostWebview.createNewWebview(webviewHandle, { /* todo */ }, extension); - const revivedView = new ExtHostWebviewView(webviewHandle, this._proxy, viewType, webview, true); + const revivedView = new ExtHostWebviewView(webviewHandle, this._proxy, viewType, title, webview, true); this._webviewViews.set(webviewHandle, revivedView); diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index 35df550543..36e9384da2 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -405,7 +405,7 @@ namespace schema { }; export const submenusContribution: IJSONSchema = { - description: localize('vscode.extension.contributes.submenus', "(Proposed API) Contributes submenu items to the editor"), + description: localize('vscode.extension.contributes.submenus', "Contributes submenu items to the editor"), type: 'array', items: submenu }; @@ -622,11 +622,6 @@ submenusExtensionPoint.setHandler(extensions => { return; } - if (!extension.description.enableProposedApi) { - collector.error(localize('submenu.proposedAPI.invalid', "Submenus are proposed API and are only available when running out of dev or with the following command line switch: --enable-proposed-api {0}", extension.description.identifier.value)); - return; - } - let absoluteIcon: { dark: URI; light?: URI; } | ThemeIcon | undefined; if (entry.value.icon) { if (typeof entry.value.icon === 'string') { @@ -675,7 +670,6 @@ menusExtensionPoint.setHandler(extensions => { } let menu = _apiMenusByKey.get(entry.key); - let isSubmenu = false; if (!menu) { const submenu = _submenus.get(entry.key); @@ -686,7 +680,6 @@ menusExtensionPoint.setHandler(extensions => { id: submenu.id, description: '' }; - isSubmenu = true; } } @@ -700,11 +693,6 @@ menusExtensionPoint.setHandler(extensions => { return; } - if (isSubmenu && !extension.description.enableProposedApi) { - collector.error(localize('proposedAPI.invalid.submenu', "{0} is a submenu identifier and is only available when running out of dev or with the following command line switch: --enable-proposed-api {1}", entry.key, extension.description.identifier.value)); - return; - } - for (const menuItem of entry.value) { let item: IMenuItem | ISubmenuItem; @@ -725,13 +713,8 @@ menusExtensionPoint.setHandler(extensions => { item = { command, alt, group: undefined, order: undefined, when: undefined }; } else { - if (!extension.description.enableProposedApi) { - collector.error(localize('proposedAPI.invalid.submenureference', "Menu item references a submenu which is only available when running out of dev or with the following command line switch: --enable-proposed-api {0}", extension.description.identifier.value)); - continue; - } - if (menu.supportsSubmenus === false) { - collector.error(localize('proposedAPI.unsupported.submenureference', "Menu item references a submenu for a menu which doesn't have submenu support.")); + collector.error(localize('unsupported.submenureference', "Menu item references a submenu for a menu which doesn't have submenu support.")); continue; } diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 86b6ade822..bf43ae1cea 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -610,7 +610,12 @@ export class MoveViewAction extends Action { return new Promise((resolve, reject) => { quickPick.onDidAccept(() => { const viewId = quickPick.selectedItems[0]; - resolve(viewId.id); + if (viewId.id) { + resolve(viewId.id); + } else { + reject(); + } + quickPick.hide(); }); diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index 67598743f5..4757af339d 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, ActiveEditorAvailableEditorIdsContext } from 'vs/workbench/common/editor'; +import { ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, TEXT_DIFF_EDITOR_ID, SplitEditorsVertically, InEditorZenModeContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, 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'; @@ -21,14 +21,12 @@ import { PanelPositionContext } from 'vs/workbench/common/panel'; import { getRemoteName } from 'vs/platform/remote/common/remoteHosts'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -export const RemoteNameContext = new RawContextKey('remoteName', ''); -export const RemoteConnectionState = new RawContextKey<'' | 'initializing' | 'disconnected' | 'connected'>('remoteConnectionState', ''); - export const WorkbenchStateContext = new RawContextKey('workbenchState', undefined); - export const WorkspaceFolderCountContext = new RawContextKey('workspaceFolderCount', 0); -export const RemoteFileDialogContext = new RawContextKey('remoteFileDialogVisible', false); +export const DirtyWorkingCopiesContext = new RawContextKey('dirtyWorkingCopies', false); + +export const RemoteNameContext = new RawContextKey('remoteName', ''); export const IsFullscreenContext = new RawContextKey('isFullscreen', false); @@ -89,7 +87,7 @@ export class WorkbenchContextKeysHandler extends Disposable { // Editors this.activeEditorContext = ActiveEditorContext.bindTo(this.contextKeyService); - this.activeEditorIsReadonly = ActiveEditorIsReadonlyContext.bindTo(this.contextKeyService); + this.activeEditorIsReadonly = ActiveEditorReadonlyContext.bindTo(this.contextKeyService); this.activeEditorAvailableEditorIds = ActiveEditorAvailableEditorIdsContext.bindTo(this.contextKeyService); this.editorsVisibleContext = EditorsVisibleContext.bindTo(this.contextKeyService); this.textCompareEditorVisibleContext = TextCompareEditorVisibleContext.bindTo(this.contextKeyService); diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 10d41a7923..4bc9ca4706 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -13,12 +13,12 @@ import { IBadge, NumberBadge } from 'vs/workbench/services/activity/common/activ import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; -import { ToggleActivityBarVisibilityAction, ToggleMenuBarAction } from 'vs/workbench/browser/actions/layoutActions'; +import { ToggleActivityBarVisibilityAction, ToggleMenuBarAction, ToggleSidebarPositionAction } from 'vs/workbench/browser/actions/layoutActions'; import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER, ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_INACTIVE_FOREGROUND, ACTIVITY_BAR_ACTIVE_BACKGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BORDER } from 'vs/workbench/common/theme'; import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { CompositeBar, ICompositeBarItem, CompositeDragAndDrop } from 'vs/workbench/browser/parts/compositeBar'; -import { Dimension, addClass, removeNode, createCSSRule, asCSSUrl, toggleClass, addDisposableListener, EventType } from 'vs/base/browser/dom'; +import { Dimension, createCSSRule, asCSSUrl, addDisposableListener, EventType } from 'vs/base/browser/dom'; import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { URI, UriComponents } from 'vs/base/common/uri'; @@ -181,6 +181,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { actions.push(toggleAccountsVisibilityAction); actions.push(new Separator()); + actions.push(this.instantiationService.createInstance(ToggleSidebarPositionAction, ToggleSidebarPositionAction.ID, ToggleSidebarPositionAction.getLabel(this.layoutService))); actions.push(new Action( ToggleActivityBarVisibilityAction.ID, nls.localize('hideActivitBar', "Hide Activity Bar"), @@ -406,7 +407,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { } if (this.menuBarContainer) { - removeNode(this.menuBarContainer); + this.menuBarContainer.remove(); this.menuBarContainer = undefined; this.registerKeyboardNavigationListeners(); } @@ -414,7 +415,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { private installMenubar() { this.menuBarContainer = document.createElement('div'); - addClass(this.menuBarContainer, 'menubar'); + this.menuBarContainer.classList.add('menubar'); const content = assertIsDefined(this.content); content.prepend(this.menuBarContainer); @@ -430,7 +431,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { this.element = parent; this.content = document.createElement('div'); - addClass(this.content, 'content'); + this.content.classList.add('content'); parent.appendChild(this.content); // Home action bar @@ -456,7 +457,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { // Global action bar this.globalActivitiesContainer = document.createElement('div'); - addClass(this.globalActivitiesContainer, 'global-activity'); + this.globalActivitiesContainer.classList.add('global-activity'); this.content.appendChild(this.globalActivitiesContainer); this.createGlobalActivityActionBar(this.globalActivitiesContainer); @@ -537,7 +538,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { this.homeBarContainer = document.createElement('div'); this.homeBarContainer.setAttribute('aria-label', nls.localize('homeIndicator', "Home")); this.homeBarContainer.setAttribute('role', 'toolbar'); - addClass(this.homeBarContainer, 'home-bar'); + this.homeBarContainer.classList.add('home-bar'); this.homeBar = this._register(new ActionBar(this.homeBarContainer, { orientation: ActionsOrientation.VERTICAL, @@ -549,7 +550,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { })); const homeBarIconBadge = document.createElement('div'); - addClass(homeBarIconBadge, 'home-bar-icon-badge'); + homeBarIconBadge.classList.add('home-bar-icon-badge'); this.homeBarContainer.appendChild(homeBarIconBadge); this.homeBar.push(this._register(this.instantiationService.createInstance(HomeAction, href, title, icon))); @@ -565,7 +566,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { container.style.backgroundColor = background; const borderColor = this.getColor(ACTIVITY_BAR_BORDER) || this.getColor(contrastBorder) || ''; - toggleClass(container, 'bordered', !!borderColor); + container.classList.toggle('bordered', !!borderColor); container.style.borderColor = borderColor ? borderColor : ''; } diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index 051b8d09e3..31e905608d 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -12,7 +12,7 @@ import { IBadge } from 'vs/workbench/services/activity/common/activity'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { CompositeActionViewItem, CompositeOverflowActivityAction, ICompositeActivity, CompositeOverflowActivityActionViewItem, ActivityAction, ICompositeBar, ICompositeBarColors } from 'vs/workbench/browser/parts/compositeBarActions'; -import { Dimension, $, addDisposableListener, EventType, EventHelper, toggleClass, isAncestor } from 'vs/base/browser/dom'; +import { Dimension, $, addDisposableListener, EventType, EventHelper, isAncestor } from 'vs/base/browser/dom'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Widget } from 'vs/base/browser/ui/widget'; @@ -285,8 +285,8 @@ export class CompositeBar extends Widget implements ICompositeBar { } private updateFromDragging(element: HTMLElement, showFeedback: boolean, front: boolean): Before2D | undefined { - toggleClass(element, 'dragged-over-head', showFeedback && front); - toggleClass(element, 'dragged-over-tail', showFeedback && !front); + element.classList.toggle('dragged-over-head', showFeedback && front); + element.classList.toggle('dragged-over-tail', showFeedback && !front); if (!showFeedback) { return undefined; diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index 4751eccd9b..2d23f1a1cc 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -205,7 +205,7 @@ export class ActivityActionViewItem extends BaseActionViewItem { // Try hard to prevent keyboard only focus feedback when using mouse this._register(dom.addDisposableListener(this.container, dom.EventType.MOUSE_DOWN, () => { - dom.addClass(this.container, 'clicked'); + this.container.classList.add('clicked'); })); this._register(dom.addDisposableListener(this.container, dom.EventType.MOUSE_UP, () => { @@ -214,7 +214,7 @@ export class ActivityActionViewItem extends BaseActionViewItem { } this.mouseUpTimeout = setTimeout(() => { - dom.removeClass(this.container, 'clicked'); + this.container.classList.remove('clicked'); }, 800); // delayed to prevent focus feedback from showing on mouse up })); @@ -328,7 +328,7 @@ export class ActivityActionViewItem extends BaseActionViewItem { if (this.options.icon && !this.activity.iconUrl) { // Only apply codicon class to activity bar icon items without iconUrl - dom.addClass(this.label, 'codicon'); + this.label.classList.add('codicon'); } if (!this.options.icon) { @@ -588,10 +588,10 @@ export class CompositeActionViewItem extends ActivityActionViewItem { const left = forceLeft || (preferLeft && !lastClasses.horizontal) || (!forceRight && lastClasses.horizontal === 'left'); const right = forceRight || (!preferLeft && !lastClasses.horizontal) || (!forceLeft && lastClasses.horizontal === 'right'); - dom.toggleClass(element, 'top', showFeedback && top); - dom.toggleClass(element, 'bottom', showFeedback && bottom); - dom.toggleClass(element, 'left', showFeedback && left); - dom.toggleClass(element, 'right', showFeedback && right); + element.classList.toggle('top', showFeedback && top); + element.classList.toggle('bottom', showFeedback && bottom); + element.classList.toggle('left', showFeedback && left); + element.classList.toggle('right', showFeedback && right); if (!showFeedback) { return undefined; @@ -646,11 +646,11 @@ export class CompositeActionViewItem extends ActivityActionViewItem { protected updateChecked(): void { if (this.getAction().checked) { - dom.addClass(this.container, 'checked'); + this.container.classList.add('checked'); this.container.setAttribute('aria-label', nls.localize('compositeActive', "{0} active", this.container.title)); this.container.setAttribute('aria-expanded', 'true'); } else { - dom.removeClass(this.container, 'checked'); + this.container.classList.remove('checked'); this.container.setAttribute('aria-label', this.container.title); this.container.setAttribute('aria-expanded', 'false'); } @@ -663,9 +663,9 @@ export class CompositeActionViewItem extends ActivityActionViewItem { } if (this.getAction().enabled) { - dom.removeClass(this.element, 'disabled'); + this.element.classList.remove('disabled'); } else { - dom.addClass(this.element, 'disabled'); + this.element.classList.add('disabled'); } } diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 158991a4d2..e59cc2a261 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -7,7 +7,6 @@ import 'vs/css!./media/compositepart'; import * as nls from 'vs/nls'; import { defaultGenerator } from 'vs/base/common/idGenerator'; import { IDisposable, dispose, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; -import * as strings from 'vs/base/common/strings'; import { Emitter } from 'vs/base/common/event'; import * as errors from 'vs/base/common/errors'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; @@ -29,7 +28,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachProgressBarStyler } from 'vs/platform/theme/common/styler'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { Dimension, append, $, addClass, hide, show, addClasses } from 'vs/base/browser/dom'; +import { Dimension, append, $, hide, show, addClasses } from 'vs/base/browser/dom'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { assertIsDefined, withNullAsUndefined } from 'vs/base/common/types'; @@ -384,7 +383,7 @@ export abstract class CompositePart extends Part { // Title Area Container const titleArea = append(parent, $('.composite')); - addClass(titleArea, 'title'); + titleArea.classList.add('title'); // Left Title Label this.titleLabel = this.createTitleLabel(titleArea); @@ -413,8 +412,8 @@ export abstract class CompositePart extends Part { const $this = this; return { - updateTitle: (id, title, keybinding) => { - titleLabel.innerHTML = strings.escape(title); + updateTitle: (_id, title, keybinding) => { + titleLabel.innerText = title; titleLabel.title = keybinding ? nls.localize('titleTooltip', "{0} ({1})", title, keybinding) : title; }, diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index c576816f5e..8127d434fe 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -7,7 +7,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import * as nls from 'vs/nls'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; -import { EditorInput, IEditorInputFactory, SideBySideEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, TextCompareEditorActiveContext, EditorPinnedContext, EditorGroupEditorsCountContext, EditorStickyContext, ActiveEditorAvailableEditorIdsContext } from 'vs/workbench/common/editor'; +import { EditorInput, IEditorInputFactory, SideBySideEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, TextCompareEditorActiveContext, ActiveEditorPinnedContext, EditorGroupEditorsCountContext, ActiveEditorStickyContext, ActiveEditorAvailableEditorIdsContext, MultipleEditorGroupsContext, ActiveEditorDirtyContext } from 'vs/workbench/common/editor'; import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; @@ -53,6 +53,7 @@ import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess'; import { ActiveGroupEditorsByMostRecentlyUsedQuickAccess, AllEditorsByAppearanceQuickAccess, AllEditorsByMostRecentlyUsedQuickAccess } from 'vs/workbench/browser/parts/editor/editorQuickAccess'; +import { IPathService } from 'vs/workbench/services/path/common/pathService'; // Register String Editor Registry.as(EditorExtensions.Editors).registerEditor( @@ -113,7 +114,8 @@ class UntitledTextEditorInputFactory implements IEditorInputFactory { constructor( @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IPathService private readonly pathService: IPathService ) { } canSerialize(editorInput: EditorInput): boolean { @@ -129,7 +131,7 @@ class UntitledTextEditorInputFactory implements IEditorInputFactory { let resource = untitledTextEditorInput.resource; if (untitledTextEditorInput.model.hasAssociatedFilePath) { - resource = toLocalResource(resource, this.environmentService.configuration.remoteAuthority); // untitled with associated file path use the local schema + resource = toLocalResource(resource, this.environmentService.configuration.remoteAuthority, this.pathService.defaultUriScheme); // untitled with associated file path use the local schema } // Mode: only remember mode if it is either specific (not text) @@ -447,9 +449,9 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCo MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: nls.localize('closeAllSaved', "Close Saved") }, group: '1_close', order: 40 }); MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeAll', "Close All") }, group: '1_close', order: 50 }); MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: ReopenResourcesAction.ID, title: ReopenResourcesAction.LABEL }, group: '1_open', order: 10, when: ActiveEditorAvailableEditorIdsContext }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.KEEP_EDITOR_COMMAND_ID, title: nls.localize('keepOpen', "Keep Open"), precondition: EditorPinnedContext.toNegated() }, group: '3_preview', order: 10, when: ContextKeyExpr.has('config.workbench.editor.enablePreview') }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.PIN_EDITOR_COMMAND_ID, title: nls.localize('pin', "Pin") }, group: '3_preview', order: 20, when: ContextKeyExpr.and(EditorStickyContext.toNegated(), ContextKeyExpr.has('config.workbench.editor.showTabs')) }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.UNPIN_EDITOR_COMMAND_ID, title: nls.localize('unpin', "Unpin") }, group: '3_preview', order: 20, when: ContextKeyExpr.and(EditorStickyContext, ContextKeyExpr.has('config.workbench.editor.showTabs')) }); +MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.KEEP_EDITOR_COMMAND_ID, title: nls.localize('keepOpen', "Keep Open"), precondition: ActiveEditorPinnedContext.toNegated() }, group: '3_preview', order: 10, when: ContextKeyExpr.has('config.workbench.editor.enablePreview') }); +MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.PIN_EDITOR_COMMAND_ID, title: nls.localize('pin', "Pin") }, group: '3_preview', order: 20, when: ActiveEditorStickyContext.toNegated() }); +MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.UNPIN_EDITOR_COMMAND_ID, title: nls.localize('unpin', "Unpin") }, group: '3_preview', order: 20, when: ActiveEditorStickyContext }); MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.SPLIT_EDITOR_UP, title: nls.localize('splitUp', "Split Up") }, group: '5_split', order: 10 }); MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.SPLIT_EDITOR_DOWN, title: nls.localize('splitDown', "Split Down") }, group: '5_split', order: 20 }); MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.SPLIT_EDITOR_LEFT, title: nls.localize('splitLeft', "Split Left") }, group: '5_split', order: 30 }); @@ -518,14 +520,14 @@ appendEditorToolItem( } ); -// Editor Title Menu: Close Group (tabs disabled) +// Editor Title Menu: Close (tabs disabled, dirty editor) appendEditorToolItem( { id: editorCommands.CLOSE_EDITOR_COMMAND_ID, title: nls.localize('close', "Close"), - icon: { id: 'codicon/close' } + icon: { id: 'codicon/close-dirty' } }, - ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ContextKeyExpr.not('groupActiveEditorDirty')), + ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ActiveEditorDirtyContext), 1000000, // towards the far end { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, @@ -534,13 +536,30 @@ appendEditorToolItem( } ); +// Editor Title Menu: Close (tabs disabled, sticky editor) +appendEditorToolItem( + { + id: editorCommands.UNPIN_EDITOR_COMMAND_ID, + title: nls.localize('unpin', "Unpin"), + icon: { id: 'codicon/pinned' } + }, + ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ActiveEditorDirtyContext.toNegated(), ActiveEditorStickyContext), + 1000000, // towards the far end + { + id: editorCommands.CLOSE_EDITOR_COMMAND_ID, + title: nls.localize('close', "Close"), + icon: { id: 'codicon/close' } + } +); + +// Editor Title Menu: Unpin (tabs disabled, normal editor) appendEditorToolItem( { id: editorCommands.CLOSE_EDITOR_COMMAND_ID, title: nls.localize('close', "Close"), - icon: { id: 'codicon/close-dirty' } + icon: { id: 'codicon/close' } }, - ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ContextKeyExpr.has('groupActiveEditorDirty')), + ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ActiveEditorDirtyContext.toNegated(), ActiveEditorStickyContext.toNegated()), 1000000, // towards the far end { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, @@ -596,12 +615,15 @@ appendEditorToolItem( // Editor Commands for Command Palette const viewCategory = { value: nls.localize('view', "View"), original: 'View' }; MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.KEEP_EDITOR_COMMAND_ID, title: { value: nls.localize('keepEditor', "Keep Editor"), original: 'Keep Editor' }, category: viewCategory }, when: ContextKeyExpr.has('config.workbench.editor.enablePreview') }); -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.PIN_EDITOR_COMMAND_ID, title: { value: nls.localize('pinEditor', "Pin Editor"), original: 'Pin Editor' }, category: viewCategory }, when: ContextKeyExpr.has('config.workbench.editor.showTabs') }); -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.UNPIN_EDITOR_COMMAND_ID, title: { value: nls.localize('unpinEditor', "Unpin Editor"), original: 'Unpin Editor' }, category: viewCategory }, when: ContextKeyExpr.has('config.workbench.editor.showTabs') }); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.PIN_EDITOR_COMMAND_ID, title: { value: nls.localize('pinEditor', "Pin Editor"), original: 'Pin Editor' }, category: viewCategory } }); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.UNPIN_EDITOR_COMMAND_ID, title: { value: nls.localize('unpinEditor', "Unpin Editor"), original: 'Unpin Editor' }, category: viewCategory } }); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_EDITOR_COMMAND_ID, title: { value: nls.localize('closeEditor', "Close Editor"), original: 'Close Editor' }, category: viewCategory } }); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_PINNED_EDITOR_COMMAND_ID, title: { value: nls.localize('closePinnedEditor', "Close Pinned Editor"), original: 'Close Pinned Editor' }, category: viewCategory } }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: { value: nls.localize('closeEditorsInGroup', "Close All Editors in Group"), original: 'Close All Editors in Group' }, category: viewCategory } }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: { value: nls.localize('closeSavedEditors', "Close Saved Editors in Group"), original: 'Close Saved Editors in Group' }, category: viewCategory } }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, title: { value: nls.localize('closeOtherEditors', "Close Other Editors in Group"), original: 'Close Other Editors in Group' }, category: viewCategory } }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, title: { value: nls.localize('closeRightEditors', "Close Editors to the Right in Group"), original: 'Close Editors to the Right in Group' }, category: viewCategory } }); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_EDITORS_AND_GROUP_COMMAND_ID, title: { value: nls.localize('closeEditorGroup', "Close Editor Group"), original: 'Close Editor Group' }, category: viewCategory }, when: MultipleEditorGroupsContext }); // File menu MenuRegistry.appendMenuItem(MenuId.MenubarRecentMenu, { diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index a98caa5f1d..2f6c6578f4 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -30,6 +30,7 @@ export const DEFAULT_EDITOR_PART_OPTIONS: IEditorPartOptions = { highlightModifiedTabs: false, tabCloseButton: 'right', tabSizing: 'fit', + pinnedTabSizing: 'shrink', titleScrollbarSizing: 'default', focusRecentEditorAfterClose: true, showIcons: true, diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index de43eb5c67..141f3a2fe6 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -10,7 +10,7 @@ import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/la import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { CLOSE_EDITOR_COMMAND_ID, MOVE_ACTIVE_EDITOR_COMMAND_ID, ActiveEditorMoveArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, mergeAllGroups } from 'vs/workbench/browser/parts/editor/editorCommands'; +import { CLOSE_EDITOR_COMMAND_ID, MOVE_ACTIVE_EDITOR_COMMAND_ID, ActiveEditorMoveArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, mergeAllGroups, UNPIN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { IEditorGroupsService, IEditorGroup, GroupsArrangement, GroupLocation, GroupDirection, preferredSideBySideGroupDirection, IFindGroupScope, GroupOrientation, EditorGroupLayout, GroupsOrder, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -406,6 +406,24 @@ export class CloseEditorAction extends Action { } } +export class UnpinEditorAction extends Action { + + static readonly ID = 'workbench.action.unpinActiveEditor'; + static readonly LABEL = nls.localize('unpinEditor', "Unpin Editor"); + + constructor( + id: string, + label: string, + @ICommandService private readonly commandService: ICommandService + ) { + super(id, label, Codicon.pinned.classNames); + } + + run(context?: IEditorCommandsContext): Promise { + return this.commandService.executeCommand(UNPIN_EDITOR_COMMAND_ID, undefined, context); + } +} + export class CloseOneEditorAction extends Action { static readonly ID = 'workbench.action.closeActiveEditor'; diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index 3f05e726d0..0707e87c10 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as types from 'vs/base/common/types'; +import { isObject, isString, isUndefined, isNumber, withNullAsUndefined } from 'vs/base/common/types'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { TextCompareEditorVisibleContext, EditorInput, IEditorIdentifier, IEditorCommandsContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, CloseDirection, IEditorInput, IVisibleEditorPane, EditorStickyContext, EditorsOrder } from 'vs/workbench/common/editor'; +import { TextCompareEditorVisibleContext, EditorInput, IEditorIdentifier, IEditorCommandsContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, CloseDirection, IEditorInput, IVisibleEditorPane, ActiveEditorStickyContext, EditorsOrder } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor'; @@ -29,6 +29,7 @@ export const CLOSE_EDITORS_IN_GROUP_COMMAND_ID = 'workbench.action.closeEditorsI export const CLOSE_EDITORS_AND_GROUP_COMMAND_ID = 'workbench.action.closeEditorsAndGroup'; export const CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID = 'workbench.action.closeEditorsToTheRight'; export const CLOSE_EDITOR_COMMAND_ID = 'workbench.action.closeActiveEditor'; +export const CLOSE_PINNED_EDITOR_COMMAND_ID = 'workbench.action.closeActivePinnedEditor'; export const CLOSE_EDITOR_GROUP_COMMAND_ID = 'workbench.action.closeGroup'; export const CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID = 'workbench.action.closeOtherEditors'; @@ -59,19 +60,19 @@ export interface ActiveEditorMoveArguments { } const isActiveEditorMoveArg = function (arg: ActiveEditorMoveArguments): boolean { - if (!types.isObject(arg)) { + if (!isObject(arg)) { return false; } - if (!types.isString(arg.to)) { + if (!isString(arg.to)) { return false; } - if (!types.isUndefined(arg.by) && !types.isString(arg.by)) { + if (!isUndefined(arg.by) && !isString(arg.by)) { return false; } - if (!types.isUndefined(arg.value) && !types.isNumber(arg.value)) { + if (!isUndefined(arg.value) && !isNumber(arg.value)) { return false; } @@ -451,7 +452,7 @@ export function splitEditor(editorGroupService: IEditorGroupsService, direction: if (context && typeof context.editorIndex === 'number') { editorToCopy = sourceGroup.getEditorByIndex(context.editorIndex); } else { - editorToCopy = types.withNullAsUndefined(sourceGroup.activeEditor); + editorToCopy = withNullAsUndefined(sourceGroup.activeEditor); } if (editorToCopy && (editorToCopy as EditorInput).supportsSplitEditor()) { @@ -469,7 +470,7 @@ function registerSplitEditorCommands() { { id: SPLIT_EDITOR_LEFT, direction: GroupDirection.LEFT }, { id: SPLIT_EDITOR_RIGHT, direction: GroupDirection.RIGHT } ].forEach(({ id, direction }) => { - CommandsRegistry.registerCommand(id, function (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) { + CommandsRegistry.registerCommand(id, function (accessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext) { splitEditor(accessor.get(IEditorGroupsService), direction, getCommandsContext(resourceOrContext, context)); }); }); @@ -477,51 +478,55 @@ function registerSplitEditorCommands() { function registerCloseEditorCommands() { - KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: CLOSE_SAVED_EDITORS_COMMAND_ID, - weight: KeybindingWeight.WorkbenchContrib, - when: undefined, - primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_U), - handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { - const editorGroupService = accessor.get(IEditorGroupsService); - const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService); + // A special handler for "Close Editor" depending on context + // - keybindining: do not close sticky editors, rather open the next non-sticky editor + // - menu: always close editor, even sticky ones + function closeEditorHandler(accessor: ServicesAccessor, forceCloseStickyEditors: boolean, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext): Promise { + const editorGroupsService = accessor.get(IEditorGroupsService); + const editorService = accessor.get(IEditorService); - const activeGroup = editorGroupService.activeGroup; - if (contexts.length === 0) { - contexts.push({ groupId: activeGroup.id }); // active group as fallback - } - - return Promise.all(distinct(contexts.map(c => c.groupId)).map(async groupId => { - const group = editorGroupService.getGroup(groupId); - if (group) { - return group.closeEditors({ savedOnly: true, excludeSticky: true }); - } - })); + let keepStickyEditors = true; + if (forceCloseStickyEditors) { + keepStickyEditors = false; // explicitly close sticky editors + } else if (resourceOrContext || context) { + keepStickyEditors = false; // we have a context, as such this command was used e.g. from the tab context menu } - }); - KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: CLOSE_EDITORS_IN_GROUP_COMMAND_ID, - weight: KeybindingWeight.WorkbenchContrib, - when: undefined, - primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_W), - handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { - const editorGroupService = accessor.get(IEditorGroupsService); - const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService); - const distinctGroupIds = distinct(contexts.map(c => c.groupId)); + // Without context: skip over sticky editor and select next if active editor is sticky + if (keepStickyEditors && !resourceOrContext && !context) { + const activeGroup = editorGroupsService.activeGroup; + const activeEditor = activeGroup.activeEditor; - if (distinctGroupIds.length === 0) { - distinctGroupIds.push(editorGroupService.activeGroup.id); - } + if (activeEditor && activeGroup.isSticky(activeEditor)) { - return Promise.all(distinctGroupIds.map(async groupId => { - const group = editorGroupService.getGroup(groupId); - if (group) { - return group.closeAllEditors({ excludeSticky: true }); + // Open next recently active in same group + const nextNonStickyEditorInGroup = activeGroup.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE, { excludeSticky: true })[0]; + if (nextNonStickyEditorInGroup) { + return activeGroup.openEditor(nextNonStickyEditorInGroup); } - })); + + // Open next recently active across all groups + const nextNonStickyEditorInAllGroups = editorService.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE, { excludeSticky: true })[0]; + if (nextNonStickyEditorInAllGroups) { + return Promise.resolve(editorGroupsService.getGroup(nextNonStickyEditorInAllGroups.groupId)?.openEditor(nextNonStickyEditorInAllGroups.editor)); + } + } } - }); + + // With context: proceed to close editors as instructed + const { editors, groups } = getEditorsContext(accessor, resourceOrContext, context); + + return Promise.all(groups.map(async group => { + if (group) { + const editorsToClose = coalesce(editors + .filter(editor => editor.groupId === group.id) + .map(editor => typeof editor.editorIndex === 'number' ? group.getEditorByIndex(editor.editorIndex) : group.activeEditor)) + .filter(editor => !keepStickyEditors || !group.isSticky(editor)); + + return group.closeEditors(editorsToClose); + } + })); + } KeybindingsRegistry.registerCommandAndKeybindingRule({ id: CLOSE_EDITOR_COMMAND_ID, @@ -529,25 +534,24 @@ function registerCloseEditorCommands() { when: undefined, primary: KeyMod.CtrlCmd | KeyCode.KEY_W, win: { primary: KeyMod.CtrlCmd | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_W] }, - handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { - const editorGroupService = accessor.get(IEditorGroupsService); - const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService); + handler: (accessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { + return closeEditorHandler(accessor, false, resourceOrContext, context); + } + }); - const activeGroup = editorGroupService.activeGroup; - if (contexts.length === 0 && activeGroup.activeEditor) { - contexts.push({ groupId: activeGroup.id, editorIndex: activeGroup.getIndexOfEditor(activeGroup.activeEditor) }); // active editor as fallback - } + CommandsRegistry.registerCommand(CLOSE_PINNED_EDITOR_COMMAND_ID, (accessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { + return closeEditorHandler(accessor, true /* force close pinned editors */, resourceOrContext, context); + }); - const groupIds = distinct(contexts.map(context => context.groupId)); - - return Promise.all(groupIds.map(async groupId => { - const group = editorGroupService.getGroup(groupId); + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: CLOSE_EDITORS_IN_GROUP_COMMAND_ID, + weight: KeybindingWeight.WorkbenchContrib, + when: undefined, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_W), + handler: (accessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { + return Promise.all(getEditorsContext(accessor, resourceOrContext, context).groups.map(async group => { if (group) { - const editors = coalesce(contexts - .filter(context => context.groupId === groupId) - .map(context => typeof context.editorIndex === 'number' ? group.getEditorByIndex(context.editorIndex) : group.activeEditor)); - - return group.closeEditors(editors); + return group.closeAllEditors({ excludeSticky: true }); } })); } @@ -559,7 +563,7 @@ function registerCloseEditorCommands() { when: ContextKeyExpr.and(ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext), primary: KeyMod.CtrlCmd | KeyCode.KEY_W, win: { primary: KeyMod.CtrlCmd | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_W] }, - handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { + handler: (accessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { const editorGroupService = accessor.get(IEditorGroupsService); const commandsContext = getCommandsContext(resourceOrContext, context); @@ -576,34 +580,40 @@ function registerCloseEditorCommands() { } }); + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: CLOSE_SAVED_EDITORS_COMMAND_ID, + weight: KeybindingWeight.WorkbenchContrib, + when: undefined, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_U), + handler: (accessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { + return Promise.all(getEditorsContext(accessor, resourceOrContext, context).groups.map(async group => { + if (group) { + return group.closeEditors({ savedOnly: true, excludeSticky: true }); + } + })); + } + }); + KeybindingsRegistry.registerCommandAndKeybindingRule({ id: CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, weight: KeybindingWeight.WorkbenchContrib, when: undefined, primary: undefined, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_T }, - handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { - const editorGroupService = accessor.get(IEditorGroupsService); - const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService); - - const activeGroup = editorGroupService.activeGroup; - if (contexts.length === 0 && activeGroup.activeEditor) { - contexts.push({ groupId: activeGroup.id, editorIndex: activeGroup.getIndexOfEditor(activeGroup.activeEditor) }); // active editor as fallback - } - - const groupIds = distinct(contexts.map(context => context.groupId)); - - return Promise.all(groupIds.map(async groupId => { - const group = editorGroupService.getGroup(groupId); + handler: (accessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { + const { editors, groups } = getEditorsContext(accessor, resourceOrContext, context); + return Promise.all(groups.map(async group => { if (group) { - const editors = contexts - .filter(context => context.groupId === groupId) - .map(context => typeof context.editorIndex === 'number' ? group.getEditorByIndex(context.editorIndex) : group.activeEditor); + const editorsToKeep = editors + .filter(editor => editor.groupId === group.id) + .map(editor => typeof editor.editorIndex === 'number' ? group.getEditorByIndex(editor.editorIndex) : group.activeEditor); - const editorsToClose = group.getEditors(EditorsOrder.SEQUENTIAL, { excludeSticky: true }).filter(editor => !editors.includes(editor)); + const editorsToClose = group.getEditors(EditorsOrder.SEQUENTIAL, { excludeSticky: true }).filter(editor => !editorsToKeep.includes(editor)); - if (group.activeEditor) { - group.pinEditor(group.activeEditor); + for (const editorToKeep of editorsToKeep) { + if (editorToKeep) { + group.pinEditor(editorToKeep); + } } return group.closeEditors(editorsToClose); @@ -617,7 +627,7 @@ function registerCloseEditorCommands() { weight: KeybindingWeight.WorkbenchContrib, when: undefined, primary: undefined, - handler: async (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { + handler: async (accessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { const editorGroupService = accessor.get(IEditorGroupsService); const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context)); @@ -631,12 +641,28 @@ function registerCloseEditorCommands() { } }); + CommandsRegistry.registerCommand(CLOSE_EDITORS_AND_GROUP_COMMAND_ID, async (accessor: ServicesAccessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { + const editorGroupService = accessor.get(IEditorGroupsService); + + const { group } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context)); + if (group) { + await group.closeAllEditors(); + + if (group.count === 0 && editorGroupService.getGroup(group.id) /* could be gone by now */) { + editorGroupService.removeGroup(group); // only remove group if it is now empty + } + } + }); +} + +function registerOtherEditorCommands(): void { + KeybindingsRegistry.registerCommandAndKeybindingRule({ id: KEEP_EDITOR_COMMAND_ID, weight: KeybindingWeight.WorkbenchContrib, when: undefined, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.Enter), - handler: async (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { + handler: async (accessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { const editorGroupService = accessor.get(IEditorGroupsService); const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context)); @@ -649,9 +675,9 @@ function registerCloseEditorCommands() { KeybindingsRegistry.registerCommandAndKeybindingRule({ id: PIN_EDITOR_COMMAND_ID, weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(EditorStickyContext.toNegated(), ContextKeyExpr.has('config.workbench.editor.showTabs')), + when: ActiveEditorStickyContext.toNegated(), primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.Shift | KeyCode.Enter), - handler: async (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { + handler: async (accessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { const editorGroupService = accessor.get(IEditorGroupsService); const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context)); @@ -664,9 +690,9 @@ function registerCloseEditorCommands() { KeybindingsRegistry.registerCommandAndKeybindingRule({ id: UNPIN_EDITOR_COMMAND_ID, weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(EditorStickyContext, ContextKeyExpr.has('config.workbench.editor.showTabs')), + when: ActiveEditorStickyContext, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.Shift | KeyCode.Enter), - handler: async (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { + handler: async (accessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { const editorGroupService = accessor.get(IEditorGroupsService); const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context)); @@ -681,7 +707,7 @@ function registerCloseEditorCommands() { weight: KeybindingWeight.WorkbenchContrib, when: undefined, primary: undefined, - handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { + handler: (accessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { const editorGroupService = accessor.get(IEditorGroupsService); const quickInputService = accessor.get(IQuickInputService); @@ -696,22 +722,30 @@ function registerCloseEditorCommands() { return quickInputService.quickAccess.show(ActiveGroupEditorsByMostRecentlyUsedQuickAccess.PREFIX); } }); - - CommandsRegistry.registerCommand(CLOSE_EDITORS_AND_GROUP_COMMAND_ID, async (accessor: ServicesAccessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { - const editorGroupService = accessor.get(IEditorGroupsService); - - const { group } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context)); - if (group) { - await group.closeAllEditors(); - - if (group.count === 0 && editorGroupService.getGroup(group.id) /* could be gone by now */) { - editorGroupService.removeGroup(group); // only remove group if it is now empty - } - } - }); } -function getCommandsContext(resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext): IEditorCommandsContext | undefined { +function getEditorsContext(accessor: ServicesAccessor, resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext): { editors: IEditorCommandsContext[], groups: Array } { + const editorGroupService = accessor.get(IEditorGroupsService); + const listService = accessor.get(IListService); + + const editorContext = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), listService, editorGroupService); + + const activeGroup = editorGroupService.activeGroup; + if (editorContext.length === 0 && activeGroup.activeEditor) { + // add the active editor as fallback + editorContext.push({ + groupId: activeGroup.id, + editorIndex: activeGroup.getIndexOfEditor(activeGroup.activeEditor) + }); + } + + return { + editors: editorContext, + groups: distinct(editorContext.map(context => context.groupId)).map(groupId => editorGroupService.getGroup(groupId)) + }; +} + +function getCommandsContext(resourceOrContext?: URI | IEditorCommandsContext, context?: IEditorCommandsContext): IEditorCommandsContext | undefined { if (URI.isUri(resourceOrContext)) { return context; } @@ -731,12 +765,16 @@ function resolveCommandsContext(editorGroupService: IEditorGroupsService, contex // Resolve from context let group = context && typeof context.groupId === 'number' ? editorGroupService.getGroup(context.groupId) : undefined; - let editor = group && context && typeof context.editorIndex === 'number' ? types.withNullAsUndefined(group.getEditorByIndex(context.editorIndex)) : undefined; + let editor = group && context && typeof context.editorIndex === 'number' ? withNullAsUndefined(group.getEditorByIndex(context.editorIndex)) : undefined; // Fallback to active group as needed if (!group) { group = editorGroupService.activeGroup; - editor = group.activeEditor; + } + + // Fallback to active editor as needed + if (!editor) { + editor = withNullAsUndefined(group.activeEditor); } return { group, editor }; @@ -803,6 +841,7 @@ export function setup(): void { registerDiffEditorCommands(); registerOpenEditorAtIndexCommands(); registerCloseEditorCommands(); + registerOtherEditorCommands(); registerFocusEditorGroupAtIndexCommands(); registerSplitEditorCommands(); } diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 9fcbde9ba5..b9512a1b22 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/editorgroupview'; import { EditorGroup, IEditorOpenOptions, EditorCloseEvent, ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup'; -import { EditorInput, EditorOptions, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditorPane, EditorGroupEditorsCountContext, SaveReason, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane, EditorStickyContext, EditorPinnedContext } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, ActiveEditorDirtyContext, IEditorPane, EditorGroupEditorsCountContext, SaveReason, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane, ActiveEditorStickyContext, ActiveEditorPinnedContext, Deprecated_EditorPinnedContext, Deprecated_EditorDirtyContext } from 'vs/workbench/common/editor'; import { Event, Emitter, Relay } from 'vs/base/common/event'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { addClass, addClasses, Dimension, trackFocus, toggleClass, removeClass, addDisposableListener, EventType, EventHelper, findParentWithClass, clearNode, isAncestor } from 'vs/base/browser/dom'; @@ -217,10 +217,12 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } private handleGroupContextKeys(contextKeyService: IContextKeyService): void { - const groupActiveEditorDirtyContextKey = EditorGroupActiveEditorDirtyContext.bindTo(contextKeyService); + const groupActiveEditorDirtyContext = ActiveEditorDirtyContext.bindTo(contextKeyService); + const deprecatedGroupActiveEditorDirtyContext = Deprecated_EditorDirtyContext.bindTo(contextKeyService); + const groupActiveEditorPinnedContext = ActiveEditorPinnedContext.bindTo(contextKeyService); + const deprecatedGroupActiveEditorPinnedContext = Deprecated_EditorPinnedContext.bindTo(contextKeyService); + const groupActiveEditorStickyContext = ActiveEditorStickyContext.bindTo(contextKeyService); const groupEditorsCountContext = EditorGroupEditorsCountContext.bindTo(contextKeyService); - const groupActiveEditorPinnedContext = EditorPinnedContext.bindTo(contextKeyService); - const groupActiveEditorStickyContext = EditorStickyContext.bindTo(contextKeyService); const activeEditorListener = new MutableDisposable(); @@ -229,10 +231,15 @@ export class EditorGroupView extends Themable implements IEditorGroupView { const activeEditor = this._group.activeEditor; if (activeEditor) { - groupActiveEditorDirtyContextKey.set(activeEditor.isDirty() && !activeEditor.isSaving()); - activeEditorListener.value = activeEditor.onDidChangeDirty(() => groupActiveEditorDirtyContextKey.set(activeEditor.isDirty() && !activeEditor.isSaving())); + groupActiveEditorDirtyContext.set(activeEditor.isDirty() && !activeEditor.isSaving()); + deprecatedGroupActiveEditorDirtyContext.set(activeEditor.isDirty() && !activeEditor.isSaving()); + activeEditorListener.value = activeEditor.onDidChangeDirty(() => { + groupActiveEditorDirtyContext.set(activeEditor.isDirty() && !activeEditor.isSaving()); + deprecatedGroupActiveEditorDirtyContext.set(activeEditor.isDirty() && !activeEditor.isSaving()); + }); } else { - groupActiveEditorDirtyContextKey.set(false); + groupActiveEditorDirtyContext.set(false); + deprecatedGroupActiveEditorDirtyContext.set(false); } }; @@ -247,6 +254,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { case GroupChangeKind.EDITOR_PIN: if (e.editor && e.editor === this._group.activeEditor) { groupActiveEditorPinnedContext.set(this._group.isPinned(this._group.activeEditor)); + deprecatedGroupActiveEditorPinnedContext.set(this._group.isPinned(this._group.activeEditor)); } break; case GroupChangeKind.EDITOR_STICKY: @@ -294,7 +302,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Toolbar const groupId = this._group.id; const containerToolbar = this._register(new ActionBar(toolbarContainer, { - ariaLabel: localize('araLabelGroupActions', "Editor group actions"), actionRunner: this._register(new class extends ActionRunner { + ariaLabel: localize('ariaLabelGroupActions', "Editor group actions"), actionRunner: this._register(new class extends ActionRunner { run(action: IAction) { return action.run(groupId); } diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css index 20462dc553..93a5e9de07 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css @@ -62,9 +62,9 @@ padding-left: 10px; } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon.close-button-right, -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon.close-button-off:not(.sticky) { - padding-left: 5px; /* reduce padding when we show icons and are in shrinking mode and tab close button is not left (unless sticky) */ +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon.tab-actions-right, +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon.tab-actions-off:not(.sticky-compact) { + padding-left: 5px; /* reduce padding when we show icons and are in shrinking mode and tab actions is not left (unless sticky-compact) */ } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit { @@ -75,50 +75,67 @@ } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink { - min-width: 60px; + min-width: 80px; flex-basis: 0; /* all tabs are even */ flex-grow: 1; /* all tabs grow even */ max-width: fit-content; max-width: -moz-fit-content; } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit.sticky, -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.sticky { +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit.sticky-compact, +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.sticky-compact, +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit.sticky-shrink, +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.sticky-shrink { - /** Sticky tabs do not scroll in case of overflow and are always above unsticky tabs which scroll under */ + /** Sticky compact/shrink tabs do not scroll in case of overflow and are always above unsticky tabs which scroll under */ position: sticky; z-index: 1; - /** Sticky tabs are even and never grow */ + /** Sticky compact/shrink tabs are even and never grow */ flex-basis: 0; flex-grow: 0; +} - /** Sticky tabs have a fixed width of 38px */ +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit.sticky-compact, +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.sticky-compact { + + /** Sticky compact tabs have a fixed width of 38px */ width: 38px; min-width: 38px; max-width: 38px; } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-fit.sticky, -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-shrink.sticky { +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit.sticky-shrink, +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.sticky-shrink { - /** Disable sticky positions for sticky tabs if the available space is too little */ + /** Sticky shrink tabs have a fixed width of 80px */ + width: 80px; + min-width: 80px; + max-width: 80px; +} + +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-fit.sticky-compact, +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-shrink.sticky-compact, +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-fit.sticky-shrink, +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-shrink.sticky-shrink { + + /** Disable sticky positions for sticky compact/shrink tabs if the available space is too little */ position: static; } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-left .action-label { +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-left .action-label { margin-right: 4px !important; } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.close-button-left::after, -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.close-button-off::after { +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.tab-actions-left::after, +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.tab-actions-off::after { content: ''; display: flex; flex: 0; width: 5px; /* Reserve space to hide tab fade when close button is left or off (fixes https://github.com/Microsoft/vscode/issues/45728) */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.close-button-left { +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.tab-actions-left { min-width: 80px; /* make more room for close button when it shows to the left */ padding-right: 5px; /* we need less room when sizing is shrink */ } @@ -131,7 +148,7 @@ pointer-events: none; /* prevents cursor flickering (fixes https://github.com/Microsoft/vscode/issues/38753) */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-left { +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-left { flex-direction: row-reverse; padding-left: 0; padding-right: 10px; @@ -178,6 +195,7 @@ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab .tab-label { margin-top: auto; margin-bottom: auto; + line-height: 35px; /* aligns icon and label vertically centered in the tab */ } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink .tab-label { @@ -198,8 +216,8 @@ opacity: 0; /* when tab has the focus this shade breaks the tab border (fixes https://github.com/Microsoft/vscode/issues/57819) */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sticky:not(.has-icon) .monaco-icon-label { - text-align: center; /* ensure that sticky tabs without icon have label centered */ +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sticky-compact:not(.has-icon) .monaco-icon-label { + text-align: center; /* ensure that sticky-compact tabs without icon have label centered */ } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit .monaco-icon-label, @@ -215,61 +233,57 @@ text-overflow: ellipsis; } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab .monaco-icon-label::before { - height: 16px; /* tweak the icon size of the editor labels when icons are enabled */ -} +/* Tab Actions */ -/* Tab Close */ - -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-close { +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-actions { margin-top: auto; margin-bottom: auto; width: 28px; } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-right.sizing-shrink > .tab-close { +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-right.sizing-shrink > .tab-actions { flex: 0; - overflow: hidden; /* let the close button be pushed out of view when sizing is set to shrink to make more room... */ + overflow: hidden; /* let the tab actions be pushed out of view when sizing is set to shrink to make more room... */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty.close-button-right.sizing-shrink > .tab-close, -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-right.sizing-shrink:hover > .tab-close, -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-right.sizing-shrink > .tab-close:focus-within { - overflow: visible; /* ...but still show the close button on hover, focus and when dirty */ +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty.tab-actions-right.sizing-shrink > .tab-actions, +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-right.sizing-shrink:hover > .tab-actions, +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-right.sizing-shrink > .tab-actions:focus-within { + overflow: visible; /* ...but still show the tab actions on hover, focus and when dirty */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off:not(.dirty) > .tab-close, -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.sticky > .tab-close { - display: none; /* hide the close action bar when we are configured to hide it (unless dirty, but always when sticky) */ +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-off:not(.dirty) > .tab-actions, +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-off.sticky-compact > .tab-actions { + display: none; /* hide the tab actions when we are configured to hide it (unless dirty, but always when sticky-compact) */ } -.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.active > .tab-close .action-label, /* always show it for active tab */ -.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab > .tab-close .action-label:focus, /* always show it on focus */ -.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab:hover > .tab-close .action-label, /* always show it on hover */ -.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.active:hover > .tab-close .action-label, /* always show it on hover */ -.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.dirty > .tab-close .action-label { /* always show it for dirty tabs */ +.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.active > .tab-actions .action-label, /* always show tab actions for active tab */ +.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab > .tab-actions .action-label:focus, /* always show tab actions on focus */ +.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab:hover > .tab-actions .action-label, /* always show tab actions on hover */ +.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.active:hover > .tab-actions .action-label, /* always show tab actions on hover */ +.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.dirty > .tab-actions .action-label { /* always show tab actions for dirty tabs */ opacity: 1; } -.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab > .tab-close .action-label.codicon { +.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab > .tab-actions .action-label.codicon { color: inherit; font-size: 16px; } -/* change close icon to dirty state icon */ -.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.dirty > .tab-close .action-label:not(:hover)::before, -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty > .tab-close .action-label:not(:hover)::before { +/* change tab actions icon to dirty state icon if tab dirty */ +.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.dirty > .tab-actions .action-label:not(:hover)::before, +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty > .tab-actions .action-label:not(:hover)::before { content: "\ea71"; /* use `circle-filled` icon unicode */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active > .tab-close .action-label, /* show dimmed for inactive group */ -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active:hover > .tab-close .action-label, /* show dimmed for inactive group */ -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty > .tab-close .action-label, /* show dimmed for inactive group */ -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab:hover > .tab-close .action-label { /* show dimmed for inactive group */ +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active > .tab-actions .action-label, /* show tab actions dimmed for inactive group */ +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active:hover > .tab-actions .action-label, /* show tab actions dimmed for inactive group */ +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty > .tab-actions .action-label, /* show tab actions dimmed for inactive group */ +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab:hover > .tab-actions .action-label { /* show tab actions dimmed for inactive group */ opacity: 0.5; } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-close .action-label { +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-actions .action-label { opacity: 0; display: block; height: 16px; @@ -280,26 +294,26 @@ margin-right: 0.5em; } -/* No Tab Close Button */ +/* No Tab Actions */ -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off { - padding-right: 10px; /* give a little bit more room if close button is off */ +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-off { + padding-right: 10px; /* give a little bit more room if tab actions is off */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.close-button-off:not(.sticky) { - padding-right: 5px; /* we need less room when sizing is shrink (unless tab is sticky) */ +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.tab-actions-off:not(.sticky-compact) { + padding-right: 5px; /* we need less room when sizing is shrink (unless tab is sticky-compact) */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty-border-top > .tab-close { - display: none; /* hide dirty state when highlightModifiedTabs is enabled and when running without close button */ +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-off.dirty-border-top > .tab-actions { + display: none; /* hide dirty state when highlightModifiedTabs is enabled and when running without tab actions */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty:not(.dirty-border-top):not(.sticky) { - padding-right: 0; /* remove extra padding when we are running without close button (unless tab is sticky) */ +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-off.dirty:not(.dirty-border-top):not(.sticky-compact) { + padding-right: 0; /* remove extra padding when we are running without tab actions (unless tab is sticky-compact) */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off > .tab-close { - pointer-events: none; /* don't allow dirty state/close button to be clicked when running without close button */ +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-off > .tab-actions { + pointer-events: none; /* don't allow tab actions to be clicked when running without tab actions */ } /* Editor Actions */ diff --git a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts index 5ba8c7b422..eddc9dce14 100644 --- a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts +++ b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts @@ -68,7 +68,7 @@ export class SideBySideEditor extends EditorPane { } protected createEditor(parent: HTMLElement): void { - DOM.addClass(parent, 'side-by-side-editor'); + parent.classList.add('side-by-side-editor'); const splitview = this.splitview = this._register(new SplitView(parent, { orientation: Orientation.HORIZONTAL })); this._register(this.splitview.onDidSashReset(() => splitview.distributeViewSizes())); diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index e62a1882db..cb8d0f39b7 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -20,12 +20,12 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IMenuService } from 'vs/platform/actions/common/actions'; import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -import { IDisposable, dispose, DisposableStore, combinedDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, DisposableStore, combinedDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { getOrSet } from 'vs/base/common/map'; -import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; -import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BACKGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, WORKBENCH_BACKGROUND, TAB_ACTIVE_BORDER_TOP, TAB_UNFOCUSED_ACTIVE_BORDER_TOP, TAB_ACTIVE_MODIFIED_BORDER, TAB_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_BACKGROUND, TAB_HOVER_FOREGROUND, TAB_UNFOCUSED_HOVER_FOREGROUND, EDITOR_GROUP_HEADER_TABS_BORDER } from 'vs/workbench/common/theme'; +import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BACKGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, WORKBENCH_BACKGROUND, TAB_ACTIVE_BORDER_TOP, TAB_UNFOCUSED_ACTIVE_BORDER_TOP, TAB_ACTIVE_MODIFIED_BORDER, TAB_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_BACKGROUND, TAB_HOVER_FOREGROUND, TAB_UNFOCUSED_HOVER_FOREGROUND, EDITOR_GROUP_HEADER_TABS_BORDER, TAB_LAST_PINNED_BORDER } from 'vs/workbench/common/theme'; import { activeContrastBorder, contrastBorder, editorBackground, breadcrumbsBackground } from 'vs/platform/theme/common/colorRegistry'; import { ResourcesDropHandler, DraggedEditorIdentifier, DraggedEditorGroupIdentifier, DragAndDropObserver } from 'vs/workbench/browser/dnd'; import { Color } from 'vs/base/common/color'; @@ -35,7 +35,7 @@ import { MergeGroupMode, IMergeGroupOptions, GroupsArrangement, IEditorGroupsSer import { addClass, addDisposableListener, hasClass, EventType, EventHelper, removeClass, Dimension, scheduleAtNextAnimationFrame, findParentWithClass, clearNode } from 'vs/base/browser/dom'; import { localize } from 'vs/nls'; import { IEditorGroupsAccessor, IEditorGroupView, EditorServiceImpl, EDITOR_TITLE_HEIGHT } from 'vs/workbench/browser/parts/editor/editor'; -import { CloseOneEditorAction } from 'vs/workbench/browser/parts/editor/editorActions'; +import { CloseOneEditorAction, UnpinEditorAction } from 'vs/workbench/browser/parts/editor/editorActions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { BreadcrumbsControl } from 'vs/workbench/browser/parts/editor/breadcrumbsControl'; import { IFileService } from 'vs/platform/files/common/files'; @@ -45,6 +45,8 @@ import { basenameOrAuthority } from 'vs/base/common/resources'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { IPath, win32, posix } from 'vs/base/common/path'; +import { insert } from 'vs/base/common/arrays'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; // {{SQL CARBON EDIT}} -- Display the editor's tab color import { IQueryEditorConfiguration } from 'sql/platform/query/common/query'; @@ -67,7 +69,8 @@ export class TabsTitleControl extends TitleControl { }; private static readonly TAB_SIZES = { - sticky: 38, + compact: 38, + shrink: 80, fit: 120 }; @@ -77,10 +80,12 @@ export class TabsTitleControl extends TitleControl { private editorToolbarContainer: HTMLElement | undefined; private tabsScrollbar: ScrollableElement | undefined; - private closeOneEditorAction: CloseOneEditorAction; + private readonly closeEditorAction = this._register(this.instantiationService.createInstance(CloseOneEditorAction, CloseOneEditorAction.ID, CloseOneEditorAction.LABEL)); + private readonly unpinEditorAction = this._register(this.instantiationService.createInstance(UnpinEditorAction, UnpinEditorAction.ID, UnpinEditorAction.LABEL)); - private tabResourceLabels: ResourceLabels; + private readonly tabResourceLabels = this._register(this.instantiationService.createInstance(ResourceLabels, DEFAULT_LABELS_CONTAINER)); private tabLabels: IEditorInputLabel[] = []; + private tabActionBars: ActionBar[] = []; private tabDisposables: IDisposable[] = []; private dimension: Dimension | undefined; @@ -111,9 +116,6 @@ export class TabsTitleControl extends TitleControl { ) { super(parent, accessor, group, contextMenuService, instantiationService, contextKeyService, keybindingService, telemetryService, notificationService, menuService, quickInputService, themeService, extensionService, configurationService, fileService); - this.tabResourceLabels = this._register(this.instantiationService.createInstance(ResourceLabels, DEFAULT_LABELS_CONTAINER)); - this.closeOneEditorAction = this._register(this.instantiationService.createInstance(CloseOneEditorAction, CloseOneEditorAction.ID, CloseOneEditorAction.LABEL)); - // Resolve the correct path library for the OS we are on // If we are connected to remote, this accounts for the // remote OS. @@ -429,6 +431,7 @@ export class TabsTitleControl extends TitleControl { this.tabDisposables = dispose(this.tabDisposables); this.tabResourceLabels.clear(); this.tabLabels = []; + this.tabActionBars = []; this.clearEditorActionsToolbar(); } @@ -442,8 +445,8 @@ export class TabsTitleControl extends TitleControl { this.tabLabels.splice(targetIndex, 0, editorLabel); // As such we need to redraw each tab - this.forEachTab((editor, index, tabContainer, tabLabelWidget, tabLabel) => { - this.redrawTab(editor, index, tabContainer, tabLabelWidget, tabLabel); + this.forEachTab((editor, index, tabContainer, tabLabelWidget, tabLabel, tabActionBar) => { + this.redrawTab(editor, index, tabContainer, tabLabelWidget, tabLabel, tabActionBar); }); // Moving an editor requires a layout to keep the active editor visible @@ -465,7 +468,7 @@ export class TabsTitleControl extends TitleControl { private doHandleStickyEditorChange(editor: IEditorInput): void { // Update tab - this.withTab(editor, (editor, index, tabContainer, tabLabelWidget, tabLabel) => this.redrawTab(editor, index, tabContainer, tabLabelWidget, tabLabel)); + this.withTab(editor, (editor, index, tabContainer, tabLabelWidget, tabLabel, tabActionBar) => this.redrawTab(editor, index, tabContainer, tabLabelWidget, tabLabel, tabActionBar)); // A change to the sticky state requires a layout to keep the active editor visible this.layout(this.dimension); @@ -525,6 +528,7 @@ export class TabsTitleControl extends TitleControl { oldOptions.labelFormat !== newOptions.labelFormat || oldOptions.tabCloseButton !== newOptions.tabCloseButton || oldOptions.tabSizing !== newOptions.tabSizing || + oldOptions.pinnedTabSizing !== newOptions.pinnedTabSizing || oldOptions.showIcons !== newOptions.showIcons || oldOptions.hasIcons !== newOptions.hasIcons || oldOptions.highlightModifiedTabs !== newOptions.highlightModifiedTabs @@ -537,23 +541,24 @@ export class TabsTitleControl extends TitleControl { this.redraw(); } - private forEachTab(fn: (editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel) => void): void { + private forEachTab(fn: (editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel, tabActionBar: ActionBar) => void): void { this.group.editors.forEach((editor, index) => { this.doWithTab(index, editor, fn); }); } - private withTab(editor: IEditorInput, fn: (editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel) => void): void { + private withTab(editor: IEditorInput, fn: (editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel, tabActionBar: ActionBar) => void): void { this.doWithTab(this.group.getIndexOfEditor(editor), editor, fn); } - private doWithTab(index: number, editor: IEditorInput, fn: (editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel) => void): void { + private doWithTab(index: number, editor: IEditorInput, fn: (editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel, tabActionBar: ActionBar) => void): void { const tabsContainer = assertIsDefined(this.tabsContainer); const tabContainer = tabsContainer.children[index] as HTMLElement; const tabResourceLabel = this.tabResourceLabels.get(index); const tabLabel = this.tabLabels[index]; + const tabActionBar = this.tabActionBars[index]; if (tabContainer && tabResourceLabel && tabLabel) { - fn(editor, index, tabContainer, tabResourceLabel, tabLabel); + fn(editor, index, tabContainer, tabResourceLabel, tabLabel, tabActionBar); } } @@ -577,26 +582,31 @@ export class TabsTitleControl extends TitleControl { // Tab Editor Label const editorLabel = this.tabResourceLabels.create(tabContainer); - // Tab Close Button - const tabCloseContainer = document.createElement('div'); - addClass(tabCloseContainer, 'tab-close'); - tabContainer.appendChild(tabCloseContainer); + // Tab Actions + const tabActionsContainer = document.createElement('div'); + addClass(tabActionsContainer, 'tab-actions'); + tabContainer.appendChild(tabActionsContainer); + + const tabActionRunner = new EditorCommandsContextActionRunner({ groupId: this.group.id, editorIndex: index }); + + const tabActionBar = new ActionBar(tabActionsContainer, { ariaLabel: localize('ariaLabelTabActions', "Tab actions"), actionRunner: tabActionRunner, }); + tabActionBar.onDidBeforeRun(e => { + if (e.action.id === this.closeEditorAction.id) { + this.blockRevealActiveTabOnce(); + } + }); + + const tabActionBarDisposable = combinedDisposable(tabActionBar, toDisposable(insert(this.tabActionBars, tabActionBar))); // Tab Border Bottom const tabBorderBottomContainer = document.createElement('div'); addClass(tabBorderBottomContainer, 'tab-border-bottom-container'); tabContainer.appendChild(tabBorderBottomContainer); - const tabActionRunner = new EditorCommandsContextActionRunner({ groupId: this.group.id, editorIndex: index }); - - const tabActionBar = new ActionBar(tabCloseContainer, { ariaLabel: localize('araLabelTabActions', "Tab actions"), actionRunner: tabActionRunner }); - tabActionBar.push(this.closeOneEditorAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(this.closeOneEditorAction) }); - tabActionBar.onDidBeforeRun(() => this.blockRevealActiveTabOnce()); - // Eventing const eventsDisposable = this.registerTabListeners(tabContainer, index, tabsContainer, tabsScrollbar); - this.tabDisposables.push(combinedDisposable(eventsDisposable, tabActionBar, tabActionRunner, editorLabel)); + this.tabDisposables.push(combinedDisposable(eventsDisposable, tabActionBarDisposable, tabActionRunner, editorLabel)); return tabContainer; } @@ -659,7 +669,7 @@ export class TabsTitleControl extends TitleControl { EventHelper.stop(e, true /* for https://github.com/Microsoft/vscode/issues/56715 */); this.blockRevealActiveTabOnce(); - this.closeOneEditorAction.run({ groupId: this.group.id, editorIndex: index }); + this.closeEditorAction.run({ groupId: this.group.id, editorIndex: index }); } })); @@ -1011,8 +1021,8 @@ export class TabsTitleControl extends TitleControl { } // For each tab - this.forEachTab((editor, index, tabContainer, tabLabelWidget, tabLabel) => { - this.redrawTab(editor, index, tabContainer, tabLabelWidget, tabLabel); + this.forEachTab((editor, index, tabContainer, tabLabelWidget, tabLabel, tabActionBar) => { + this.redrawTab(editor, index, tabContainer, tabLabelWidget, tabLabel, tabActionBar); }); // Update Editor Actions Toolbar @@ -1022,28 +1032,38 @@ export class TabsTitleControl extends TitleControl { this.layout(this.dimension); } - private redrawTab(editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel): void { + private redrawTab(editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel, tabActionBar: ActionBar): void { + const isTabSticky = this.group.isSticky(index); + const isTabLastSticky = isTabSticky && this.group.stickyCount === index + 1; + const options = this.accessor.partOptions; // Label this.redrawLabel(editor, index, tabContainer, tabLabelWidget, tabLabel); + // Action + const tabAction = isTabSticky ? this.unpinEditorAction : this.closeEditorAction; + if (!tabActionBar.hasAction(tabAction)) { + if (!tabActionBar.isEmpty()) { + tabActionBar.clear(); + } + tabActionBar.push(tabAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(tabAction) }); + } + // Borders / Outline - const borderRightColor = (this.getColor(TAB_BORDER) || this.getColor(contrastBorder)); + const borderRightColor = ((isTabLastSticky ? this.getColor(TAB_LAST_PINNED_BORDER) : undefined) || this.getColor(TAB_BORDER) || this.getColor(contrastBorder)); tabContainer.style.borderRight = borderRightColor ? `1px solid ${borderRightColor}` : ''; tabContainer.style.outlineColor = this.getColor(activeContrastBorder) || ''; // Settings - const isTabSticky = this.group.isSticky(index); - const options = this.accessor.partOptions; - - const tabCloseButton = isTabSticky ? 'off' /* treat sticky tabs as tabCloseButton: 'off' */ : options.tabCloseButton; + const tabActionsVisibility = isTabSticky && options.pinnedTabSizing === 'compact' ? 'off' /* treat sticky compact tabs as tabCloseButton: 'off' */ : options.tabCloseButton; ['off', 'left', 'right'].forEach(option => { - const domAction = tabCloseButton === option ? addClass : removeClass; - domAction(tabContainer, `close-button-${option}`); + const domAction = tabActionsVisibility === option ? addClass : removeClass; + domAction(tabContainer, `tab-actions-${option}`); }); + const tabSizing = isTabSticky && options.pinnedTabSizing === 'shrink' ? 'shrink' /* treat sticky shrink tabs as tabSizing: 'shrink' */ : options.tabSizing; ['fit', 'shrink'].forEach(option => { - const domAction = options.tabSizing === option ? addClass : removeClass; + const domAction = tabSizing === option ? addClass : removeClass; domAction(tabContainer, `sizing-${option}`); }); @@ -1053,13 +1073,26 @@ export class TabsTitleControl extends TitleControl { removeClass(tabContainer, 'has-icon'); } - // Sticky Tabs need a position to remain at their location + ['compact', 'shrink', 'normal'].forEach(option => { + const domAction = isTabSticky && options.pinnedTabSizing === option ? addClass : removeClass; + domAction(tabContainer, `sticky-${option}`); + }); + + // Sticky compact/shrink tabs need a position to remain at their location // when scrolling to stay in view (requirement for position: sticky) - if (isTabSticky) { - addClass(tabContainer, 'sticky'); - tabContainer.style.left = `${index * TabsTitleControl.TAB_SIZES.sticky}px`; + if (isTabSticky && options.pinnedTabSizing !== 'normal') { + let stickyTabWidth = 0; + switch (options.pinnedTabSizing) { + case 'compact': + stickyTabWidth = TabsTitleControl.TAB_SIZES.compact; + break; + case 'shrink': + stickyTabWidth = TabsTitleControl.TAB_SIZES.shrink; + break; + } + + tabContainer.style.left = `${index * stickyTabWidth}px`; } else { - removeClass(tabContainer, 'sticky'); tabContainer.style.left = 'auto'; } @@ -1068,17 +1101,19 @@ export class TabsTitleControl extends TitleControl { } private redrawLabel(editor: IEditorInput, index: number, tabContainer: HTMLElement, tabLabelWidget: IResourceLabel, tabLabel: IEditorInputLabel): void { - const isTabSticky = this.group.isSticky(index); + const options = this.accessor.partOptions; - // Unless tabs are sticky, show the full label and description - // Sticky tabs will only show an icon if icons are enabled + // Unless tabs are sticky compact, show the full label and description + // Sticky compact tabs will only show an icon if icons are enabled // or their first character of the name otherwise let name: string | undefined; + let forceLabel = false; let description: string; - if (isTabSticky) { - const isShowingIcons = this.accessor.partOptions.showIcons && this.accessor.partOptions.hasIcons; + if (options.pinnedTabSizing === 'compact' && this.group.isSticky(index)) { + const isShowingIcons = options.showIcons && options.hasIcons; name = isShowingIcons ? '' : tabLabel.name?.charAt(0).toUpperCase(); description = ''; + forceLabel = true; } else { name = tabLabel.name; description = tabLabel.description || ''; @@ -1097,7 +1132,7 @@ export class TabsTitleControl extends TitleControl { // Label tabLabelWidget.setResource( { name, description, resource: toResource(editor, { supportSideBySide: SideBySideEditor.BOTH }) }, - { title, extraClasses: ['tab-label'], italic: !this.group.isPinned(editor), forceLabel: isTabSticky } + { title, extraClasses: ['tab-label'], italic: !this.group.isPinned(editor), forceLabel } ); this.setEditorTabColor(editor, tabContainer, this.group.isActive(editor)); // {{SQL CARBON EDIT}} -- Display the editor's tab color @@ -1265,7 +1300,7 @@ export class TabsTitleControl extends TitleControl { // // Synopsis // - allTabsWidth: sum of all tab widths - // - stickyTabsWidth: sum of all sticky tab widths + // - stickyTabsWidth: sum of all sticky tab widths (unless `pinnedTabSizing: normal`) // - visibleContainerWidth: size of tab container // - availableContainerWidth: size of tab container minus size of sticky tabs // @@ -1281,7 +1316,24 @@ export class TabsTitleControl extends TitleControl { const visibleTabsContainerWidth = tabsContainer.offsetWidth; const allTabsWidth = tabsContainer.scrollWidth; - let stickyTabsWidth = this.group.stickyCount * TabsTitleControl.TAB_SIZES.sticky; + // Compute width of sticky tabs depending on pinned tab sizing + // - compact: sticky-tabs * TAB_SIZES.compact + // - shrink: sticky-tabs * TAB_SIZES.shrink + // - normal: 0 (sticky tabs inherit look and feel from non-sticky tabs) + let stickyTabsWidth = 0; + if (this.group.stickyCount > 0) { + let stickyTabWidth = 0; + switch (this.accessor.partOptions.pinnedTabSizing) { + case 'compact': + stickyTabWidth = TabsTitleControl.TAB_SIZES.compact; + break; + case 'shrink': + stickyTabWidth = TabsTitleControl.TAB_SIZES.shrink; + break; + } + + stickyTabsWidth = this.group.stickyCount * stickyTabWidth; + } let activeTabSticky = this.group.isSticky(activeIndex); let availableTabsContainerWidth = visibleTabsContainerWidth - stickyTabsWidth; @@ -1475,7 +1527,7 @@ export class TabsTitleControl extends TitleControl { let sqlEditor = editor as any; const tabColorMode = this.configurationService.getValue('queryEditor').tabColorMode; if (tabColorMode === 'off' || (tabColorMode !== 'border' && tabColorMode !== 'fill') - || this.themeService.getColorTheme().type === HIGH_CONTRAST || !sqlEditor.tabColor) { + || this.themeService.getColorTheme().type === ColorScheme.HIGH_CONTRAST || !sqlEditor.tabColor) { tabContainer.style.borderTopColor = ''; tabContainer.style.borderTopWidth = ''; tabContainer.style.borderTopStyle = ''; @@ -1502,7 +1554,7 @@ export class TabsTitleControl extends TitleControl { registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { // Add border between tabs and breadcrumbs in high contrast mode. - if (theme.type === HIGH_CONTRAST) { + if (theme.type === ColorScheme.HIGH_CONTRAST) { const borderColor = (theme.getColor(TAB_BORDER) || theme.getColor(contrastBorder)); collector.addRule(` .monaco-workbench .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container { @@ -1526,10 +1578,10 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) = outline-offset: -5px; } - .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active > .tab-close .action-label, - .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active:hover > .tab-close .action-label, - .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty > .tab-close .action-label, - .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab:hover > .tab-close .action-label { + .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active > .tab-actions .action-label, + .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active:hover > .tab-actions .action-label, + .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty > .tab-actions .action-label, + .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab:hover > .tab-actions .action-label { opacity: 1 !important; } `); @@ -1621,11 +1673,11 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) = // Adjust gradient for focused and unfocused hover background const makeTabHoverBackgroundRule = (color: Color, colorDrag: Color, hasFocus = false) => ` - .monaco-workbench .part.editor > .content:not(.dragged-over) .editor-group-container${hasFocus ? '.active' : ''} > .title .tabs-container > .tab.sizing-shrink:not(.dragged):not(.sticky):hover > .tab-label::after { + .monaco-workbench .part.editor > .content:not(.dragged-over) .editor-group-container${hasFocus ? '.active' : ''} > .title .tabs-container > .tab.sizing-shrink:not(.dragged):not(.sticky-compact):hover > .tab-label::after { background: linear-gradient(to left, ${color}, transparent) !important; } - .monaco-workbench .part.editor > .content.dragged-over .editor-group-container${hasFocus ? '.active' : ''} > .title .tabs-container > .tab.sizing-shrink:not(.dragged):not(.sticky):hover > .tab-label::after { + .monaco-workbench .part.editor > .content.dragged-over .editor-group-container${hasFocus ? '.active' : ''} > .title .tabs-container > .tab.sizing-shrink:not(.dragged):not(.sticky-compact):hover > .tab-label::after { background: linear-gradient(to left, ${colorDrag}, transparent) !important; } `; @@ -1648,19 +1700,19 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) = if (editorDragAndDropBackground && adjustedTabDragBackground) { const adjustedColorDrag = editorDragAndDropBackground.flatten(adjustedTabDragBackground); collector.addRule(` - .monaco-workbench .part.editor > .content.dragged-over .editor-group-container.active > .title .tabs-container > .tab.sizing-shrink.dragged-over:not(.active):not(.dragged):not(.sticky) > .tab-label::after, - .monaco-workbench .part.editor > .content.dragged-over .editor-group-container:not(.active) > .title .tabs-container > .tab.sizing-shrink.dragged-over:not(.dragged):not(.sticky) > .tab-label::after { + .monaco-workbench .part.editor > .content.dragged-over .editor-group-container.active > .title .tabs-container > .tab.sizing-shrink.dragged-over:not(.active):not(.dragged):not(.sticky-compact) > .tab-label::after, + .monaco-workbench .part.editor > .content.dragged-over .editor-group-container:not(.active) > .title .tabs-container > .tab.sizing-shrink.dragged-over:not(.dragged):not(.sticky-compact) > .tab-label::after { background: linear-gradient(to left, ${adjustedColorDrag}, transparent) !important; } `); } const makeTabBackgroundRule = (color: Color, colorDrag: Color, focused: boolean, active: boolean) => ` - .monaco-workbench .part.editor > .content:not(.dragged-over) .editor-group-container${focused ? '.active' : ':not(.active)'} > .title .tabs-container > .tab.sizing-shrink${active ? '.active' : ''}:not(.dragged):not(.sticky) > .tab-label::after { + .monaco-workbench .part.editor > .content:not(.dragged-over) .editor-group-container${focused ? '.active' : ':not(.active)'} > .title .tabs-container > .tab.sizing-shrink${active ? '.active' : ''}:not(.dragged):not(.sticky-compact) > .tab-label::after { background: linear-gradient(to left, ${color}, transparent); } - .monaco-workbench .part.editor > .content.dragged-over .editor-group-container${focused ? '.active' : ':not(.active)'} > .title .tabs-container > .tab.sizing-shrink${active ? '.active' : ''}:not(.dragged):not(.sticky) > .tab-label::after { + .monaco-workbench .part.editor > .content.dragged-over .editor-group-container${focused ? '.active' : ':not(.active)'} > .title .tabs-container > .tab.sizing-shrink${active ? '.active' : ''}:not(.dragged):not(.sticky-compact) > .tab-label::after { background: linear-gradient(to left, ${colorDrag}, transparent); } `; diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index bb6653f143..f6e84e1daa 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -32,7 +32,7 @@ import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs'; import { BreadcrumbsControl, IBreadcrumbsControlOptions } from 'vs/workbench/browser/parts/editor/breadcrumbsControl'; import { IEditorGroupsAccessor, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; -import { EditorCommandsContextActionRunner, IEditorCommandsContext, IEditorInput, toResource, IEditorPartOptions, SideBySideEditor, EditorPinnedContext, EditorStickyContext } from 'vs/workbench/common/editor'; +import { EditorCommandsContextActionRunner, IEditorCommandsContext, IEditorInput, toResource, IEditorPartOptions, SideBySideEditor, ActiveEditorPinnedContext, ActiveEditorStickyContext } from 'vs/workbench/common/editor'; import { ResourceContextKey } from 'vs/workbench/common/resources'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; @@ -87,8 +87,8 @@ export abstract class TitleControl extends Themable { super(themeService); this.resourceContext = this._register(instantiationService.createInstance(ResourceContextKey)); - this.editorPinnedContext = EditorPinnedContext.bindTo(contextKeyService); - this.editorStickyContext = EditorStickyContext.bindTo(contextKeyService); + this.editorPinnedContext = ActiveEditorPinnedContext.bindTo(contextKeyService); + this.editorStickyContext = ActiveEditorStickyContext.bindTo(contextKeyService); this.contextMenu = this._register(this.menuService.createMenu(MenuId.EditorTitleContext, this.contextKeyService)); @@ -138,7 +138,7 @@ export abstract class TitleControl extends Themable { this.editorActionsToolbar = this._register(new ToolBar(container, this.contextMenuService, { actionViewItemProvider: action => this.actionViewItemProvider(action), orientation: ActionsOrientation.HORIZONTAL, - ariaLabel: localize('araLabelEditorActions', "Editor actions"), + ariaLabel: localize('ariaLabelEditorActions', "Editor actions"), getKeyBinding: action => this.getKeybinding(action), actionRunner: this._register(new EditorCommandsContextActionRunner(context)), anchorAlignmentProvider: () => AnchorAlignment.RIGHT diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index d117256a3a..6121752b56 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -26,7 +26,7 @@ import { CompositeBar, ICompositeBarItem, CompositeDragAndDrop } from 'vs/workbe import { ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositeBarActions'; import { IBadge } from 'vs/workbench/services/activity/common/activity'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { Dimension, trackFocus, addClass, toggleClass, EventHelper } from 'vs/base/browser/dom'; +import { Dimension, trackFocus, EventHelper } from 'vs/base/browser/dom'; import { localize } from 'vs/nls'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -422,10 +422,10 @@ export class PanelPart extends CompositePart implements IPanelService { private createEmptyPanelMessage(): void { const contentArea = this.getContentArea()!; this.emptyPanelMessageElement = document.createElement('div'); - addClass(this.emptyPanelMessageElement, 'empty-panel-message-area'); + this.emptyPanelMessageElement.classList.add('empty-panel-message-area'); const messageElement = document.createElement('div'); - addClass(messageElement, 'empty-panel-message'); + messageElement.classList.add('empty-panel-message'); messageElement.innerText = localize('panel.emptyMessage', "Drag a view into the panel to display."); this.emptyPanelMessageElement.appendChild(messageElement); @@ -613,7 +613,7 @@ export class PanelPart extends CompositePart implements IPanelService { private emptyPanelMessageElement: HTMLElement | undefined; private layoutEmptyMessage(): void { if (this.emptyPanelMessageElement) { - toggleClass(this.emptyPanelMessageElement, 'visible', this.compositeBar.getVisibleComposites().length === 0); + this.emptyPanelMessageElement.classList.toggle('visible', this.compositeBar.getVisibleComposites().length === 0); } } diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 7b5d15affc..3980ac98fc 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -15,7 +15,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { StatusbarAlignment, IStatusbarService, IStatusbarEntry, IStatusbarEntryAccessor } from 'vs/workbench/services/statusbar/common/statusbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Action, IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification, Separator } from 'vs/base/common/actions'; -import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector, ThemeColor, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector, ThemeColor } from 'vs/platform/theme/common/themeService'; import { STATUS_BAR_BACKGROUND, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_ITEM_HOVER_BACKGROUND, STATUS_BAR_ITEM_ACTIVE_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_FOREGROUND, STATUS_BAR_PROMINENT_ITEM_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_HOVER_BACKGROUND, STATUS_BAR_BORDER, STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_NO_FOLDER_BORDER } from 'vs/workbench/common/theme'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { contrastBorder, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; @@ -38,6 +38,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; interface IPendingStatusbarEntry { id: string; @@ -672,7 +673,7 @@ export class StatusbarPart extends Part implements IStatusbarService { this.styleElement = createStyleSheet(container); } - this.styleElement.innerHTML = `.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-beak:before { border-bottom-color: ${backgroundColor}; }`; + this.styleElement.textContent = `.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-beak:before { border-bottom-color: ${backgroundColor}; }`; } private doCreateStatusItem(id: string, alignment: StatusbarAlignment, ...extraClasses: string[]): HTMLElement { @@ -889,7 +890,7 @@ class StatusbarEntryItem extends Disposable { } registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { - if (theme.type !== HIGH_CONTRAST) { + if (theme.type !== ColorScheme.HIGH_CONTRAST) { const statusBarItemHoverBackground = theme.getColor(STATUS_BAR_ITEM_HOVER_BACKGROUND); if (statusBarItemHoverBackground) { collector.addRule(`.monaco-workbench .part.statusbar > .items-container > .statusbar-item a:hover { background-color: ${statusBarItemHoverBackground}; }`); diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index d202b9f1ed..f9b8952379 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -39,7 +39,7 @@ import { IMenuService, IMenu, MenuId } from 'vs/platform/actions/common/actions' import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IProductService } from 'vs/platform/product/common/productService'; -import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; +import { Schemas } from 'vs/base/common/network'; export class TitlebarPart extends Part implements ITitleService { @@ -290,7 +290,7 @@ export class TitlebarPart extends Part implements ITitleService { const folderPath = folder ? this.labelService.getUriLabel(folder.uri) : ''; const dirty = editor?.isDirty() && !editor.isSaving() ? TitlebarPart.TITLE_DIRTY : ''; const appName = this.productService.nameLong; - const remoteName = this.labelService.getHostLabel(REMOTE_HOST_SCHEME, this.environmentService.configuration.remoteAuthority); + const remoteName = this.labelService.getHostLabel(Schemas.vscodeRemote, this.environmentService.configuration.remoteAuthority); const separator = this.configurationService.getValue('window.titleSeparator'); const titleTemplate = this.configurationService.getValue('window.title'); diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 9d3d41b23f..2697708e0c 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -9,9 +9,8 @@ import { Event, Emitter } from 'vs/base/common/event'; import { ColorIdentifier, activeContrastBorder, foreground } from 'vs/platform/theme/common/colorRegistry'; import { attachStyler, IColorMapping, attachButtonStyler, attachLinkStyler, attachProgressBarStyler } from 'vs/platform/theme/common/styler'; import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_SECTION_HEADER_BORDER, PANEL_BACKGROUND, SIDE_BAR_BACKGROUND, PANEL_SECTION_HEADER_FOREGROUND, PANEL_SECTION_HEADER_BACKGROUND, PANEL_SECTION_HEADER_BORDER, PANEL_SECTION_DRAG_AND_DROP_BACKGROUND, PANEL_SECTION_BORDER } from 'vs/workbench/common/theme'; -import { after, append, $, trackFocus, toggleClass, EventType, isAncestor, Dimension, addDisposableListener, removeClass, addClass, createCSSRule, asCSSUrl, addClasses } from 'vs/base/browser/dom'; +import { after, append, $, trackFocus, EventType, isAncestor, Dimension, addDisposableListener, createCSSRule, asCSSUrl, addClasses } from 'vs/base/browser/dom'; import { IDisposable, combinedDisposable, dispose, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { firstIndex } from 'vs/base/common/arrays'; import { IAction, Separator, IActionViewItem } from 'vs/base/common/actions'; import { ActionsOrientation, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -238,7 +237,7 @@ export abstract class ViewPane extends Pane implements IView { set headerVisible(visible: boolean) { super.headerVisible = visible; - toggleClass(this.element, 'merged-header', !visible); + this.element.classList.toggle('merged-header', !visible); } setVisible(visible: boolean): void { @@ -294,7 +293,7 @@ export abstract class ViewPane extends Pane implements IView { this.renderHeaderTitle(container, this.title); const actions = append(container, $('.actions')); - toggleClass(actions, 'show', this.showActionsAlways); + actions.classList.toggle('show', this.showActionsAlways); this.toolbar = new ToolBar(actions, this.contextMenuService, { orientation: ActionsOrientation.HORIZONTAL, actionViewItemProvider: action => this.getActionViewItem(action), @@ -357,7 +356,7 @@ export abstract class ViewPane extends Pane implements IView { -webkit-mask-size: 16px; `); } else if (isString(icon)) { - addClass(this.iconContainer, 'codicon'); + this.iconContainer.classList.add('codicon'); cssClass = icon; } @@ -366,7 +365,7 @@ export abstract class ViewPane extends Pane implements IView { } const calculatedTitle = this.calculateTitle(title); - this.titleContainer = append(container, $('h3.title', undefined, calculatedTitle)); + this.titleContainer = append(container, $('h3.title', { title: calculatedTitle }, calculatedTitle)); if (this._titleDescription) { this.setTitleDescription(this._titleDescription); @@ -380,6 +379,7 @@ export abstract class ViewPane extends Pane implements IView { const calculatedTitle = this.calculateTitle(title); if (this.titleContainer) { this.titleContainer.textContent = calculatedTitle; + this.titleContainer.setAttribute('title', calculatedTitle); } if (this.iconContainer) { @@ -394,9 +394,10 @@ export abstract class ViewPane extends Pane implements IView { private setTitleDescription(description: string | undefined) { if (this.titleDescriptionContainer) { this.titleDescriptionContainer.textContent = description ?? ''; + this.titleDescriptionContainer.setAttribute('title', description ?? ''); } else if (description && this.titleContainer) { - this.titleDescriptionContainer = after(this.titleContainer, $('span.description', undefined, description)); + this.titleDescriptionContainer = after(this.titleContainer, $('span.description', { title: description }, description)); } } @@ -489,7 +490,7 @@ export abstract class ViewPane extends Pane implements IView { return; } const shouldAlwaysShowActions = this.configurationService.getValue('workbench.view.alwaysShowHeaderActions'); - toggleClass(this.headerContainer, 'actions-always-visible', shouldAlwaysShowActions); + this.headerContainer.classList.toggle('actions-always-visible', shouldAlwaysShowActions); } protected updateActions(): void { @@ -534,7 +535,7 @@ export abstract class ViewPane extends Pane implements IView { this.viewWelcomeDisposable.dispose(); if (!this.shouldShowWelcome()) { - removeClass(this.bodyContainer, 'welcome'); + this.bodyContainer.classList.remove('welcome'); this.viewWelcomeContainer.innerText = ''; this.scrollableElement.scanDomNode(); return; @@ -543,14 +544,14 @@ export abstract class ViewPane extends Pane implements IView { const contents = this.viewWelcomeController.contents; if (contents.length === 0) { - removeClass(this.bodyContainer, 'welcome'); + this.bodyContainer.classList.remove('welcome'); this.viewWelcomeContainer.innerText = ''; this.scrollableElement.scanDomNode(); return; } const disposables = new DisposableStore(); - addClass(this.bodyContainer, 'welcome'); + this.bodyContainer.classList.add('welcome'); this.viewWelcomeContainer.innerText = ''; let buttonIndex = 0; @@ -680,15 +681,15 @@ class ViewPaneDropOverlay extends Themable { // Parent this.paneElement.appendChild(this.container); - addClass(this.paneElement, 'dragged-over'); + this.paneElement.classList.add('dragged-over'); this._register(toDisposable(() => { this.paneElement.removeChild(this.container); - removeClass(this.paneElement, 'dragged-over'); + this.paneElement.classList.remove('dragged-over'); })); // Overlay this.overlay = document.createElement('div'); - addClass(this.overlay, 'pane-overlay-indicator'); + this.overlay.classList.add('pane-overlay-indicator'); this.container.appendChild(this.overlay); // Overlay Event Handling @@ -819,7 +820,7 @@ class ViewPaneDropOverlay extends Themable { this.overlay.style.opacity = '1'; // Enable transition after a timeout to prevent initial animation - setTimeout(() => addClass(this.overlay, 'overlay-move-transition'), 0); + setTimeout(() => this.overlay.classList.add('overlay-move-transition'), 0); // Remember as current split direction this._currentDropOperation = dropDirection; @@ -1581,7 +1582,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } private removePane(pane: ViewPane): void { - const index = firstIndex(this.paneItems, i => i.pane === pane); + const index = this.paneItems.findIndex(i => i.pane === pane); if (index === -1) { return; @@ -1598,8 +1599,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } movePane(from: ViewPane, to: ViewPane): void { - const fromIndex = firstIndex(this.paneItems, item => item.pane === from); - const toIndex = firstIndex(this.paneItems, item => item.pane === to); + const fromIndex = this.paneItems.findIndex(item => item.pane === from); + const toIndex = this.paneItems.findIndex(item => item.pane === to); const fromViewDescriptor = this.viewContainerModel.visibleViewDescriptors[fromIndex]; const toViewDescriptor = this.viewContainerModel.visibleViewDescriptors[toIndex]; diff --git a/src/vs/workbench/browser/style.ts b/src/vs/workbench/browser/style.ts index de804791bd..90576a33b4 100644 --- a/src/vs/workbench/browser/style.ts +++ b/src/vs/workbench/browser/style.ts @@ -5,12 +5,13 @@ import 'vs/css!./media/style'; -import { registerThemingParticipant, IColorTheme, ICssStyleCollector, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { iconForeground, foreground, selectionBackground, focusBorder, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listHighlightForeground, inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry'; import { WORKBENCH_BACKGROUND, TITLE_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme'; import { isWeb, isIOS, isMacintosh, isWindows } from 'vs/base/common/platform'; import { createMetaElement } from 'vs/base/browser/dom'; import { isSafari, isStandalone } from 'vs/base/browser/browser'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { @@ -127,7 +128,7 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) = } // High Contrast theme overwrites for outline - if (theme.type === HIGH_CONTRAST) { + if (theme.type === ColorScheme.HIGH_CONTRAST) { collector.addRule(` .hc-black [tabindex="0"]:focus, .hc-black [tabindex="-1"]:focus, diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index ee61d479c8..b9668d57b0 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { mark } from 'vs/base/common/performance'; -import { domContentLoaded, addDisposableListener, EventType, EventHelper } from 'vs/base/browser/dom'; +import { domContentLoaded, addDisposableListener, EventType, EventHelper, detectFullscreen, addDisposableThrottledListener } from 'vs/base/browser/dom'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { ILogService, ConsoleLogService, MultiplexLogService } from 'vs/platform/log/common/log'; import { ConsoleLogInAutomationService } from 'vs/platform/log/browser/log'; @@ -25,8 +25,8 @@ import { Schemas } from 'vs/base/common/network'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { onUnexpectedError } from 'vs/base/common/errors'; -import * as browser from 'vs/base/browser/browser'; -import * as platform from 'vs/base/common/platform'; +import { setFullscreen } from 'vs/base/browser/browser'; +import { isIOS, isMacintosh } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; @@ -34,9 +34,6 @@ import { ConfigurationCache } from 'vs/workbench/services/configuration/browser/ import { ISignService } from 'vs/platform/sign/common/sign'; import { SignService } from 'vs/platform/sign/browser/signService'; import { IWorkbenchConstructionOptions, IWorkspace, IWorkbench } from 'vs/workbench/workbench.web.api'; -import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; -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 } from 'vs/platform/storage/common/storage'; import { registerWindowDriver } from 'vs/platform/driver/browser/driver'; @@ -61,6 +58,14 @@ class BrowserMain extends Disposable { private readonly configuration: IWorkbenchConstructionOptions ) { super(); + + this.init(); + } + + private init(): void { + + // Browser config + setFullscreen(!!detectFullscreen()); } async open(): Promise { @@ -102,7 +107,7 @@ class BrowserMain extends Disposable { private registerListeners(workbench: Workbench, storageService: BrowserStorageService): void { // Layout - const viewport = platform.isIOS && (window).visualViewport ? (window).visualViewport /** Visual viewport */ : window /** Layout viewport */; + const viewport = isIOS && window.visualViewport ? window.visualViewport /** Visual viewport */ : window /** Layout viewport */; this._register(addDisposableListener(viewport, EventType.RESIZE, () => workbench.layout())); // Prevent the back/forward gestures in macOS @@ -126,16 +131,15 @@ class BrowserMain extends Disposable { })); this._register(workbench.onShutdown(() => this.dispose())); - // Fullscreen + // Fullscreen (Browser) [EventType.FULLSCREEN_CHANGE, EventType.WK_FULLSCREEN_CHANGE].forEach(event => { - this._register(addDisposableListener(document, event, () => { - if (document.fullscreenElement || (document).webkitFullscreenElement || (document).webkitIsFullScreen) { - browser.setFullscreen(true); - } else { - browser.setFullscreen(false); - } - })); + this._register(addDisposableListener(document, event, () => setFullscreen(!!detectFullscreen()))); }); + + // Fullscreen (Native) + this._register(addDisposableThrottledListener(viewport, EventType.RESIZE, () => { + setFullscreen(!!detectFullscreen()); + }, undefined, isMacintosh ? 2000 /* adjust for macOS animation */ : 800 /* can be throttled */)); } private async initServices(): Promise<{ serviceCollection: ServiceCollection, logService: ILogService, storageService: BrowserStorageService }> { @@ -213,11 +217,14 @@ class BrowserMain extends Disposable { serviceCollection.set(IUserDataInitializationService, userDataInitializationService); if (await userDataInitializationService.requiresInitialization()) { + mark('willInitRequiredUserData'); // Initialize required resources - settings & global state await userDataInitializationService.initializeRequiredResources(); - // Reload configuration after initializing - await configurationService.reloadConfiguration(); + // Important Reload only local user configuration after initializing + // Reloading complete configuraiton blocks workbench until remote configuration is loaded. + await configurationService.reloadLocalUserConfiguration(); + mark('didInitRequiredUserData'); } return { serviceCollection, logService, storageService }; @@ -250,17 +257,9 @@ class BrowserMain extends Disposable { const connection = remoteAgentService.getConnection(); if (connection) { - // Remote file system const remoteFileSystemProvider = this._register(new RemoteFileSystemProvider(remoteAgentService)); fileService.registerProvider(Schemas.vscodeRemote, remoteFileSystemProvider); - - if (!this.configuration.userDataProvider) { - const remoteUserDataUri = this.getRemoteUserDataUri(); - if (remoteUserDataUri) { - this.configuration.userDataProvider = this._register(new FileUserDataProvider(remoteUserDataUri, joinPath(remoteUserDataUri, BACKUPS), remoteFileSystemProvider, environmentService, logService)); - } - } } // User data @@ -331,18 +330,6 @@ class BrowserMain extends Disposable { return { id: 'empty-window' }; } - private getRemoteUserDataUri(): URI | undefined { - const element = document.getElementById('vscode-remote-user-data-uri'); - if (element) { - const remoteUserDataPath = element.getAttribute('data-settings'); - if (remoteUserDataPath) { - return joinPath(URI.revive(JSON.parse(remoteUserDataPath)), 'User'); - } - } - - return undefined; - } - private getCookieValue(name: string): string | undefined { const match = document.cookie.match('(^|[^;]+)\\s*' + name + '\\s*=\\s*([^;]+)'); // See https://stackoverflow.com/a/25490531 diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index fe64f60c57..373a3f057e 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -34,12 +34,12 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio }, 'workbench.editor.scrollToSwitchTabs': { '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 whether 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."), + 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'scrollToSwitchTabs' }, "Controls whether 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. This value is ignored when `#workbench.editor.showTabs#` is `false`."), 'default': false }, 'workbench.editor.highlightModifiedTabs': { 'type': 'boolean', - 'description': nls.localize('highlightModifiedTabs', "Controls whether a top border is drawn on modified (dirty) editor tabs or not."), + 'markdownDescription': nls.localize('highlightModifiedTabs', "Controls whether a top border is drawn on modified (dirty) editor tabs or not. This value is ignored when `#workbench.editor.showTabs#` is `false`."), 'default': false }, 'workbench.editor.labelFormat': { @@ -74,7 +74,7 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio 'type': 'string', 'enum': ['left', 'right', 'off'], 'default': 'right', - 'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'editorTabCloseButton' }, "Controls the position of the editor's tabs close buttons, or disables them when set to 'off'.") + 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'editorTabCloseButton' }, "Controls the position of the editor's tabs close buttons, or disables them when set to 'off'. This value is ignored when `#workbench.editor.showTabs#` is `false`.") }, 'workbench.editor.tabSizing': { 'type': 'string', @@ -84,7 +84,18 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio nls.localize('workbench.editor.tabSizing.fit', "Always keep tabs large enough to show the full editor label."), nls.localize('workbench.editor.tabSizing.shrink', "Allow tabs to get smaller when the available space is not enough to show all tabs at once.") ], - 'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'tabSizing' }, "Controls the sizing of editor tabs.") + 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'tabSizing' }, "Controls the sizing of editor tabs. This value is ignored when `#workbench.editor.showTabs#` is `false`.") + }, + 'workbench.editor.pinnedTabSizing': { + 'type': 'string', + 'enum': ['compact', 'shrink', 'normal'], + 'default': 'shrink', + 'enumDescriptions': [ + nls.localize('workbench.editor.pinnedTabSizing.compact', "A pinned tab will show in a compact form with only icon or first letter of the editor name."), + nls.localize('workbench.editor.pinnedTabSizing.shrink', "A pinned tab shrinks to a compact fixed size showing parts of the editor name."), + nls.localize('workbench.editor.pinnedTabSizing.normal', "A pinned tab inherits the look of non pinned tabs.") + ], + 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'pinnedTabSizing' }, "Controls the sizing of pinned editor tabs. Pinned tabs are sorted to the begining of all opened tabs and typically do not close until unpinned. This value is ignored when `#workbench.editor.showTabs#` is `false`.") }, 'workbench.editor.splitSizing': { 'type': 'string', @@ -330,7 +341,7 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio nls.localize('window.menuBarVisibility.visible', "Menu is always visible even in full screen mode."), nls.localize('window.menuBarVisibility.toggle', "Menu is hidden but can be displayed via Alt key."), nls.localize('window.menuBarVisibility.hidden', "Menu is always hidden."), - nls.localize('window.menuBarVisibility.compact', "Menu is displayed as a compact button in the sidebar. This value is ignored when 'window.titleBarStyle' is 'native'.") + nls.localize('window.menuBarVisibility.compact', "Menu is displayed as a compact button in the sidebar. This value is ignored when `#window.titleBarStyle#` is `native`.") ], 'default': isWeb ? 'compact' : 'default', 'scope': ConfigurationScope.APPLICATION, diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 22e60ecebe..341bbbac18 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -24,23 +24,34 @@ import { IResourceEditorInputType } from 'vs/workbench/services/editor/common/ed import { IRange } from 'vs/editor/common/core/range'; import { IExtUri } from 'vs/base/common/resources'; -export const DirtyWorkingCopiesContext = new RawContextKey('dirtyWorkingCopies', false); +// Editor State Context Keys +export const ActiveEditorDirtyContext = new RawContextKey('activeEditorIsDirty', false); +export const ActiveEditorPinnedContext = new RawContextKey('activeEditorIsNotPreview', false); +export const ActiveEditorStickyContext = new RawContextKey('activeEditorIsPinned', false); +export const ActiveEditorReadonlyContext = new RawContextKey('activeEditorIsReadonly', false); + +/** TODO@ben remove me eventually */ +/** @deprecated */ +export const Deprecated_EditorPinnedContext = new RawContextKey('editorPinned', false); +/** @deprecated */ +export const Deprecated_EditorDirtyContext = new RawContextKey('groupActiveEditorDirty', false); + +// Editor Kind Context Keys export const ActiveEditorContext = new RawContextKey('activeEditor', null); -export const ActiveEditorIsReadonlyContext = new RawContextKey('activeEditorIsReadonly', false); export const ActiveEditorAvailableEditorIdsContext = new RawContextKey('activeEditorAvailableEditorIds', ''); -export const EditorsVisibleContext = new RawContextKey('editorIsOpen', false); -export const EditorPinnedContext = new RawContextKey('editorPinned', false); -export const EditorStickyContext = new RawContextKey('editorSticky', false); -export const EditorGroupActiveEditorDirtyContext = new RawContextKey('groupActiveEditorDirty', false); -export const EditorGroupEditorsCountContext = new RawContextKey('groupEditorsCount', 0); -export const NoEditorsVisibleContext = EditorsVisibleContext.toNegated(); export const TextCompareEditorVisibleContext = new RawContextKey('textCompareEditorVisible', false); export const TextCompareEditorActiveContext = new RawContextKey('textCompareEditorActive', false); + +// Editor Group Context Keys +export const EditorGroupEditorsCountContext = new RawContextKey('groupEditorsCount', 0); export const ActiveEditorGroupEmptyContext = new RawContextKey('activeEditorGroupEmpty', false); export const ActiveEditorGroupIndexContext = new RawContextKey('activeEditorGroupIndex', 0); export const ActiveEditorGroupLastContext = new RawContextKey('activeEditorGroupLast', false); export const MultipleEditorGroupsContext = new RawContextKey('multipleEditorGroups', false); export const SingleEditorGroupsContext = MultipleEditorGroupsContext.toNegated(); + +// Editor Layout Context Keys +export const EditorsVisibleContext = new RawContextKey('editorIsOpen', false); export const InEditorZenModeContext = new RawContextKey('inZenMode', false); export const IsCenteredLayoutContext = new RawContextKey('isCenteredLayout', false); export const SplitEditorsVertically = new RawContextKey('splitEditorsVertically', false); @@ -1212,6 +1223,7 @@ interface IEditorPartConfiguration { highlightModifiedTabs?: boolean; tabCloseButton?: 'left' | 'right' | 'off'; tabSizing?: 'fit' | 'shrink'; + pinnedTabSizing?: 'compact' | 'shrink' | 'normal'; titleScrollbarSizing?: 'default' | 'large'; focusRecentEditorAfterClose?: boolean; showIcons?: boolean; diff --git a/src/vs/workbench/common/editor/editorGroup.ts b/src/vs/workbench/common/editor/editorGroup.ts index ee2325194a..438b143348 100644 --- a/src/vs/workbench/common/editor/editorGroup.ts +++ b/src/vs/workbench/common/editor/editorGroup.ts @@ -131,12 +131,6 @@ export class EditorGroup extends Disposable { private onConfigurationUpdated(): void { this.editorOpenPositioning = this.configurationService.getValue('workbench.editor.openPositioning'); this.focusRecentEditorAfterClose = this.configurationService.getValue('workbench.editor.focusRecentEditorAfterClose'); - - if (this.configurationService.getValue('workbench.editor.showTabs') === false) { - // Disabling tabs disables sticky editors until we support - // an indication of sticky editors when tabs are disabled - this.sticky = -1; - } } get count(): number { diff --git a/src/vs/workbench/common/editor/textResourceEditorInput.ts b/src/vs/workbench/common/editor/textResourceEditorInput.ts index a4bf75b68b..6825e004bf 100644 --- a/src/vs/workbench/common/editor/textResourceEditorInput.ts +++ b/src/vs/workbench/common/editor/textResourceEditorInput.ts @@ -137,7 +137,10 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput { } isUntitled(): boolean { - return this.resource.scheme === Schemas.untitled; + // anyFile: is never untitled as it can be saved + // untitled: is untitled by definition + // anyOther: is untitled because it cannot be saved, as such we expect a "Save As" dialog + return !this.fileService.canHandleResource(this.resource); } isReadonly(): boolean { @@ -161,6 +164,14 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput { } save(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { + + // If this is neither an `untitled` resource, nor a resource + // we can handle with the file service, we can only "Save As..." + if (this.resource.scheme !== Schemas.untitled && !this.fileService.canHandleResource(this.resource)) { + return this.saveAs(group, options); + } + + // Normal save return this.doSave(group, options, false); } diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index 66454885dd..4e14d7f53a 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -181,6 +181,12 @@ export const TAB_BORDER = registerColor('tab.border', { hc: contrastBorder }, nls.localize('tabBorder', "Border to separate tabs from each other. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +export const TAB_LAST_PINNED_BORDER = registerColor('tab.lastPinnedBorder', { + dark: null, + light: null, + hc: contrastBorder +}, nls.localize('lastPinnedTabBorder', "Border to separate pinned tabs from other tabs. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); + // < --- Editors --- > export const EDITOR_PANE_BACKGROUND = registerColor('editorPane.background', { diff --git a/src/vs/workbench/contrib/backup/common/backupRestorer.ts b/src/vs/workbench/contrib/backup/common/backupRestorer.ts index dd9e7d0912..91c9a1a0a3 100644 --- a/src/vs/workbench/contrib/backup/common/backupRestorer.ts +++ b/src/vs/workbench/contrib/backup/common/backupRestorer.ts @@ -15,6 +15,7 @@ import { toLocalResource, isEqual } from 'vs/base/common/resources'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { Registry } from 'vs/platform/registry/common/platform'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IPathService } from 'vs/workbench/services/path/common/pathService'; export class BackupRestorer implements IWorkbenchContribution { @@ -28,6 +29,7 @@ export class BackupRestorer implements IWorkbenchContribution { @ILifecycleService private readonly lifecycleService: ILifecycleService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IInstantiationService private readonly instantiationService: IInstantiationService, + @IPathService private readonly pathService: IPathService ) { this.restoreBackups(); } @@ -99,7 +101,7 @@ export class BackupRestorer implements IWorkbenchContribution { // an associated file path or not by just looking at the path. and // if so, we must ensure to restore the local resource it had. if (resource.scheme === Schemas.untitled && !BackupRestorer.UNTITLED_REGEX.test(resource.path) && BackupRestorer.SQLQUERY_REGEX.test(resource.path)) { // {{SQL CARBON EDIT}} @anthonydresser add sql regex test - return { resource: toLocalResource(resource, this.environmentService.configuration.remoteAuthority), options, forceUntitled: true }; + return { resource: toLocalResource(resource, this.environmentService.configuration.remoteAuthority, this.pathService.defaultUriScheme), options, forceUntitled: true }; } // handle custom editors by asking the custom editor input factory diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts index 8b9071e855..6062182e72 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts @@ -49,9 +49,9 @@ export class BulkCellEdits { // } // apply edits - const cellEdits = group.map(edit => edit.cellEdit); - this._notebookService.transformEditsOutputs(ref.object.notebook, cellEdits); - ref.object.notebook.applyEdit(ref.object.notebook.versionId, cellEdits, true); + const edits = group.map(entry => entry.cellEdit); + this._notebookService.transformEditsOutputs(ref.object.notebook, edits); + ref.object.notebook.applyEdits(ref.object.notebook.versionId, edits, true, undefined, () => undefined); ref.dispose(); this._progress.report(undefined); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts index 86d4daefd1..f32c1e562e 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts @@ -146,7 +146,7 @@ class BulkEditPreviewContribution { // the actual work... try { - return await view.setInput(edits, session.cts.token); + return await view.setInput(edits, session.cts.token) ?? []; } finally { // restore UX state diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index d93a5dfbc5..7b8ed40ee6 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -163,7 +163,7 @@ export class BulkEditPane extends ViewPane { this.element.dataset['state'] = state; } - async setInput(edit: ResourceEdit[], token: CancellationToken): Promise { + async setInput(edit: ResourceEdit[], token: CancellationToken): Promise { this._setState(State.Data); this._sessionDisposables.clear(); this._treeViewStates.clear(); @@ -186,9 +186,9 @@ export class BulkEditPane extends ViewPane { this._currentInput = input; - return new Promise(async resolve => { + return new Promise(async resolve => { - token.onCancellationRequested(() => resolve()); + token.onCancellationRequested(() => resolve(undefined)); this._currentResolve = resolve; this._setTreeInput(input); diff --git a/src/vs/workbench/contrib/callHierarchy/common/callHierarchy.ts b/src/vs/workbench/contrib/callHierarchy/common/callHierarchy.ts index b70fa54f4f..d0e2eeee73 100644 --- a/src/vs/workbench/contrib/callHierarchy/common/callHierarchy.ts +++ b/src/vs/workbench/contrib/callHierarchy/common/callHierarchy.ts @@ -12,7 +12,7 @@ import { URI } from 'vs/base/common/uri'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { onUnexpectedExternalError } from 'vs/base/common/errors'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable } from 'vs/base/common/lifecycle'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { assertType } from 'vs/base/common/types'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -180,7 +180,7 @@ CommandsRegistry.registerCommand('_executePrepareCallHierarchy', async (accessor return [model.root]; } finally { - dispose(textModelReference); + textModelReference?.dispose(); } }); diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts index 678169a506..858c8da0a2 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts @@ -20,7 +20,7 @@ import { FontStyle, LanguageIdentifier, StandardTokenType, TokenMetadata, Docume import { IModeService } from 'vs/editor/common/services/modeService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { editorHoverBackground, editorHoverBorder } from 'vs/platform/theme/common/colorRegistry'; -import { HIGH_CONTRAST, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { findMatchingThemeRule } from 'vs/workbench/services/textMate/common/TMHelper'; import { ITextMateService, IGrammar, IToken, StackElement } from 'vs/workbench/services/textMate/common/textMateService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; @@ -29,6 +29,7 @@ import { ColorThemeData, TokenStyleDefinitions, TokenStyleDefinition, TextMateTh import { SemanticTokenRule, TokenStyleData, TokenStyle } from 'vs/platform/theme/common/tokenClassificationRegistry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { SEMANTIC_HIGHLIGHTING_SETTING_ID, IEditorSemanticHighlightingOptions } from 'vs/editor/common/services/modelServiceImpl'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; const $ = dom.$; @@ -643,7 +644,7 @@ registerEditorAction(InspectEditorTokens); registerThemingParticipant((theme, collector) => { const border = theme.getColor(editorHoverBorder); if (border) { - let borderWidth = theme.type === HIGH_CONTRAST ? 2 : 1; + let borderWidth = theme.type === ColorScheme.HIGH_CONTRAST ? 2 : 1; collector.addRule(`.monaco-editor .token-inspect-widget { border: ${borderWidth}px solid ${border}; }`); collector.addRule(`.monaco-editor .token-inspect-widget .tiw-metadata-separator { background-color: ${border}; }`); } diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index 72cd4dbabc..cf55de5244 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -663,7 +663,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._disposables.add(newCommentNode); this._disposables.add(newCommentNode.onDidClick(clickedNode => - this.setFocusedComment(arrays.firstIndex(this._commentElements, commentNode => commentNode.comment.uniqueIdInThread === clickedNode.comment.uniqueIdInThread)) + this.setFocusedComment(this._commentElements.findIndex(commentNode => commentNode.comment.uniqueIdInThread === clickedNode.comment.uniqueIdInThread)) )); return newCommentNode; @@ -692,7 +692,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } if (label) { - this._headingLabel.innerHTML = strings.escape(label); + this._headingLabel.textContent = strings.escape(label); this._headingLabel.setAttribute('aria-label', label); } } @@ -916,7 +916,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget font-weight: ${fontInfo.fontWeight}; }`); - this._styleElement.innerHTML = content.join('\n'); + this._styleElement.textContent = content.join('\n'); // Editor decorations should also be responsive to theme changes this.setCommentEditorDecorations(); diff --git a/src/vs/workbench/contrib/comments/browser/commentsView.ts b/src/vs/workbench/contrib/comments/browser/commentsView.ts index 2f5608a884..41c082b5c9 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsView.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsView.ts @@ -109,7 +109,7 @@ export class CommentsPanel extends ViewPane { content.push(`.comments-panel .comments-panel-container .text code { color: ${codeTextForegroundColor}; }`); } - styleElement.innerHTML = content.join('\n'); + styleElement.textContent = content.join('\n'); } private async renderComments(): Promise { diff --git a/src/vs/workbench/contrib/comments/common/commentModel.ts b/src/vs/workbench/contrib/comments/common/commentModel.ts index 8c899c8c0c..d02c043fc6 100644 --- a/src/vs/workbench/contrib/comments/common/commentModel.ts +++ b/src/vs/workbench/contrib/comments/common/commentModel.ts @@ -6,7 +6,7 @@ import { URI } from 'vs/base/common/uri'; import { IRange } from 'vs/editor/common/core/range'; import { Comment, CommentThread, CommentThreadChangedEvent } from 'vs/editor/common/modes'; -import { groupBy, firstIndex, flatten } from 'vs/base/common/arrays'; +import { groupBy, flatten } from 'vs/base/common/arrays'; import { localize } from 'vs/nls'; export interface ICommentThreadChangedEvent extends CommentThreadChangedEvent { @@ -83,11 +83,11 @@ export class CommentsModel { removed.forEach(thread => { // Find resource that has the comment thread - const matchingResourceIndex = firstIndex(threadsForOwner, (resourceData) => resourceData.id === thread.resource); + const matchingResourceIndex = threadsForOwner.findIndex((resourceData) => resourceData.id === thread.resource); const matchingResourceData = threadsForOwner[matchingResourceIndex]; // Find comment node on resource that is that thread and remove it - const index = firstIndex(matchingResourceData.commentThreads, (commentThread) => commentThread.threadId === thread.threadId); + const index = matchingResourceData.commentThreads.findIndex((commentThread) => commentThread.threadId === thread.threadId); matchingResourceData.commentThreads.splice(index, 1); // If the comment thread was the last thread for a resource, remove that resource from the list @@ -98,11 +98,11 @@ export class CommentsModel { changed.forEach(thread => { // Find resource that has the comment thread - const matchingResourceIndex = firstIndex(threadsForOwner, (resourceData) => resourceData.id === thread.resource); + const matchingResourceIndex = threadsForOwner.findIndex((resourceData) => resourceData.id === thread.resource); const matchingResourceData = threadsForOwner[matchingResourceIndex]; // Find comment node on resource that is that thread and replace it - const index = firstIndex(matchingResourceData.commentThreads, (commentThread) => commentThread.threadId === thread.threadId); + const index = matchingResourceData.commentThreads.findIndex((commentThread) => commentThread.threadId === thread.threadId); if (index >= 0) { matchingResourceData.commentThreads[index] = ResourceWithCommentThreads.createCommentNode(owner, URI.parse(matchingResourceData.id), thread); } else if (thread.comments && thread.comments.length) { diff --git a/src/vs/workbench/contrib/configExporter/electron-browser/configurationExportHelper.contribution.ts b/src/vs/workbench/contrib/configExporter/electron-browser/configurationExportHelper.contribution.ts index 2bac2b9d99..f1b2eeffa6 100644 --- a/src/vs/workbench/contrib/configExporter/electron-browser/configurationExportHelper.contribution.ts +++ b/src/vs/workbench/contrib/configExporter/electron-browser/configurationExportHelper.contribution.ts @@ -8,8 +8,8 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { DefaultConfigurationExportHelper } from 'vs/workbench/contrib/configExporter/electron-browser/configurationExportHelper'; -import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; export class ExtensionPoints implements IWorkbenchContribution { diff --git a/src/vs/workbench/contrib/configExporter/electron-browser/configurationExportHelper.ts b/src/vs/workbench/contrib/configExporter/electron-browser/configurationExportHelper.ts index bc91e3c823..b0aba574f0 100644 --- a/src/vs/workbench/contrib/configExporter/electron-browser/configurationExportHelper.ts +++ b/src/vs/workbench/contrib/configExporter/electron-browser/configurationExportHelper.ts @@ -5,8 +5,8 @@ import { writeFile } from 'vs/base/node/pfs'; import product from 'vs/platform/product/common/product'; -import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationNode, IConfigurationRegistry, Extensions, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditor.contribution.ts b/src/vs/workbench/contrib/customEditor/browser/customEditor.contribution.ts index 7d58c370bd..3ded4a394f 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditor.contribution.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditor.contribution.ts @@ -13,7 +13,7 @@ import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } fr import { Extensions as EditorInputExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; import { CustomEditorInputFactory } from 'vs/workbench/contrib/customEditor/browser/customEditorInputFactory'; import { ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; -import { WebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewEditor'; +import { WebviewEditor } from 'vs/workbench/contrib/webviewPanel/browser/webviewEditor'; import { CustomEditorInput } from './customEditorInput'; import { CustomEditorContribution, CustomEditorService } from './customEditors'; diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index 847b5f2f6e..44361564b1 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -18,7 +18,7 @@ import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { GroupIdentifier, IEditorInput, IRevertOptions, ISaveOptions, Verbosity } from 'vs/workbench/common/editor'; import { ICustomEditorModel, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { IWebviewService, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; -import { IWebviewWorkbenchService, LazilyResolvedWebviewEditorInput } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; +import { IWebviewWorkbenchService, LazilyResolvedWebviewEditorInput } from 'vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts index 1ebe67dd7e..d911e2ed1b 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts @@ -9,8 +9,8 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IEditorInput } from 'vs/workbench/common/editor'; import { CustomEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; import { IWebviewService, WebviewExtensionDescription, WebviewContentPurpose } from 'vs/workbench/contrib/webview/browser/webview'; -import { reviveWebviewExtensionDescription, SerializedWebview, WebviewEditorInputFactory, DeserializedWebview } from 'vs/workbench/contrib/webview/browser/webviewEditorInputFactory'; -import { IWebviewWorkbenchService, WebviewInputOptions } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; +import { reviveWebviewExtensionDescription, SerializedWebview, WebviewEditorInputFactory, DeserializedWebview } from 'vs/workbench/contrib/webviewPanel/browser/webviewEditorInputFactory'; +import { IWebviewWorkbenchService, WebviewInputOptions } from 'vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; export interface CustomDocumentBackupData { diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index 2365a85370..4057a58352 100644 --- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -43,7 +43,7 @@ export interface IVariableTemplateData { export function renderViewTree(container: HTMLElement): HTMLElement { const treeContainer = $('.'); - dom.addClass(treeContainer, 'debug-view-content'); + treeContainer.classList.add('debug-view-content'); container.appendChild(treeContainer); return treeContainer; } @@ -55,9 +55,9 @@ export function renderExpressionValue(expressionOrValue: IExpressionContainer | container.className = 'value'; // when resolving expressions we represent errors from the server as a variable with name === null. if (value === null || ((expressionOrValue instanceof Expression || expressionOrValue instanceof Variable || expressionOrValue instanceof ReplEvaluationResult) && !expressionOrValue.available)) { - dom.addClass(container, 'unavailable'); + container.classList.add('unavailable'); if (value !== Expression.DEFAULT_VALUE) { - dom.addClass(container, 'error'); + container.classList.add('error'); } } else if ((expressionOrValue instanceof ExpressionContainer) && options.showChanged && expressionOrValue.valueChanged && value !== Expression.DEFAULT_VALUE) { // value changed color has priority over other colors. @@ -67,13 +67,13 @@ export function renderExpressionValue(expressionOrValue: IExpressionContainer | if (options.colorize && typeof expressionOrValue !== 'string') { if (expressionOrValue.type === 'number' || expressionOrValue.type === 'boolean' || expressionOrValue.type === 'string') { - dom.addClass(container, expressionOrValue.type); + container.classList.add(expressionOrValue.type); } else if (!isNaN(+value)) { - dom.addClass(container, 'number'); + container.classList.add('number'); } else if (booleanRegex.test(value)) { - dom.addClass(container, 'boolean'); + container.classList.add('boolean'); } else if (stringRegex.test(value)) { - dom.addClass(container, 'string'); + container.classList.add('string'); } } @@ -103,7 +103,7 @@ export function renderVariable(variable: Variable, data: IVariableTemplateData, text += ':'; } data.label.set(text, highlights, variable.type ? variable.type : variable.name); - dom.toggleClass(data.name, 'virtual', !!variable.presentationHint && variable.presentationHint.kind === 'virtual'); + data.name.classList.toggle('virtual', !!variable.presentationHint && variable.presentationHint.kind === 'virtual'); } else if (variable.value && typeof variable.name === 'string' && variable.name) { data.label.set(':'); } diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index 6c31c54d61..d5845c1620 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -627,7 +627,7 @@ class InlineBreakpointWidget implements IContentWidget, IDisposable { return null; } // Workaround: since the content widget can not be placed before the first column we need to force the left position - dom.toggleClass(this.domNode, 'line-start', this.range.startColumn === 1); + this.domNode.classList.toggle('line-start', this.range.startColumn === 1); return { position: { lineNumber: this.range.startLineNumber, column: this.range.startColumn - 1 }, diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index dc58ee7284..fe62d271fd 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -86,8 +86,8 @@ export class BreakpointsView extends ViewPane { public renderBody(container: HTMLElement): void { super.renderBody(container); - dom.addClass(this.element, 'debug-pane'); - dom.addClass(container, 'debug-breakpoints'); + this.element.classList.add('debug-pane'); + container.classList.add('debug-breakpoints'); const delegate = new BreakpointsDelegate(this.debugService); this.list = >this.instantiationService.createInstance(WorkbenchList, 'Breakpoints', container, delegate, [ @@ -369,7 +369,7 @@ class BreakpointsRenderer implements IListRenderer, index: number, data: IStackFrameTemplateData): void { const stackFrame = element.element; - dom.toggleClass(data.stackFrame, 'disabled', !stackFrame.source || !stackFrame.source.available || isDeemphasized(stackFrame)); - dom.toggleClass(data.stackFrame, 'label', stackFrame.presentationHint === 'label'); - dom.toggleClass(data.stackFrame, 'subtle', stackFrame.presentationHint === 'subtle'); + data.stackFrame.classList.toggle('disabled', !stackFrame.source || !stackFrame.source.available || isDeemphasized(stackFrame)); + data.stackFrame.classList.toggle('label', stackFrame.presentationHint === 'label'); + data.stackFrame.classList.toggle('subtle', stackFrame.presentationHint === 'subtle'); const hasActions = !!stackFrame.thread.session.capabilities.supportsRestartFrame && stackFrame.presentationHint !== 'label' && stackFrame.presentationHint !== 'subtle'; - dom.toggleClass(data.stackFrame, 'has-actions', hasActions); + data.stackFrame.classList.toggle('has-actions', hasActions); data.file.title = stackFrame.source.inMemory ? stackFrame.source.uri.path : this.labelService.getUriLabel(stackFrame.source.uri); if (stackFrame.source.raw.origin) { @@ -648,9 +648,9 @@ class StackFramesRenderer implements ICompressibleTreeRenderer Promise) }[] = []; private toDispose: IDisposable[]; private selected = 0; - private providers: { label: string, pick: () => Promise<{ launch: ILaunch, config: IConfig } | undefined> }[] = []; + private providers: { label: string, provider: IDebugConfigurationProvider, pick: () => Promise<{ launch: ILaunch, config: IConfig } | undefined> }[] = []; constructor( private context: unknown, @@ -67,7 +67,7 @@ export class StartDebugActionViewItem implements IActionViewItem { render(container: HTMLElement): void { this.container = container; - dom.addClass(container, 'start-debug-action-item'); + container.classList.add('start-debug-action-item'); this.start = dom.append(container, $('.codicon.codicon-debug-start')); this.start.title = this.action.label; this.start.setAttribute('role', 'button'); @@ -80,14 +80,14 @@ export class StartDebugActionViewItem implements IActionViewItem { this.toDispose.push(dom.addDisposableListener(this.start, dom.EventType.MOUSE_DOWN, (e: MouseEvent) => { if (this.action.enabled && e.button === 0) { - dom.addClass(this.start, 'active'); + this.start.classList.add('active'); } })); this.toDispose.push(dom.addDisposableListener(this.start, dom.EventType.MOUSE_UP, () => { - dom.removeClass(this.start, 'active'); + this.start.classList.remove('active'); })); this.toDispose.push(dom.addDisposableListener(this.start, dom.EventType.MOUSE_OUT, () => { - dom.removeClass(this.start, 'active'); + this.start.classList.remove('active'); })); this.toDispose.push(dom.addDisposableListener(this.start, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => { @@ -182,7 +182,7 @@ export class StartDebugActionViewItem implements IActionViewItem { const label = inWorkspace ? `${name} (${launch.name})` : name; this.options.push({ label, handler: async () => { - manager.selectConfiguration(launch, name); + await manager.selectConfiguration(launch, name); return true; } }); @@ -196,7 +196,7 @@ export class StartDebugActionViewItem implements IActionViewItem { } this.providers.forEach(p => { - if (p.label === manager.selectedConfiguration.name) { + if (p.provider.type === manager.selectedConfiguration.config?.type) { this.selected = this.options.length; } @@ -204,7 +204,7 @@ export class StartDebugActionViewItem implements IActionViewItem { label: `${p.label}...`, handler: async () => { const picked = await p.pick(); if (picked) { - manager.selectConfiguration(picked.launch, p.label, picked.config); + await manager.selectConfiguration(picked.launch, picked.config.name, picked.config); return true; } return false; diff --git a/src/vs/workbench/contrib/debug/browser/debugActions.ts b/src/vs/workbench/contrib/debug/browser/debugActions.ts index 08fb9f950b..af7a887e9e 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActions.ts @@ -408,7 +408,9 @@ export class CopyValueAction extends Action { try { const evaluation = await session.evaluate(toEvaluate, stackFrame.frameId, context); - this.clipboardService.writeText(evaluation.body.result); + if (evaluation) { + this.clipboardService.writeText(evaluation.body.result); + } } catch (e) { this.clipboardService.writeText(typeof this.value === 'string' ? this.value : this.value.value); } diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index de234405e4..be29285cec 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -167,8 +167,8 @@ export function registerCommands(): void { const source = stackFrame.thread.session.getSourceForUri(resource); if (source) { const response = await stackFrame.thread.session.gotoTargets(source.raw, position.lineNumber, position.column); - const targets = response.body.targets; - if (targets.length) { + const targets = response?.body.targets; + if (targets && targets.length) { let id = targets[0].id; if (targets.length > 1) { const picks = targets.map(t => ({ label: t.label, _id: t.id })); diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index 1bafe60ee2..0ad3bd6e3f 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -37,7 +37,7 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance import { withUndefinedAsNull } from 'vs/base/common/types'; import { sequence } from 'vs/base/common/async'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; -import { first, flatten } from 'vs/base/common/arrays'; +import { flatten } from 'vs/base/common/arrays'; import { getVisibleAndSorted } from 'vs/workbench/contrib/debug/common/debugUtils'; import { DebugConfigurationProviderTriggerKind } from 'vs/workbench/api/common/extHostTypes'; @@ -46,6 +46,8 @@ jsonRegistry.registerSchema(launchSchemaId, launchSchema); const DEBUG_SELECTED_CONFIG_NAME_KEY = 'debug.selectedconfigname'; const DEBUG_SELECTED_ROOT = 'debug.selectedroot'; +// Debug type is only stored if a dynamic configuration is used for better restore +const DEBUG_SELECTED_TYPE = 'debug.selectedtype'; interface IDynamicPickItem { label: string, launch: ILaunch, config: IConfig } @@ -84,13 +86,15 @@ export class ConfigurationManager implements IConfigurationManager { this.initLaunches(); this.registerListeners(); const previousSelectedRoot = this.storageService.get(DEBUG_SELECTED_ROOT, StorageScope.WORKSPACE); + const previousSelectedType = this.storageService.get(DEBUG_SELECTED_TYPE, StorageScope.WORKSPACE); const previousSelectedLaunch = this.launches.find(l => l.uri.toString() === previousSelectedRoot); + const previousSelectedName = this.storageService.get(DEBUG_SELECTED_CONFIG_NAME_KEY, StorageScope.WORKSPACE); this.debugConfigurationTypeContext = CONTEXT_DEBUG_CONFIGURATION_TYPE.bindTo(contextKeyService); this.debuggersAvailable = CONTEXT_DEBUGGERS_AVAILABLE.bindTo(contextKeyService); if (previousSelectedLaunch && previousSelectedLaunch.getConfigurationNames().length) { - this.selectConfiguration(previousSelectedLaunch, this.storageService.get(DEBUG_SELECTED_CONFIG_NAME_KEY, StorageScope.WORKSPACE)); + this.selectConfiguration(previousSelectedLaunch, previousSelectedName, undefined, previousSelectedType); } else if (this.launches.length > 0) { - this.selectConfiguration(undefined); + this.selectConfiguration(undefined, previousSelectedName, undefined, previousSelectedType); } } @@ -259,7 +263,7 @@ export class ConfigurationManager implements IConfigurationManager { return results.reduce((first, second) => first.concat(second), []); } - async getDynamicProviders(): Promise<{ label: string, pick: () => Promise<{ launch: ILaunch, config: IConfig } | undefined> }[]> { + async getDynamicProviders(): Promise<{ label: string, provider: IDebugConfigurationProvider, pick: () => Promise<{ launch: ILaunch, config: IConfig } | undefined> }[]> { const extensions = await this.extensionService.getExtensions(); const onDebugDynamicConfigurationsName = 'onDebugDynamicConfigurations'; const debugDynamicExtensionsTypes = extensions.reduce((acc, e) => { @@ -289,9 +293,12 @@ export class ConfigurationManager implements IConfigurationManager { return acc; }, [] as string[]); + await Promise.all(debugDynamicExtensionsTypes.map(type => this.activateDebuggers(onDebugDynamicConfigurationsName, type))); return debugDynamicExtensionsTypes.map(type => { + const provider = this.configProviders.find(p => p.type === type && p.triggerKind === DebugConfigurationProviderTriggerKind.Dynamic && p.provideDebugConfigurations)!; return { label: this.getDebuggerLabel(type)!, + provider, pick: async () => { const disposables = new DisposableStore(); const input = disposables.add(this.quickInputService.createQuickPick()); @@ -308,15 +315,13 @@ export class ConfigurationManager implements IConfigurationManager { await launch.openConfigFile(false, config.type); // Only Launch have a pin trigger button await (launch as Launch).writeConfiguration(config); - this.selectConfiguration(launch, config.name); + await this.selectConfiguration(launch, config.name); })); - disposables.add(input.onDidHide(() => { chosenDidCancel = true; resolve(); })); + disposables.add(input.onDidHide(() => { chosenDidCancel = true; resolve(undefined); })); }); - await this.activateDebuggers(onDebugDynamicConfigurationsName, type); const token = new CancellationTokenSource(); const picks: Promise[] = []; - const provider = this.configProviders.filter(p => p.type === type && p.triggerKind === DebugConfigurationProviderTriggerKind.Dynamic && p.provideDebugConfigurations)[0]; this.getLaunches().forEach(launch => { if (launch.workspace && provider) { picks.push(provider.provideDebugConfigurations!(launch.workspace.uri, token.token).then(configurations => configurations.map(config => ({ @@ -508,12 +513,12 @@ export class ConfigurationManager implements IConfigurationManager { return undefined; } - selectConfiguration(launch: ILaunch | undefined, name?: string, config?: IConfig): void { + async selectConfiguration(launch: ILaunch | undefined, name?: string, config?: IConfig, type?: string): Promise { if (typeof launch === 'undefined') { const rootUri = this.historyService.getLastActiveWorkspaceRoot(); launch = this.getLaunch(rootUri); if (!launch || launch.getConfigurationNames().length === 0) { - launch = first(this.launches, l => !!(l && l.getConfigurationNames().length), launch) || this.launches[0]; + launch = this.launches.find(l => !!(l && l.getConfigurationNames().length)) || launch || this.launches[0]; } } @@ -526,14 +531,31 @@ export class ConfigurationManager implements IConfigurationManager { } else { this.storageService.remove(DEBUG_SELECTED_ROOT, StorageScope.WORKSPACE); } + const names = launch ? launch.getConfigurationNames() : []; if ((name && names.indexOf(name) >= 0) || config) { this.setSelectedLaunchName(name); } else if (!this.selectedName || names.indexOf(this.selectedName) === -1) { - this.setSelectedLaunchName(names.length ? names[0] : undefined); + // We could not find the previously used name. We should get all dynamic configurations from providers + // And potentially auto select the previously used dynamic configuration #96293 + const providers = await this.getDynamicProviders(); + const provider = providers.find(p => p.provider.type === type); + let nameToSet = names.length ? names[0] : undefined; + if (provider && launch && launch.workspace) { + const token = new CancellationTokenSource(); + const dynamicConfigs = await provider.provider.provideDebugConfigurations!(launch.workspace.uri, token.token); + const dynamicConfig = dynamicConfigs.find(c => c.name === name); + if (dynamicConfig) { + config = dynamicConfig; + nameToSet = name; + } + } + + this.setSelectedLaunchName(nameToSet); } this.selectedConfig = config; + this.storageService.store(DEBUG_SELECTED_TYPE, this.selectedConfig?.type, StorageScope.WORKSPACE); const configForType = this.selectedConfig || (this.selectedLaunch && this.selectedName ? this.selectedLaunch.getConfiguration(this.selectedName) : undefined); if (configForType) { this.debugConfigurationTypeContext.set(configForType.type); diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts index a01c96c2fd..e4042d55bc 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts @@ -277,6 +277,10 @@ class StepIntoTargetsAction extends EditorAction { if (session && frame && editor.hasModel() && editor.getModel().uri.toString() === frame.source.uri.toString()) { const targets = await session.stepInTargets(frame.frameId); + if (!targets) { + return; + } + editor.revealLineInCenterIfOutsideViewport(frame.range.startLineNumber); const cursorCoords = editor.getScrolledVisiblePosition({ lineNumber: frame.range.startLineNumber, column: frame.range.startColumn }); const editorCoords = getDomNodePagePosition(editor.getDomNode()); diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index 4feda46b3c..ae47c047a2 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -26,7 +26,6 @@ import { ExceptionWidget } from 'vs/workbench/contrib/debug/browser/exceptionWid import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets'; import { Position } from 'vs/editor/common/core/position'; import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands'; -import { first } from 'vs/base/common/arrays'; import { memoize, createMemoizer } from 'vs/base/common/decorators'; import { IEditorHoverOptions, EditorOption } from 'vs/editor/common/config/editorOptions'; import { DebugHoverWidget } from 'vs/workbench/contrib/debug/browser/debugHover'; @@ -373,7 +372,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { } // First call stack frame that is available is the frame where exception has been thrown - const exceptionSf = first(callStack, sf => !!(sf && sf.source && sf.source.available && sf.source.presentationHint !== 'deemphasize'), undefined); + const exceptionSf = callStack.find(sf => !!(sf && sf.source && sf.source.available && sf.source.presentationHint !== 'deemphasize')); if (!exceptionSf || exceptionSf !== focusedSf) { this.closeExceptionWidget(); return; diff --git a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts index 33b9191856..c5157be4c4 100644 --- a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts +++ b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts @@ -64,7 +64,7 @@ export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider { - this.debugService.getConfigurationManager().selectConfiguration(config.launch, config.name); + await this.debugService.getConfigurationManager().selectConfiguration(config.launch, config.name); try { await this.debugService.startDebugging(config.launch); } catch (error) { diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index b6700ca8ed..8996abcf31 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -404,7 +404,7 @@ export class DebugSession implements IDebugSession { } const response = await this.raw.dataBreakpointInfo({ name, variablesReference }); - return response.body; + return response?.body; } async sendDataBreakpoints(dataBreakpoints: IDataBreakpoint[]): Promise { @@ -431,7 +431,7 @@ export class DebugSession implements IDebugSession { const source = this.getRawSource(uri); const response = await this.raw.breakpointLocations({ source, line: lineNumber }); - if (!response.body || !response.body.breakpoints) { + if (!response || !response.body || !response.body.breakpoints) { return []; } @@ -444,7 +444,7 @@ export class DebugSession implements IDebugSession { return this.model.getDebugProtocolBreakpoint(breakpointId, this.getId()); } - customRequest(request: string, args: any): Promise { + customRequest(request: string, args: any): Promise { if (!this.raw) { throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", request)); } @@ -452,7 +452,7 @@ export class DebugSession implements IDebugSession { return this.raw.custom(request, args); } - stackTrace(threadId: number, startFrame: number, levels: number, token: CancellationToken): Promise { + stackTrace(threadId: number, startFrame: number, levels: number, token: CancellationToken): Promise { if (!this.raw) { throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'stackTrace')); } @@ -479,7 +479,7 @@ export class DebugSession implements IDebugSession { return undefined; } - scopes(frameId: number, threadId: number): Promise { + scopes(frameId: number, threadId: number): Promise { if (!this.raw) { throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'scopes')); } @@ -488,7 +488,7 @@ export class DebugSession implements IDebugSession { return this.raw.scopes({ frameId }, token); } - variables(variablesReference: number, threadId: number | undefined, filter: 'indexed' | 'named' | undefined, start: number | undefined, count: number | undefined): Promise { + variables(variablesReference: number, threadId: number | undefined, filter: 'indexed' | 'named' | undefined, start: number | undefined, count: number | undefined): Promise { if (!this.raw) { throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'variables')); } @@ -497,7 +497,7 @@ export class DebugSession implements IDebugSession { return this.raw.variables({ variablesReference, filter, start, count }, token); } - evaluate(expression: string, frameId: number, context?: string): Promise { + evaluate(expression: string, frameId: number, context?: string): Promise { if (!this.raw) { throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'evaluate')); } @@ -577,7 +577,7 @@ export class DebugSession implements IDebugSession { await this.raw.terminateThreads({ threadIds }); } - setVariable(variablesReference: number, name: string, value: string): Promise { + setVariable(variablesReference: number, name: string, value: string): Promise { if (!this.raw) { throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'setVariable')); } @@ -585,7 +585,7 @@ export class DebugSession implements IDebugSession { return this.raw.setVariable({ variablesReference, name, value }); } - gotoTargets(source: DebugProtocol.Source, line: number, column?: number): Promise { + gotoTargets(source: DebugProtocol.Source, line: number, column?: number): Promise { if (!this.raw) { throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'gotoTargets')); } @@ -593,7 +593,7 @@ export class DebugSession implements IDebugSession { return this.raw.gotoTargets({ source, line, column }); } - goto(threadId: number, targetId: number): Promise { + goto(threadId: number, targetId: number): Promise { if (!this.raw) { throw new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'goto')); } @@ -601,7 +601,7 @@ export class DebugSession implements IDebugSession { return this.raw.goto({ threadId, targetId }); } - loadSource(resource: URI): Promise { + loadSource(resource: URI): Promise { if (!this.raw) { return Promise.reject(new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'loadSource'))); } @@ -632,7 +632,7 @@ export class DebugSession implements IDebugSession { } } - async completions(frameId: number | undefined, threadId: number, text: string, position: Position, overwriteBefore: number, token: CancellationToken): Promise { + async completions(frameId: number | undefined, threadId: number, text: string, position: Position, overwriteBefore: number, token: CancellationToken): Promise { if (!this.raw) { return Promise.reject(new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'completions'))); } @@ -646,16 +646,16 @@ export class DebugSession implements IDebugSession { }, sessionCancelationToken); } - async stepInTargets(frameId: number): Promise<{ id: number, label: string }[]> { + async stepInTargets(frameId: number): Promise<{ id: number, label: string }[] | undefined> { if (!this.raw) { return Promise.reject(new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'stepInTargets'))); } const response = await this.raw.stepInTargets({ frameId }); - return response.body.targets; + return response?.body.targets; } - async cancel(progressId: string): Promise { + async cancel(progressId: string): Promise { if (!this.raw) { return Promise.reject(new Error(localize('noDebugAdapter', "No debugger available, can not send '{0}'", 'cancel'))); } diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index 559eb7a433..0044131d95 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -142,7 +142,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { })); this._register(dom.addDisposableGenericMouseDownListner(this.dragArea, (event: MouseEvent) => { - dom.addClass(this.dragArea, 'dragged'); + this.dragArea.classList.add('dragged'); const mouseMoveListener = dom.addDisposableGenericMouseMoveListner(window, (e: MouseEvent) => { const mouseMoveEvent = new StandardMouseEvent(e); @@ -154,7 +154,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { const mouseUpListener = dom.addDisposableGenericMouseUpListner(window, (e: MouseEvent) => { this.storePosition(); - dom.removeClass(this.dragArea, 'dragged'); + this.dragArea.classList.remove('dragged'); mouseMoveListener.dispose(); mouseUpListener.dispose(); diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index f3b9eb8980..0b55fb8040 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -6,7 +6,6 @@ import 'vs/css!./media/debugViewlet'; import * as nls from 'vs/nls'; import { IAction, IActionViewItem } from 'vs/base/common/actions'; -import * as DOM from 'vs/base/browser/dom'; import { IDebugService, VIEWLET_ID, State, BREAKPOINTS_VIEW_ID, IDebugConfiguration, CONTEXT_DEBUG_UX, CONTEXT_DEBUG_UX_KEY, REPL_VIEW_ID } from 'vs/workbench/contrib/debug/common/debug'; import { StartAction, ConfigureAction, SelectAndStartAction, FocusSessionAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { StartDebugActionViewItem, FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/debugActionViewItems'; @@ -91,7 +90,7 @@ export class DebugViewPaneContainer extends ViewPaneContainer { create(parent: HTMLElement): void { super.create(parent); - DOM.addClass(parent, 'debug-viewlet'); + parent.classList.add('debug-viewlet'); } focus(): void { @@ -198,7 +197,7 @@ export class DebugViewPaneContainer extends ViewPaneContainer { if (state === State.Initializing) { this.progressService.withProgress({ location: VIEWLET_ID, }, _progress => { - return new Promise(resolve => this.progressResolve = resolve); + return new Promise(resolve => this.progressResolve = resolve); }); } diff --git a/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts index f40171e68c..51e7a476be 100644 --- a/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts +++ b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts @@ -33,9 +33,8 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i if (connection) { channel = connection.getChannel(ExtensionHostDebugBroadcastChannel.ChannelName); } else { + // Extension host debugging not supported in serverless. channel = { call: async () => undefined, listen: () => Event.None } as any; - // TODO@weinand TODO@isidorn fallback? - logService.warn('Extension Host Debugging not available due to missing connection.'); } super(channel); @@ -109,7 +108,7 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i environment.set('inspect-extensions', inspectExtensions); } - // Open debug window as new window. Pass ParsedArgs over. + // Open debug window as new window. Pass arguments over. await this.workspaceProvider.open(debugWorkspace, { reuse: false, // debugging always requires a new window payload: Array.from(environment.entries()) // mandatory properties to enable debugging diff --git a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts index ca568b1b13..a3b2361c93 100644 --- a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts +++ b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as dom from 'vs/base/browser/dom'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { normalize, isAbsolute, posix } from 'vs/base/common/path'; import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; @@ -441,9 +440,9 @@ export class LoadedScriptsView extends ViewPane { renderBody(container: HTMLElement): void { super.renderBody(container); - dom.addClass(this.element, 'debug-pane'); - dom.addClass(container, 'debug-loaded-scripts'); - dom.addClass(container, 'show-file-icons'); + this.element.classList.add('debug-pane'); + container.classList.add('debug-loaded-scripts'); + container.classList.add('show-file-icons'); this.treeContainer = renderViewTree(container); diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index df7c3d6de2..a395b06b53 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -251,9 +251,11 @@ export class RawDebugSession implements IDisposable { /** * Send client capabilities to the debug adapter and receive DA capabilities in return. */ - async initialize(args: DebugProtocol.InitializeRequestArguments): Promise { + async initialize(args: DebugProtocol.InitializeRequestArguments): Promise { const response = await this.send('initialize', args); - this.mergeCapabilities(response.body); + if (response) { + this.mergeCapabilities(response.body); + } return response; } @@ -267,9 +269,11 @@ export class RawDebugSession implements IDisposable { //---- DAP requests - async launchOrAttach(config: IConfig): Promise { + async launchOrAttach(config: IConfig): Promise { const response = await this.send(config.request, config); - this.mergeCapabilities(response.body); + if (response) { + this.mergeCapabilities(response.body); + } return response; } @@ -277,7 +281,7 @@ export class RawDebugSession implements IDisposable { /** * Try killing the debuggee softly... */ - terminate(restart = false): Promise { + terminate(restart = false): Promise { if (this.capabilities.supportsTerminateRequest) { if (!this.terminated) { this.terminated = true; @@ -288,32 +292,32 @@ export class RawDebugSession implements IDisposable { return Promise.reject(new Error('terminated not supported')); } - restart(): Promise { + restart(): Promise { if (this.capabilities.supportsRestartRequest) { return this.send('restart', null); } return Promise.reject(new Error('restart not supported')); } - async next(args: DebugProtocol.NextArguments): Promise { + async next(args: DebugProtocol.NextArguments): Promise { const response = await this.send('next', args); this.fireSimulatedContinuedEvent(args.threadId); return response; } - async stepIn(args: DebugProtocol.StepInArguments): Promise { + async stepIn(args: DebugProtocol.StepInArguments): Promise { const response = await this.send('stepIn', args); this.fireSimulatedContinuedEvent(args.threadId); return response; } - async stepOut(args: DebugProtocol.StepOutArguments): Promise { + async stepOut(args: DebugProtocol.StepOutArguments): Promise { const response = await this.send('stepOut', args); this.fireSimulatedContinuedEvent(args.threadId); return response; } - async continue(args: DebugProtocol.ContinueArguments): Promise { + async continue(args: DebugProtocol.ContinueArguments): Promise { const response = await this.send('continue', args); if (response && response.body && response.body.allThreadsContinued !== undefined) { this.allThreadsContinued = response.body.allThreadsContinued; @@ -323,25 +327,25 @@ export class RawDebugSession implements IDisposable { return response; } - pause(args: DebugProtocol.PauseArguments): Promise { + pause(args: DebugProtocol.PauseArguments): Promise { return this.send('pause', args); } - terminateThreads(args: DebugProtocol.TerminateThreadsArguments): Promise { + terminateThreads(args: DebugProtocol.TerminateThreadsArguments): Promise { if (this.capabilities.supportsTerminateThreadsRequest) { return this.send('terminateThreads', args); } return Promise.reject(new Error('terminateThreads not supported')); } - setVariable(args: DebugProtocol.SetVariableArguments): Promise { + setVariable(args: DebugProtocol.SetVariableArguments): Promise { if (this.capabilities.supportsSetVariable) { return this.send('setVariable', args); } return Promise.reject(new Error('setVariable not supported')); } - async restartFrame(args: DebugProtocol.RestartFrameArguments, threadId: number): Promise { + async restartFrame(args: DebugProtocol.RestartFrameArguments, threadId: number): Promise { if (this.capabilities.supportsRestartFrame) { const response = await this.send('restartFrame', args); this.fireSimulatedContinuedEvent(threadId); @@ -350,105 +354,105 @@ export class RawDebugSession implements IDisposable { return Promise.reject(new Error('restartFrame not supported')); } - stepInTargets(args: DebugProtocol.StepInTargetsArguments): Promise { + stepInTargets(args: DebugProtocol.StepInTargetsArguments): Promise { if (this.capabilities.supportsStepInTargetsRequest) { return this.send('stepInTargets', args); } return Promise.reject(new Error('stepInTargets not supported')); } - completions(args: DebugProtocol.CompletionsArguments, token: CancellationToken): Promise { + completions(args: DebugProtocol.CompletionsArguments, token: CancellationToken): Promise { if (this.capabilities.supportsCompletionsRequest) { return this.send('completions', args, token); } return Promise.reject(new Error('completions not supported')); } - setBreakpoints(args: DebugProtocol.SetBreakpointsArguments): Promise { + setBreakpoints(args: DebugProtocol.SetBreakpointsArguments): Promise { return this.send('setBreakpoints', args); } - setFunctionBreakpoints(args: DebugProtocol.SetFunctionBreakpointsArguments): Promise { + setFunctionBreakpoints(args: DebugProtocol.SetFunctionBreakpointsArguments): Promise { if (this.capabilities.supportsFunctionBreakpoints) { return this.send('setFunctionBreakpoints', args); } return Promise.reject(new Error('setFunctionBreakpoints not supported')); } - dataBreakpointInfo(args: DebugProtocol.DataBreakpointInfoArguments): Promise { + dataBreakpointInfo(args: DebugProtocol.DataBreakpointInfoArguments): Promise { if (this.capabilities.supportsDataBreakpoints) { return this.send('dataBreakpointInfo', args); } return Promise.reject(new Error('dataBreakpointInfo not supported')); } - setDataBreakpoints(args: DebugProtocol.SetDataBreakpointsArguments): Promise { + setDataBreakpoints(args: DebugProtocol.SetDataBreakpointsArguments): Promise { if (this.capabilities.supportsDataBreakpoints) { return this.send('setDataBreakpoints', args); } return Promise.reject(new Error('setDataBreakpoints not supported')); } - setExceptionBreakpoints(args: DebugProtocol.SetExceptionBreakpointsArguments): Promise { + setExceptionBreakpoints(args: DebugProtocol.SetExceptionBreakpointsArguments): Promise { return this.send('setExceptionBreakpoints', args); } - breakpointLocations(args: DebugProtocol.BreakpointLocationsArguments): Promise { + breakpointLocations(args: DebugProtocol.BreakpointLocationsArguments): Promise { if (this.capabilities.supportsBreakpointLocationsRequest) { return this.send('breakpointLocations', args); } return Promise.reject(new Error('breakpointLocations is not supported')); } - configurationDone(): Promise { + configurationDone(): Promise { if (this.capabilities.supportsConfigurationDoneRequest) { return this.send('configurationDone', null); } return Promise.reject(new Error('configurationDone not supported')); } - stackTrace(args: DebugProtocol.StackTraceArguments, token: CancellationToken): Promise { + stackTrace(args: DebugProtocol.StackTraceArguments, token: CancellationToken): Promise { return this.send('stackTrace', args, token); } - exceptionInfo(args: DebugProtocol.ExceptionInfoArguments): Promise { + exceptionInfo(args: DebugProtocol.ExceptionInfoArguments): Promise { if (this.capabilities.supportsExceptionInfoRequest) { return this.send('exceptionInfo', args); } return Promise.reject(new Error('exceptionInfo not supported')); } - scopes(args: DebugProtocol.ScopesArguments, token: CancellationToken): Promise { + scopes(args: DebugProtocol.ScopesArguments, token: CancellationToken): Promise { return this.send('scopes', args, token); } - variables(args: DebugProtocol.VariablesArguments, token?: CancellationToken): Promise { + variables(args: DebugProtocol.VariablesArguments, token?: CancellationToken): Promise { return this.send('variables', args, token); } - source(args: DebugProtocol.SourceArguments): Promise { + source(args: DebugProtocol.SourceArguments): Promise { return this.send('source', args); } - loadedSources(args: DebugProtocol.LoadedSourcesArguments): Promise { + loadedSources(args: DebugProtocol.LoadedSourcesArguments): Promise { if (this.capabilities.supportsLoadedSourcesRequest) { return this.send('loadedSources', args); } return Promise.reject(new Error('loadedSources not supported')); } - threads(): Promise { + threads(): Promise { return this.send('threads', null); } - evaluate(args: DebugProtocol.EvaluateArguments): Promise { + evaluate(args: DebugProtocol.EvaluateArguments): Promise { return this.send('evaluate', args); } - async stepBack(args: DebugProtocol.StepBackArguments): Promise { + async stepBack(args: DebugProtocol.StepBackArguments): Promise { if (this.capabilities.supportsStepBack) { const response = await this.send('stepBack', args); - if (response.body === undefined) { // TODO@AW why this check? + if (response && response.body === undefined) { // TODO@AW why this check? this.fireSimulatedContinuedEvent(args.threadId); } return response; @@ -456,10 +460,10 @@ export class RawDebugSession implements IDisposable { return Promise.reject(new Error('stepBack not supported')); } - async reverseContinue(args: DebugProtocol.ReverseContinueArguments): Promise { + async reverseContinue(args: DebugProtocol.ReverseContinueArguments): Promise { if (this.capabilities.supportsStepBack) { const response = await this.send('reverseContinue', args); - if (response.body === undefined) { // TODO@AW why this check? + if (response && response.body === undefined) { // TODO@AW why this check? this.fireSimulatedContinuedEvent(args.threadId); } return response; @@ -467,14 +471,14 @@ export class RawDebugSession implements IDisposable { return Promise.reject(new Error('reverseContinue not supported')); } - gotoTargets(args: DebugProtocol.GotoTargetsArguments): Promise { + gotoTargets(args: DebugProtocol.GotoTargetsArguments): Promise { if (this.capabilities.supportsGotoTargetsRequest) { return this.send('gotoTargets', args); } return Promise.reject(new Error('gotoTargets is not supported')); } - async goto(args: DebugProtocol.GotoArguments): Promise { + async goto(args: DebugProtocol.GotoArguments): Promise { if (this.capabilities.supportsGotoTargetsRequest) { const response = await this.send('goto', args); this.fireSimulatedContinuedEvent(args.threadId); @@ -484,11 +488,11 @@ export class RawDebugSession implements IDisposable { return Promise.reject(new Error('goto is not supported')); } - cancel(args: DebugProtocol.CancelArguments): Promise { + cancel(args: DebugProtocol.CancelArguments): Promise { return this.send('cancel', args); } - custom(request: string, args: any): Promise { + custom(request: string, args: any): Promise { return this.send(request, args); } @@ -619,12 +623,12 @@ export class RawDebugSession implements IDisposable { return this.extensionHostDebugService.openExtensionDevelopmentHostWindow(args, env, !!vscodeArgs.debugRenderer); } - private send(command: string, args: any, token?: CancellationToken, timeout?: number): Promise { + private send(command: string, args: any, token?: CancellationToken, timeout?: number): Promise { return new Promise((completeDispatch, errorDispatch) => { if (!this.debugAdapter) { if (this.inShutdown) { // We are in shutdown silently complete - completeDispatch(); + completeDispatch(undefined); } else { errorDispatch(new Error(nls.localize('noDebugAdapter', "No debugger available found. Can not send '{0}'.", command))); } diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 6bec8b7308..6e457f32d5 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -37,7 +37,6 @@ import { transparent, editorForeground } from 'vs/platform/theme/common/colorReg import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/debugActionViewItems'; import { CompletionContext, CompletionList, CompletionProviderRegistry, CompletionItem, completionKindFromString, CompletionItemKind, CompletionItemInsertTextRule } from 'vs/editor/common/modes'; -import { first } from 'vs/base/common/arrays'; import { ITreeNode, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; @@ -263,11 +262,15 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { } showPreviousValue(): void { - this.navigateHistory(true); + if (!this.isReadonly) { + this.navigateHistory(true); + } } showNextValue(): void { - this.navigateHistory(false); + if (!this.isReadonly) { + this.navigateHistory(false); + } } focusFilter(): void { @@ -306,7 +309,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { const replInputLineHeight = this.replInput.getOption(EditorOption.lineHeight); // Set the font size, font family, line height and align the twistie to be centered, and input theme color - this.styleElement.innerHTML = ` + this.styleElement.textContent = ` .repl .repl-tree .expression { font-size: ${fontSize}px; font-family: ${fontFamily}; @@ -356,7 +359,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { if (focusedSession) { session = focusedSession; } else if (!treeInput || sessionsToIgnore.has(treeInput)) { - session = first(this.debugService.getModel().getSessions(true), s => !sessionsToIgnore.has(s)) || undefined; + session = this.debugService.getModel().getSessions(true).find(s => !sessionsToIgnore.has(s)); } } if (session) { @@ -393,7 +396,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { acceptReplInput(): void { const session = this.tree.getInput(); - if (session) { + if (session && !this.isReadonly) { session.addReplExpression(this.debugService.getViewModel().focusedStackFrame, this.replInput.getValue()); revealLastElement(this.tree); this.history.add(this.replInput.getValue()); @@ -531,7 +534,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { this.replDelegate = new ReplDelegate(this.configurationService); const wordWrap = this.configurationService.getValue('debug').console.wordWrap; - dom.toggleClass(treeContainer, 'word-wrap', wordWrap); + treeContainer.classList.toggle('word-wrap', wordWrap); const linkDetector = this.instantiationService.createInstance(LinkDetector); this.tree = >this.instantiationService.createInstance( WorkbenchAsyncDataTree, @@ -606,8 +609,8 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { this._register(this.replInput.onDidFocusEditorText(() => this.updateInputDecoration())); this._register(this.replInput.onDidBlurEditorText(() => this.updateInputDecoration())); - this._register(dom.addStandardDisposableListener(this.replInputContainer, dom.EventType.FOCUS, () => dom.addClass(this.replInputContainer, 'synthetic-focus'))); - this._register(dom.addStandardDisposableListener(this.replInputContainer, dom.EventType.BLUR, () => dom.removeClass(this.replInputContainer, 'synthetic-focus'))); + this._register(dom.addStandardDisposableListener(this.replInputContainer, dom.EventType.FOCUS, () => this.replInputContainer.classList.add('synthetic-focus'))); + this._register(dom.addStandardDisposableListener(this.replInputContainer, dom.EventType.BLUR, () => this.replInputContainer.classList.remove('synthetic-focus'))); } private onContextMenu(e: ITreeContextMenuEvent): void { diff --git a/src/vs/workbench/contrib/debug/browser/replFilter.ts b/src/vs/workbench/contrib/debug/browser/replFilter.ts index e9f6500a0f..2ec1e6804e 100644 --- a/src/vs/workbench/contrib/debug/browser/replFilter.ts +++ b/src/vs/workbench/contrib/debug/browser/replFilter.ts @@ -5,7 +5,6 @@ import { matchesFuzzy } from 'vs/base/common/filters'; import { splitGlobAware } from 'vs/base/common/glob'; -import * as strings from 'vs/base/common/strings'; import { ITreeFilter, TreeVisibility, TreeFilterResult } from 'vs/base/browser/ui/tree/tree'; import { IReplElement } from 'vs/workbench/contrib/debug/common/debug'; import * as DOM from 'vs/base/browser/dom'; @@ -42,7 +41,7 @@ export class ReplFilter implements ITreeFilter { if (query && query !== '') { const filters = splitGlobAware(query, ',').map(s => s.trim()).filter(s => !!s.length); for (const f of filters) { - if (strings.startsWith(f, '!')) { + if (f.startsWith('!')) { this._parsedQueries.push({ type: 'exclude', query: f.slice(1) }); } else { this._parsedQueries.push({ type: 'include', query: f }); @@ -119,7 +118,7 @@ export class ReplFilterActionViewItem extends BaseActionViewItem { render(container: HTMLElement): void { this.container = container; - DOM.addClass(this.container, 'repl-panel-filter-container'); + this.container.classList.add('repl-panel-filter-container'); this.element = DOM.append(this.container, DOM.$('')); this.element.className = this.class; diff --git a/src/vs/workbench/contrib/debug/browser/replViewer.ts b/src/vs/workbench/contrib/debug/browser/replViewer.ts index 287d61f733..58c87b5e31 100644 --- a/src/vs/workbench/contrib/debug/browser/replViewer.ts +++ b/src/vs/workbench/contrib/debug/browser/replViewer.ts @@ -147,7 +147,7 @@ export class ReplSimpleElementsRenderer implements ITreeRenderer element.sourceData; @@ -226,7 +226,7 @@ export class ReplRawObjectsRenderer implements ITreeRenderer .items-container > .statusbar-item.has-beak:before { border-bottom-color: ${backgroundColor} !important; }`; + this.styleElement.textContent = `.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-beak:before { border-bottom-color: ${backgroundColor} !important; }`; } private getColorKey(noFolderColor: string, debuggingColor: string, normalColor: string): string { diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 74148ee13c..fd569a8866 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -114,8 +114,8 @@ export class VariablesView extends ViewPane { renderBody(container: HTMLElement): void { super.renderBody(container); - dom.addClass(this.element, 'debug-pane'); - dom.addClass(container, 'debug-variables'); + this.element.classList.add('debug-pane'); + container.classList.add('debug-variables'); const treeContainer = renderViewTree(container); this.tree = >this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'VariablesView', treeContainer, new VariablesDelegate(), diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index 5b8cf3d14a..b3fdfdf23b 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -5,7 +5,6 @@ import * as nls from 'vs/nls'; import { RunOnceScheduler } from 'vs/base/common/async'; -import * as dom from 'vs/base/browser/dom'; import { CollapseAction } from 'vs/workbench/browser/viewlet'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IDebugService, IExpression, CONTEXT_WATCH_EXPRESSIONS_FOCUSED } from 'vs/workbench/contrib/debug/common/debug'; @@ -68,8 +67,8 @@ export class WatchExpressionsView extends ViewPane { renderBody(container: HTMLElement): void { super.renderBody(container); - dom.addClass(this.element, 'debug-pane'); - dom.addClass(container, 'debug-watch'); + this.element.classList.add('debug-pane'); + container.classList.add('debug-watch'); const treeContainer = renderViewTree(container); const expressionsRenderer = this.instantiationService.createInstance(WatchExpressionsRenderer); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index bcb95cb592..e819663480 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -238,17 +238,18 @@ export interface IDebugSession extends ITreeElement { breakpointsLocations(uri: uri, lineNumber: number): Promise; getDebugProtocolBreakpoint(breakpointId: string): DebugProtocol.Breakpoint | undefined; - stackTrace(threadId: number, startFrame: number, levels: number, token: CancellationToken): Promise; + stackTrace(threadId: number, startFrame: number, levels: number, token: CancellationToken): Promise; exceptionInfo(threadId: number): Promise; - scopes(frameId: number, threadId: number): Promise; - variables(variablesReference: number, threadId: number | undefined, filter: 'indexed' | 'named' | undefined, start: number | undefined, count: number | undefined): Promise; - evaluate(expression: string, frameId?: number, context?: string): Promise; - customRequest(request: string, args: any): Promise; - cancel(progressId: string): Promise; + scopes(frameId: number, threadId: number): Promise; + variables(variablesReference: number, threadId: number | undefined, filter: 'indexed' | 'named' | undefined, start: number | undefined, count: number | undefined): Promise; + evaluate(expression: string, frameId?: number, context?: string): Promise; + customRequest(request: string, args: any): Promise; + cancel(progressId: string): Promise; restartFrame(frameId: number, threadId: number): Promise; next(threadId: number): Promise; stepIn(threadId: number, targetId?: number): Promise; + stepInTargets(frameId: number): Promise<{ id: number, label: string }[] | undefined>; stepOut(threadId: number): Promise; stepBack(threadId: number): Promise; continue(threadId: number): Promise; @@ -256,14 +257,13 @@ export interface IDebugSession extends ITreeElement { pause(threadId: number): Promise; terminateThreads(threadIds: number[]): Promise; - stepInTargets(frameId: number): Promise<{ id: number, label: string }[]>; - completions(frameId: number | undefined, threadId: number, text: string, position: Position, overwriteBefore: number, token: CancellationToken): Promise; - setVariable(variablesReference: number | undefined, name: string, value: string): Promise; - loadSource(resource: uri): Promise; + completions(frameId: number | undefined, threadId: number, text: string, position: Position, overwriteBefore: number, token: CancellationToken): Promise; + setVariable(variablesReference: number | undefined, name: string, value: string): Promise; + loadSource(resource: uri): Promise; getLoadedSources(): Promise; - gotoTargets(source: DebugProtocol.Source, line: number, column?: number): Promise; - goto(threadId: number, targetId: number): Promise; + gotoTargets(source: DebugProtocol.Source, line: number, column?: number): Promise; + goto(threadId: number, targetId: number): Promise; } export interface IThread extends ITreeElement { @@ -671,7 +671,7 @@ export interface IConfigurationManager { name: string | undefined; }; - selectConfiguration(launch: ILaunch | undefined, name?: string, config?: IConfig): void; + selectConfiguration(launch: ILaunch | undefined, name?: string, config?: IConfig): Promise; getLaunches(): ReadonlyArray; @@ -692,7 +692,7 @@ export interface IConfigurationManager { isDebuggerInterestedInLanguage(language: string): boolean; hasDebugConfigurationProvider(debugType: string): boolean; - getDynamicProviders(): Promise<{ label: string, pick: () => Promise<{ launch: ILaunch, config: IConfig } | undefined> }[]>; + getDynamicProviders(): Promise<{ label: string, provider: IDebugConfigurationProvider, pick: () => Promise<{ launch: ILaunch, config: IConfig } | undefined> }[]>; registerDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): IDisposable; unregisterDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): void; diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 641b8d309b..cd6b3671cd 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -10,7 +10,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { generateUuid } from 'vs/base/common/uuid'; import { RunOnceScheduler } from 'vs/base/common/async'; import { isString, isUndefinedOrNull } from 'vs/base/common/types'; -import { distinct, lastIndex, first } from 'vs/base/common/arrays'; +import { distinct, lastIndex } from 'vs/base/common/arrays'; import { Range, IRange } from 'vs/editor/common/core/range'; import { ITreeElement, IExpression, IExpressionContainer, IDebugSession, IStackFrame, IExceptionBreakpoint, IBreakpoint, IFunctionBreakpoint, IDebugModel, @@ -421,7 +421,7 @@ export class Thread implements IThread { } getTopStackFrame(): IStackFrame | undefined { - return first(this.getCallStack(), sf => !!(sf && sf.source && sf.source.available && sf.source.presentationHint !== 'deemphasize'), undefined); + return this.getCallStack().find(sf => !!(sf && sf.source && sf.source.available && sf.source.presentationHint !== 'deemphasize')); } get stateLabel(): string { diff --git a/src/vs/workbench/contrib/debug/common/debugger.ts b/src/vs/workbench/contrib/debug/common/debugger.ts index dd09512222..d904692484 100644 --- a/src/vs/workbench/contrib/debug/common/debugger.ts +++ b/src/vs/workbench/contrib/debug/common/debugger.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as strings from 'vs/base/common/strings'; import * as objects from 'vs/base/common/objects'; import { isObject } from 'vs/base/common/types'; import { IJSONSchema, IJSONSchemaSnippet } from 'vs/base/common/jsonSchema'; @@ -171,7 +170,7 @@ export class Debugger implements IDebugger { // fix formatting const editorConfig = this.configurationService.getValue(); if (editorConfig.editor && editorConfig.editor.insertSpaces) { - content = content.replace(new RegExp('\t', 'g'), strings.repeat(' ', editorConfig.editor.tabSize)); + content = content.replace(new RegExp('\t', 'g'), ' '.repeat(editorConfig.editor.tabSize)); } return Promise.resolve(content); diff --git a/src/vs/workbench/contrib/debug/common/replModel.ts b/src/vs/workbench/contrib/debug/common/replModel.ts index 01ba94178a..ba43fea7f9 100644 --- a/src/vs/workbench/contrib/debug/common/replModel.ts +++ b/src/vs/workbench/contrib/debug/common/replModel.ts @@ -10,7 +10,6 @@ import { ExpressionContainer } from 'vs/workbench/contrib/debug/common/debugMode import { isString, isUndefinedOrNull, isObject } from 'vs/base/common/types'; import { basenameOrAuthority } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import { endsWith } from 'vs/base/common/strings'; import { generateUuid } from 'vs/base/common/uuid'; import { Emitter } from 'vs/base/common/event'; @@ -203,7 +202,7 @@ export class ReplModel { if (typeof data === 'string') { const previousElement = this.replElements.length ? this.replElements[this.replElements.length - 1] : undefined; - if (previousElement instanceof SimpleReplElement && previousElement.severity === sev && !endsWith(previousElement.value, '\n') && !endsWith(previousElement.value, '\r\n')) { + if (previousElement instanceof SimpleReplElement && previousElement.severity === sev && !previousElement.value.endsWith('\n') && !previousElement.value.endsWith('\r\n')) { previousElement.value += data; this._onDidChangeElements.fire(); } else { diff --git a/src/vs/workbench/contrib/debug/test/electron-browser/debugANSIHandling.test.ts b/src/vs/workbench/contrib/debug/test/electron-browser/debugANSIHandling.test.ts index fc27d11108..a7b2774609 100644 --- a/src/vs/workbench/contrib/debug/test/electron-browser/debugANSIHandling.test.ts +++ b/src/vs/workbench/contrib/debug/test/electron-browser/debugANSIHandling.test.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as dom from 'vs/base/browser/dom'; import { generateUuid } from 'vs/base/common/uuid'; import { appendStylizedStringToContainer, handleANSIOutput, calcANSI8bitColor } from 'vs/workbench/contrib/debug/browser/debugANSIHandling'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; @@ -58,8 +57,8 @@ suite.skip('Debug - ANSI Handling', () => { child = root.firstChild!; if (child instanceof HTMLSpanElement) { assert.equal('content1', child.textContent); - assert(dom.hasClass(child, 'class1')); - assert(dom.hasClass(child, 'class2')); + assert(child.classList.contains('class1')); + assert(child.classList.contains('class2')); } else { assert.fail('Unexpected assertion error'); } @@ -67,8 +66,8 @@ suite.skip('Debug - ANSI Handling', () => { child = root.lastChild!; if (child instanceof HTMLSpanElement) { assert.equal('content2', child.textContent); - assert(dom.hasClass(child, 'class2')); - assert(dom.hasClass(child, 'class3')); + assert(child.classList.contains('class2')); + assert(child.classList.contains('class3')); } else { assert.fail('Unexpected assertion error'); } @@ -143,17 +142,17 @@ suite.skip('Debug - ANSI Handling', () => { // Bold code assertSingleSequenceElement('\x1b[1m', (child) => { - assert(dom.hasClass(child, 'code-bold'), 'Bold formatting not detected after bold ANSI code.'); + assert(child.classList.contains('code-bold'), 'Bold formatting not detected after bold ANSI code.'); }); // Italic code assertSingleSequenceElement('\x1b[3m', (child) => { - assert(dom.hasClass(child, 'code-italic'), 'Italic formatting not detected after italic ANSI code.'); + assert(child.classList.contains('code-italic'), 'Italic formatting not detected after italic ANSI code.'); }); // Underline code assertSingleSequenceElement('\x1b[4m', (child) => { - assert(dom.hasClass(child, 'code-underline'), 'Underline formatting not detected after underline ANSI code.'); + assert(child.classList.contains('code-underline'), 'Underline formatting not detected after underline ANSI code.'); }); for (let i = 30; i <= 37; i++) { @@ -161,12 +160,12 @@ suite.skip('Debug - ANSI Handling', () => { // Foreground colour class assertSingleSequenceElement('\x1b[' + i + 'm', (child) => { - assert(dom.hasClass(child, customClassName), `Custom foreground class not found on element after foreground ANSI code #${i}.`); + assert(child.classList.contains(customClassName), `Custom foreground class not found on element after foreground ANSI code #${i}.`); }); // Cancellation code removes colour class assertSingleSequenceElement('\x1b[' + i + ';39m', (child) => { - assert(dom.hasClass(child, customClassName) === false, 'Custom foreground class still found after foreground cancellation code.'); + assert(child.classList.contains(customClassName) === false, 'Custom foreground class still found after foreground cancellation code.'); assertInlineColor(child, 'foreground', undefined, 'Custom color style still found after foreground cancellation code.'); }); } @@ -176,12 +175,12 @@ suite.skip('Debug - ANSI Handling', () => { // Foreground colour class assertSingleSequenceElement('\x1b[' + i + 'm', (child) => { - assert(dom.hasClass(child, customClassName), `Custom background class not found on element after background ANSI code #${i}.`); + assert(child.classList.contains(customClassName), `Custom background class not found on element after background ANSI code #${i}.`); }); // Cancellation code removes colour class assertSingleSequenceElement('\x1b[' + i + ';49m', (child) => { - assert(dom.hasClass(child, customClassName) === false, 'Custom background class still found after background cancellation code.'); + assert(child.classList.contains(customClassName) === false, 'Custom background class still found after background cancellation code.'); assertInlineColor(child, 'foreground', undefined, 'Custom color style still found after background cancellation code.'); }); } @@ -190,31 +189,31 @@ suite.skip('Debug - ANSI Handling', () => { assertSingleSequenceElement('\x1b[1;3;4;30;41m', (child) => { assert.equal(5, child.classList.length, 'Incorrect number of classes found for different ANSI codes.'); - assert(dom.hasClass(child, 'code-bold')); - assert(dom.hasClass(child, 'code-italic'), 'Different ANSI codes should not cancel each other.'); - assert(dom.hasClass(child, 'code-underline'), 'Different ANSI codes should not cancel each other.'); - assert(dom.hasClass(child, 'code-foreground-colored'), 'Different ANSI codes should not cancel each other.'); - assert(dom.hasClass(child, 'code-background-colored'), 'Different ANSI codes should not cancel each other.'); + assert(child.classList.contains('code-bold')); + assert(child.classList.contains('code-italic'), 'Different ANSI codes should not cancel each other.'); + assert(child.classList.contains('code-underline'), 'Different ANSI codes should not cancel each other.'); + assert(child.classList.contains('code-foreground-colored'), 'Different ANSI codes should not cancel each other.'); + assert(child.classList.contains('code-background-colored'), 'Different ANSI codes should not cancel each other.'); }); // New foreground codes don't remove old background codes and vice versa assertSingleSequenceElement('\x1b[40;31;42;33m', (child) => { assert.equal(2, child.classList.length); - assert(dom.hasClass(child, 'code-background-colored'), 'New foreground ANSI code should not cancel existing background formatting.'); - assert(dom.hasClass(child, 'code-foreground-colored'), 'New background ANSI code should not cancel existing foreground formatting.'); + assert(child.classList.contains('code-background-colored'), 'New foreground ANSI code should not cancel existing background formatting.'); + assert(child.classList.contains('code-foreground-colored'), 'New background ANSI code should not cancel existing foreground formatting.'); }); // Duplicate codes do not change output assertSingleSequenceElement('\x1b[1;1;4;1;4;4;1;4m', (child) => { - assert(dom.hasClass(child, 'code-bold'), 'Duplicate formatting codes should have no effect.'); - assert(dom.hasClass(child, 'code-underline'), 'Duplicate formatting codes should have no effect.'); + assert(child.classList.contains('code-bold'), 'Duplicate formatting codes should have no effect.'); + assert(child.classList.contains('code-underline'), 'Duplicate formatting codes should have no effect.'); }); // Extra terminating semicolon does not change output assertSingleSequenceElement('\x1b[1;4;m', (child) => { - assert(dom.hasClass(child, 'code-bold'), 'Extra semicolon after ANSI codes should have no effect.'); - assert(dom.hasClass(child, 'code-underline'), 'Extra semicolon after ANSI codes should have no effect.'); + assert(child.classList.contains('code-bold'), 'Extra semicolon after ANSI codes should have no effect.'); + assert(child.classList.contains('code-underline'), 'Extra semicolon after ANSI codes should have no effect.'); }); // Cancellation code removes multiple codes @@ -232,12 +231,12 @@ suite.skip('Debug - ANSI Handling', () => { // As these are controlled by theme, difficult to check actual color value // Foreground codes should add standard classes assertSingleSequenceElement('\x1b[38;5;' + i + 'm', (child) => { - assert(dom.hasClass(child, 'code-foreground-colored'), `Custom color class not found after foreground 8-bit color code 38;5;${i}`); + assert(child.classList.contains('code-foreground-colored'), `Custom color class not found after foreground 8-bit color code 38;5;${i}`); }); // Background codes should add standard classes assertSingleSequenceElement('\x1b[48;5;' + i + 'm', (child) => { - assert(dom.hasClass(child, 'code-background-colored'), `Custom color class not found after background 8-bit color code 48;5;${i}`); + assert(child.classList.contains('code-background-colored'), `Custom color class not found after background 8-bit color code 48;5;${i}`); }); } @@ -245,13 +244,13 @@ suite.skip('Debug - ANSI Handling', () => { for (let i = 16; i <= 255; i++) { // Foreground codes should add custom class and inline style assertSingleSequenceElement('\x1b[38;5;' + i + 'm', (child) => { - assert(dom.hasClass(child, 'code-foreground-colored'), `Custom color class not found after foreground 8-bit color code 38;5;${i}`); + assert(child.classList.contains('code-foreground-colored'), `Custom color class not found after foreground 8-bit color code 38;5;${i}`); assertInlineColor(child, 'foreground', (calcANSI8bitColor(i) as RGBA), `Incorrect or no color styling found after foreground 8-bit color code 38;5;${i}`); }); // Background codes should add custom class and inline style assertSingleSequenceElement('\x1b[48;5;' + i + 'm', (child) => { - assert(dom.hasClass(child, 'code-background-colored'), `Custom color class not found after background 8-bit color code 48;5;${i}`); + assert(child.classList.contains('code-background-colored'), `Custom color class not found after background 8-bit color code 48;5;${i}`); assertInlineColor(child, 'background', (calcANSI8bitColor(i) as RGBA), `Incorrect or no color styling found after background 8-bit color code 48;5;${i}`); }); } @@ -263,7 +262,7 @@ suite.skip('Debug - ANSI Handling', () => { // Should ignore any codes after the ones needed to determine color assertSingleSequenceElement('\x1b[48;5;100;42;77;99;4;24m', (child) => { - assert(dom.hasClass(child, 'code-background-colored')); + assert(child.classList.contains('code-background-colored')); assert.equal(1, child.classList.length); assertInlineColor(child, 'background', (calcANSI8bitColor(100) as RGBA)); }); @@ -277,13 +276,13 @@ suite.skip('Debug - ANSI Handling', () => { let color = new RGBA(r, g, b); // Foreground codes should add class and inline style assertSingleSequenceElement(`\x1b[38;2;${r};${g};${b}m`, (child) => { - assert(dom.hasClass(child, 'code-foreground-colored'), 'DOM should have "code-foreground-colored" class for advanced ANSI colors.'); + assert(child.classList.contains('code-foreground-colored'), 'DOM should have "code-foreground-colored" class for advanced ANSI colors.'); assertInlineColor(child, 'foreground', color); }); // Background codes should add class and inline style assertSingleSequenceElement(`\x1b[48;2;${r};${g};${b}m`, (child) => { - assert(dom.hasClass(child, 'code-background-colored'), 'DOM should have "code-foreground-colored" class for advanced ANSI colors.'); + assert(child.classList.contains('code-background-colored'), 'DOM should have "code-foreground-colored" class for advanced ANSI colors.'); assertInlineColor(child, 'background', color); }); } @@ -303,7 +302,7 @@ suite.skip('Debug - ANSI Handling', () => { // Should ignore any codes after the ones needed to determine color assertSingleSequenceElement('\x1b[48;2;100;42;77;99;200;75m', (child) => { - assert(dom.hasClass(child, 'code-background-colored'), `Color code with extra (valid) items "48;2;100;42;77;99;200;75" should still treat initial part as valid code and add class "code-background-custom".`); + assert(child.classList.contains('code-background-colored'), `Color code with extra (valid) items "48;2;100;42;77;99;200;75" should still treat initial part as valid code and add class "code-background-custom".`); assert.equal(1, child.classList.length, `Color code with extra items "48;2;100;42;77;99;200;75" should add one and only one class. (classes found: ${child.classList}).`); assertInlineColor(child, 'background', new RGBA(100, 42, 77), `Color code "48;2;100;42;77;99;200;75" should style background-color as rgb(100,42,77).`); }); @@ -337,35 +336,35 @@ suite.skip('Debug - ANSI Handling', () => { // Multiple codes affect the same text assertSingleSequenceElement('\x1b[1m\x1b[3m\x1b[4m\x1b[32m', (child) => { - assert(dom.hasClass(child, 'code-bold'), 'Bold class not found after multiple different ANSI codes.'); - assert(dom.hasClass(child, 'code-italic'), 'Italic class not found after multiple different ANSI codes.'); - assert(dom.hasClass(child, 'code-underline'), 'Underline class not found after multiple different ANSI codes.'); - assert(dom.hasClass(child, 'code-foreground-colored'), 'Foreground color class not found after multiple different ANSI codes.'); + assert(child.classList.contains('code-bold'), 'Bold class not found after multiple different ANSI codes.'); + assert(child.classList.contains('code-italic'), 'Italic class not found after multiple different ANSI codes.'); + assert(child.classList.contains('code-underline'), 'Underline class not found after multiple different ANSI codes.'); + assert(child.classList.contains('code-foreground-colored'), 'Foreground color class not found after multiple different ANSI codes.'); }); // Consecutive codes do not affect previous ones assertMultipleSequenceElements('\x1b[1mbold\x1b[32mgreen\x1b[4munderline\x1b[3mitalic\x1b[0mnothing', [ (bold) => { assert.equal(1, bold.classList.length); - assert(dom.hasClass(bold, 'code-bold'), 'Bold class not found after bold ANSI code.'); + assert(bold.classList.contains('code-bold'), 'Bold class not found after bold ANSI code.'); }, (green) => { assert.equal(2, green.classList.length); - assert(dom.hasClass(green, 'code-bold'), 'Bold class not found after both bold and color ANSI codes.'); - assert(dom.hasClass(green, 'code-foreground-colored'), 'Color class not found after color ANSI code.'); + assert(green.classList.contains('code-bold'), 'Bold class not found after both bold and color ANSI codes.'); + assert(green.classList.contains('code-foreground-colored'), 'Color class not found after color ANSI code.'); }, (underline) => { assert.equal(3, underline.classList.length); - assert(dom.hasClass(underline, 'code-bold'), 'Bold class not found after bold, color, and underline ANSI codes.'); - assert(dom.hasClass(underline, 'code-foreground-colored'), 'Color class not found after color and underline ANSI codes.'); - assert(dom.hasClass(underline, 'code-underline'), 'Underline class not found after underline ANSI code.'); + assert(underline.classList.contains('code-bold'), 'Bold class not found after bold, color, and underline ANSI codes.'); + assert(underline.classList.contains('code-foreground-colored'), 'Color class not found after color and underline ANSI codes.'); + assert(underline.classList.contains('code-underline'), 'Underline class not found after underline ANSI code.'); }, (italic) => { assert.equal(4, italic.classList.length); - assert(dom.hasClass(italic, 'code-bold'), 'Bold class not found after bold, color, underline, and italic ANSI codes.'); - assert(dom.hasClass(italic, 'code-foreground-colored'), 'Color class not found after color, underline, and italic ANSI codes.'); - assert(dom.hasClass(italic, 'code-underline'), 'Underline class not found after underline and italic ANSI codes.'); - assert(dom.hasClass(italic, 'code-italic'), 'Italic class not found after italic ANSI code.'); + assert(italic.classList.contains('code-bold'), 'Bold class not found after bold, color, underline, and italic ANSI codes.'); + assert(italic.classList.contains('code-foreground-colored'), 'Color class not found after color, underline, and italic ANSI codes.'); + assert(italic.classList.contains('code-underline'), 'Underline class not found after underline and italic ANSI codes.'); + assert(italic.classList.contains('code-italic'), 'Italic class not found after italic ANSI code.'); }, (nothing) => { assert.equal(0, nothing.classList.length, 'One or more style classes still found after reset ANSI code.'); @@ -376,21 +375,21 @@ suite.skip('Debug - ANSI Handling', () => { assertMultipleSequenceElements('\x1b[34msimple\x1b[38;2;100;100;100m24bit\x1b[38;5;3m8bitsimple\x1b[38;5;101m8bitadvanced', [ (simple) => { assert.equal(1, simple.classList.length, 'Foreground ANSI color code should add one class.'); - assert(dom.hasClass(simple, 'code-foreground-colored'), 'Foreground ANSI color codes should add custom foreground color class.'); + assert(simple.classList.contains('code-foreground-colored'), 'Foreground ANSI color codes should add custom foreground color class.'); }, (adv24Bit) => { assert.equal(1, adv24Bit.classList.length, 'Multiple foreground ANSI color codes should only add a single class.'); - assert(dom.hasClass(adv24Bit, 'code-foreground-colored'), 'Foreground ANSI color codes should add custom foreground color class.'); + assert(adv24Bit.classList.contains('code-foreground-colored'), 'Foreground ANSI color codes should add custom foreground color class.'); assertInlineColor(adv24Bit, 'foreground', new RGBA(100, 100, 100), '24-bit RGBA ANSI color code (100,100,100) should add matching color inline style.'); }, (adv8BitSimple) => { assert.equal(1, adv8BitSimple.classList.length, 'Multiple foreground ANSI color codes should only add a single class.'); - assert(dom.hasClass(adv8BitSimple, 'code-foreground-colored'), 'Foreground ANSI color codes should add custom foreground color class.'); + assert(adv8BitSimple.classList.contains('code-foreground-colored'), 'Foreground ANSI color codes should add custom foreground color class.'); // Won't assert color because it's theme based }, (adv8BitAdvanced) => { assert.equal(1, adv8BitAdvanced.classList.length, 'Multiple foreground ANSI color codes should only add a single class.'); - assert(dom.hasClass(adv8BitAdvanced, 'code-foreground-colored'), 'Foreground ANSI color codes should add custom foreground color class.'); + assert(adv8BitAdvanced.classList.contains('code-foreground-colored'), 'Foreground ANSI color codes should add custom foreground color class.'); } ], 4); 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 cdb949a14f..6fc6f15a90 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 @@ -29,7 +29,6 @@ import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/ex import { ExtensionType } from 'vs/platform/extensions/common/extensions'; 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'; @@ -108,68 +107,6 @@ suite('Experiment Service', () => { }); }); - test('Simple Experiment Test', () => { - experimentData = { - experiments: [ - { - id: 'experiment1' - }, - { - id: 'experiment2', - enabled: false - }, - { - id: 'experiment3', - enabled: true - }, - { - id: 'experiment4', - enabled: true, - condition: { - - } - }, - { - id: 'experiment5', - enabled: true, - condition: { - insidersOnly: true - } - } - ] - }; - - testObject = instantiationService.createInstance(TestExperimentService); - const tests: Promise[] = []; - tests.push(testObject.getExperimentById('experiment1')); - tests.push(testObject.getExperimentById('experiment2')); - tests.push(testObject.getExperimentById('experiment3')); - tests.push(testObject.getExperimentById('experiment4')); - tests.push(testObject.getExperimentById('experiment5')); - - return Promise.all(tests).then(results => { - assert.equal(results[0].id, 'experiment1'); - assert.equal(results[0].enabled, false); - assert.equal(results[0].state, ExperimentState.NoRun); - - assert.equal(results[1].id, 'experiment2'); - assert.equal(results[1].enabled, false); - assert.equal(results[1].state, ExperimentState.NoRun); - - assert.equal(results[2].id, 'experiment3'); - assert.equal(results[2].enabled, true); - assert.equal(results[2].state, ExperimentState.Run); - - assert.equal(results[3].id, 'experiment4'); - assert.equal(results[3].enabled, true); - assert.equal(results[3].state, ExperimentState.Run); - - assert.equal(results[4].id, 'experiment5'); - assert.equal(results[4].enabled, true); - assert.equal(results[4].state, ExperimentState.Run); - }); - }); - test('filters out experiments with newer schema versions', async () => { experimentData = { experiments: [ @@ -247,26 +184,6 @@ suite('Experiment Service', () => { }); }); - test('OldUsers experiment shouldnt be enabled for new users', () => { - experimentData = { - experiments: [ - { - id: 'experiment1', - enabled: true, - condition: { - newUser: false - } - } - ] - }; - - testObject = instantiationService.createInstance(TestExperimentService); - return testObject.getExperimentById('experiment1').then(result => { - assert.equal(result.enabled, true); - assert.equal(result.state, ExperimentState.NoRun); - }); - }); - test('Experiment without NewUser condition should be enabled for old users', () => { experimentData = { experiments: [ @@ -514,43 +431,6 @@ suite('Experiment Service', () => { assert(didGetCall); }); - test('Activation events run experiments in realtime', async () => { - experimentData = { - experiments: [ - { - id: 'experiment1', - enabled: true, - condition: { - activationEvent: { - event: 'my:event', - minEvents: 2, - } - } - } - ] - }; - - let calls = 0; - instantiationService.stub(IStorageService, 'get', (a: string, b: StorageScope, c?: string) => { - return a === 'experimentEventRecord-my-event' - ? JSON.stringify({ count: [++calls, 0, 0, 0, 0, 0, 0], mostRecentBucket: Date.now() }) - : undefined; - }); - - const enabledListener = sinon.stub(); - testObject = instantiationService.createInstance(TestExperimentService); - testObject.onExperimentEnabled(enabledListener); - - assert.equal((await testObject.getExperimentById('experiment1')).state, ExperimentState.Evaluating); - assert.equal((await testObject.getExperimentById('experiment1')).state, ExperimentState.Evaluating); - assert.equal(enabledListener.callCount, 0); - - activationEvent.fire({ event: 'my:event', activation: Promise.resolve() }); - await timeout(1); - assert.equal(enabledListener.callCount, 1); - assert.equal((await testObject.getExperimentById('experiment1')).state, ExperimentState.Run); - }); - test('Experiment not matching user setting should be disabled', () => { experimentData = { experiments: [ @@ -775,70 +655,6 @@ suite('Experiment Service', () => { }); }); - test('Curated list shouldnt be available if experiment is disabled.', () => { - const promptText = 'Hello there! Can you see this?'; - const curatedExtensionsKey = 'AzureDeploy'; - const curatedExtensionsList = ['uninstalled-extention-id1', 'uninstalled-extention-id2']; - experimentData = { - experiments: [ - { - id: 'experiment1', - enabled: false, - action: { - type: 'Prompt', - properties: { - promptText, - commands: [ - { - text: 'Search Marketplace', - dontShowAgain: true, - curatedExtensionsKey, - curatedExtensionsList - }, - { - text: 'No' - } - ] - } - } - } - ] - }; - - testObject = instantiationService.createInstance(TestExperimentService); - return testObject.getExperimentById('experiment1').then(result => { - assert.equal(result.enabled, false); - assert.equal(result.action?.type, 'Prompt'); - assert.equal(result.state, ExperimentState.NoRun); - return testObject.getCuratedExtensionsList(curatedExtensionsKey).then(curatedList => { - assert.equal(curatedList.length, 0); - }); - }); - }); - - test('Maps action2 to action.', () => { - experimentData = { - experiments: [ - { - id: 'experiment1', - enabled: false, - action2: { - type: 'Prompt', - properties: { - promptText: 'Hello world', - commands: [] - } - } - } - ] - }; - - testObject = instantiationService.createInstance(TestExperimentService); - return testObject.getExperimentById('experiment1').then(result => { - assert.equal(result.action?.type, 'Prompt'); - }); - }); - test('Experiment that is disabled or deleted should be removed from storage', () => { experimentData = { experiments: [ diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 5a5f5146df..80dd8c157c 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -14,7 +14,7 @@ import { Action, IAction } from 'vs/base/common/actions'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { dispose, toDisposable, Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { domEvent } from 'vs/base/browser/event'; -import { append, $, addClass, removeClass, finalHandler, join, toggleClass, hide, show, addDisposableListener, EventType } from 'vs/base/browser/dom'; +import { append, $, finalHandler, join, hide, show, addDisposableListener, EventType } from 'vs/base/browser/dom'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -37,7 +37,6 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { Color } from 'vs/base/common/color'; -import { assign } from 'vs/base/common/objects'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtensionsTree, ExtensionData, ExtensionsGridView, getExtensions } from 'vs/workbench/contrib/extensions/browser/extensionsViewer'; @@ -363,11 +362,11 @@ export class ExtensionEditor extends EditorPane { ] } */ - this.telemetryService.publicLog('extensionGallery:openExtension', assign(extension.telemetryData, recommendationsData)); + this.telemetryService.publicLog('extensionGallery:openExtension', { ...extension.telemetryData, ...recommendationsData }); - toggleClass(template.name, 'clickable', !!extension.url); - toggleClass(template.publisher, 'clickable', !!extension.publisher); // {{SQL CARBON EDIT}} !!extension.url -> !!extension.publisher, for ADS we don't have marketplace website, but still want to make it clickable and filter extensions by publisher - // toggleClass(template.rating, 'clickable', !!extension.url); // {{SQL CARBON EDIT}} remove rating widget + template.name.classList.toggle('clickable', !!extension.url); + template.publisher.classList.toggle('clickable', !!extension.url); // {{SQL CARBON EDIT}} !!extension.url -> !!extension.publisher, for ADS we don't have marketplace website, but still want to make it clickable and filter extensions by publisher + // template.rating.classList.toggle('clickable', !!extension.url); // {{SQL CARBON EDIT}} remove rating widget if (extension.url) { this.transientDisposables.add(this.onClick(template.name, () => this.openerService.open(URI.parse(extension.url!)))); // this.transientDisposables.add(this.onClick(template.rating, () => this.openerService.open(URI.parse(`${extension.url}#review-details`)))); // {{SQL CARBON EDIT}} remove rating widget @@ -578,7 +577,7 @@ export class ExtensionEditor extends EditorPane { ] } */ - this.telemetryService.publicLog('extensionEditor:navbarChange', assign(extension.telemetryData, { navItem: id })); + this.telemetryService.publicLog('extensionEditor:navbarChange', { ...extension.telemetryData, navItem: id }); } this.contentDisposables.clear(); @@ -870,13 +869,13 @@ export class ExtensionEditor extends EditorPane { const extensionPack = append(extensionPackReadme, $('div', { class: 'extension-pack' })); if (manifest.extensionPack!.length <= 3) { - addClass(extensionPackReadme, 'one-row'); + extensionPackReadme.classList.add('one-row'); } else if (manifest.extensionPack!.length <= 6) { - addClass(extensionPackReadme, 'two-rows'); + extensionPackReadme.classList.add('two-rows'); } else if (manifest.extensionPack!.length <= 9) { - addClass(extensionPackReadme, 'three-rows'); + extensionPackReadme.classList.add('three-rows'); } else { - addClass(extensionPackReadme, 'more-rows'); + extensionPackReadme.classList.add('more-rows'); } const extensionPackHeader = append(extensionPack, $('div.header')); @@ -1460,10 +1459,10 @@ export class ExtensionEditor extends EditorPane { } private loadContents(loadingTask: () => CacheResult, template: IExtensionEditorTemplate): Promise { - addClass(template.content, 'loading'); + template.content.classList.add('loading'); const result = this.contentDisposables.add(loadingTask()); - const onDone = () => removeClass(template.content, 'loading'); + const onDone = () => template.content.classList.remove('loading'); result.promise.then(onDone, onDone); return result.promise; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts index 36deb574e5..870ae7481a 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionRecommendationsService.ts @@ -13,7 +13,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { distinct, shuffle } from 'vs/base/common/arrays'; import { Emitter, Event } from 'vs/base/common/event'; -import { assign } from 'vs/base/common/objects'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { LifecyclePhase, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { DynamicWorkspaceRecommendations } from 'vs/workbench/contrib/extensions/browser/dynamicWorkspaceRecommendations'; @@ -271,7 +270,7 @@ export class ExtensionRecommendationsService extends Disposable implements IExte ] } */ - this.telemetryService.publicLog('extensionGallery:install:recommendations', assign(e.gallery.telemetryData, { recommendationReason: recommendationReason.reasonId })); + this.telemetryService.publicLog('extensionGallery:install:recommendations', { ...e.gallery.telemetryData, recommendationReason: recommendationReason.reasonId }); } } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts index 5fbacd5e03..5c72618765 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/extension'; -import { append, $, addClass, removeClass, toggleClass } from 'vs/base/browser/dom'; +import { append, $ } from 'vs/base/browser/dom'; import { IDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle'; import { IAction } from 'vs/base/common/actions'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -137,7 +137,7 @@ export class Renderer implements IPagedRenderer { } renderPlaceholder(index: number, data: ITemplateData): void { - addClass(data.element, 'loading'); + data.element.classList.add('loading'); data.root.removeAttribute('aria-label'); data.root.removeAttribute('data-extension-id'); @@ -153,7 +153,7 @@ export class Renderer implements IPagedRenderer { } renderElement(extension: IExtension, index: number, data: ITemplateData): void { - removeClass(data.element, 'loading'); + data.element.classList.remove('loading'); data.root.setAttribute('data-extension-id', extension.identifier.id); if (extension.state !== ExtensionState.Uninstalled && !extension.server) { @@ -171,7 +171,7 @@ export class Renderer implements IPagedRenderer { const runningExtension = runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, extension.identifier))[0]; isDisabled = !(runningExtension && extension.server === this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension))); } - toggleClass(data.root, 'disabled', isDisabled); + data.root.classList.toggle('disabled', isDisabled); }; updateEnablement(); this.extensionService.onDidChangeExtensions(() => updateEnablement(), this, data.extensionDisposables); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts index 1a0249d7c7..9da50f0f13 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts @@ -136,7 +136,7 @@ export class ExtensionRenderer implements IListRenderer('img.icon')); const details = dom.append(container, dom.$('.details')); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index c26e58b33b..57051429f3 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -13,7 +13,7 @@ import { Event as EventOf, Emitter } from 'vs/base/common/event'; import { IAction, Action, Separator, SubmenuAction } from 'vs/base/common/actions'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { append, $, addClass, toggleClass, Dimension, hide, show } from 'vs/base/browser/dom'; +import { append, $, Dimension, hide, show } from 'vs/base/browser/dom'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -50,7 +50,6 @@ import { Query } from 'vs/workbench/contrib/extensions/common/extensionQuery'; import { SuggestEnabledInput, attachSuggestEnabledInputBoxStyler } from 'vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput'; import { alert } from 'vs/base/browser/ui/aria/aria'; import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ExtensionType, EXTENSION_CATEGORIES } from 'vs/platform/extensions/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; import { ILabelService } from 'vs/platform/label/common/label'; @@ -355,8 +354,6 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE @IInstantiationService instantiationService: IInstantiationService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, - @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @INotificationService private readonly notificationService: INotificationService, @IViewletService private readonly viewletService: IViewletService, @@ -406,7 +403,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE } create(parent: HTMLElement): void { - addClass(parent, 'extensions-viewlet'); + parent.classList.add('extensions-viewlet'); this.root = parent; const overlay = append(this.root, $('.overlay')); @@ -502,7 +499,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE layout(dimension: Dimension): void { if (this.root) { - toggleClass(this.root, 'narrow', dimension.width <= 300); + this.root.classList.toggle('narrow', dimension.width <= 300); } if (this.searchBox) { this.searchBox.layout({ height: 20, width: dimension.width - 34 }); @@ -536,9 +533,6 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE new SubmenuAction('workbench.extensions.action.filterExtensionsByCategory', localize('filter by category', "Category"), EXTENSION_CATEGORIES.map(category => this.instantiationService.createInstance(SearchCategoryAction, `extensions.actions.searchByCategory.${category}`, category, category))), new Separator(), ]; - if (this.extensionManagementServerService.webExtensionManagementServer || !this.environmentService.isBuilt) { - galleryFilterActions.splice(4, 0, this.instantiationService.createInstance(PredefinedExtensionFilterAction, 'extensions.filter.web', localize('web filter', "Web"), '@web')); - } filterActions.splice(0, 0, ...galleryFilterActions); filterActions.push(...[ new Separator(), @@ -752,7 +746,7 @@ export class MaliciousExtensionChecker implements IWorkbenchContribution { @IHostService private readonly hostService: IHostService, @ILogService private readonly logService: ILogService, @INotificationService private readonly notificationService: INotificationService, - @IEnvironmentService private readonly environmentService: IEnvironmentService + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService ) { if (!this.environmentService.disableExtensions) { this.loopCheckForMaliciousExtensions(); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index 72f4fc8b0e..a573190d09 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -5,7 +5,6 @@ import { localize } from 'vs/nls'; import { Disposable } from 'vs/base/common/lifecycle'; -import { assign } from 'vs/base/common/objects'; import { Event, Emitter } from 'vs/base/common/event'; import { isPromiseCanceledError, getErrorMessage } from 'vs/base/common/errors'; import { PagedModel, IPagedModel, IPager, DelayedPagedModel } from 'vs/base/common/paging'; @@ -14,7 +13,7 @@ import { IExtensionManagementServer, IExtensionManagementServerService, IExtensi import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { append, $, toggleClass, addClass } from 'vs/base/browser/dom'; +import { append, $ } from 'vs/base/browser/dom'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Delegate, Renderer, IExtensionsViewState } from 'vs/workbench/contrib/extensions/browser/extensionsList'; import { IExtension, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; @@ -31,7 +30,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { coalesce, distinct, flatten, firstIndex } from 'vs/base/common/arrays'; // {{ SQL CARBON EDIT }} +import { coalesce, distinct, flatten } from 'vs/base/common/arrays'; // {{ SQL CARBON EDIT }} import { IExperimentService, IExperiment, ExperimentActionType } from 'vs/workbench/contrib/experiments/common/experimentService'; import { alert } from 'vs/base/browser/ui/aria/aria'; import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; @@ -121,7 +120,7 @@ export class ExtensionsListView extends ViewPane { } protected renderHeader(container: HTMLElement): void { - addClass(container, 'extension-view-header'); + container.classList.add('extension-view-header'); super.renderHeader(container); this.badge = new CountBadge(append(container, $('.count-badge-wrapper'))); @@ -198,10 +197,10 @@ export class ExtensionsListView extends ViewPane { }; switch (parsedQuery.sortBy) { - case 'installs': options = assign(options, { sortBy: SortBy.InstallCount }); break; - case 'rating': options = assign(options, { sortBy: SortBy.WeightedRating }); break; - case 'name': options = assign(options, { sortBy: SortBy.Title }); break; - case 'publishedDate': options = assign(options, { sortBy: SortBy.PublishedDate }); break; + case 'installs': options.sortBy = SortBy.InstallCount; break; + case 'rating': options.sortBy = SortBy.WeightedRating; break; + case 'name': options.sortBy = SortBy.Title; break; + case 'publishedDate': options.sortBy = SortBy.PublishedDate; break; } const successCallback = (model: IPagedModel) => { @@ -490,13 +489,15 @@ export class ExtensionsListView extends ViewPane { const text = query.value; if (/\bext:([^\s]+)\b/g.test(text)) { - options = assign(options, { text, source: 'file-extension-tags' }); + options.text = text; + options.source = 'file-extension-tags'; return this.extensionsWorkbenchService.queryGallery(options, token).then(pager => this.getPagedModel(pager)); } let preferredResults: string[] = []; if (text) { - options = assign(options, { text: text.substr(0, 350), source: 'searchText' }); + options.text = text.substr(0, 350); + options.source = 'searchText'; if (!hasUserDefinedSortOrder) { const searchExperiments = await this.getSearchExperiments(); for (const experiment of searchExperiments) { @@ -562,7 +563,9 @@ export class ExtensionsListView extends ViewPane { const names = await this.experimentService.getCuratedExtensionsList(value); if (Array.isArray(names) && names.length) { options.source = `curated:${value}`; - const pager = await this.extensionsWorkbenchService.queryGallery(assign(options, { names, pageSize: names.length }), token); + options.names = names; + options.pageSize = names.length; + const pager = await this.extensionsWorkbenchService.queryGallery(options, token); this.sortFirstPage(pager, names); return this.getPagedModel(pager || []); } @@ -586,7 +589,10 @@ export class ExtensionsListView extends ViewPane { .then(local => { return this.extensionRecommendationsService.getOtherRecommendations().then((recommmended) => { const installedExtensions = local.map(x => `${x.publisher}.${x.name}`); - options = assign(options, { text: value, source: 'searchText' }); + options = { + ...options, + text: value, source: 'searchText' + }; return this.extensionsWorkbenchService.queryGallery(options, token).then((pager) => { // filter out installed extensions pager.firstPage = pager.firstPage.filter((p) => { @@ -595,8 +601,8 @@ export class ExtensionsListView extends ViewPane { // sort the marketplace extensions pager.firstPage.sort((a, b) => { - let isRecommendedA: boolean = firstIndex(recommmended, ext => ext.extensionId === `${a.publisher}.${a.name}`) > -1; - let isRecommendedB: boolean = firstIndex(recommmended, ext => ext.extensionId === `${b.publisher}.${b.name}`) > -1; + let isRecommendedA: boolean = recommmended.findIndex(ext => ext.extensionId === `${a.publisher}.${a.name}`) > -1; + let isRecommendedB: boolean = recommmended.findIndex(ext => ext.extensionId === `${b.publisher}.${b.name}`) > -1; // sort recommeded extensions before other extensions if (isRecommendedA !== isRecommendedB) { @@ -628,7 +634,7 @@ export class ExtensionsListView extends ViewPane { // filter out installed extensions and the extensions not in the recommended list pager.firstPage = pager.firstPage.filter((p) => { const extensionId = `${p.publisher}.${p.name}`; - return installedExtensions.indexOf(extensionId) === -1 && firstIndex(recommmended, ext => ext.extensionId === extensionId) !== -1; + return installedExtensions.indexOf(extensionId) === -1 && recommmended.findIndex(ext => ext.extensionId === extensionId) !== -1; }); pager.total = pager.firstPage.length; pager.pageSize = pager.firstPage.length; @@ -780,8 +786,8 @@ export class ExtensionsListView extends ViewPane { if (this.bodyTemplate && this.badge) { - toggleClass(this.bodyTemplate.extensionsList, 'hidden', count === 0); - toggleClass(this.bodyTemplate.messageContainer, 'hidden', count > 0); + this.bodyTemplate.extensionsList.classList.toggle('hidden', count === 0); + this.bodyTemplate.messageContainer.classList.toggle('hidden', count > 0); this.badge.setCount(count); if (count === 0 && this.isBodyVisible()) { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index 1a24795d0c..bbf158a462 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/extensionsWidgets'; import { Disposable, toDisposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { IExtension, IExtensionsWorkbenchService, IExtensionContainer } from 'vs/workbench/contrib/extensions/common/extensions'; -import { append, $, addClass, removeNode } from 'vs/base/browser/dom'; +import { append, $ } from 'vs/base/browser/dom'; import * as platform from 'vs/base/common/platform'; import { localize } from 'vs/nls'; import { IExtensionRecommendationsService, IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; @@ -50,7 +50,7 @@ export class InstallCountWidget extends ExtensionWidget { @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService ) { super(); - addClass(container, 'extension-install-count'); + container.classList.add('extension-install-count'); this.render(); } @@ -95,10 +95,10 @@ export class RatingsWidget extends ExtensionWidget { private small: boolean ) { super(); - addClass(container, 'extension-ratings'); + container.classList.add('extension-ratings'); if (this.small) { - addClass(container, 'small'); + container.classList.add('small'); } this.render(); @@ -325,7 +325,7 @@ export class ExtensionPackCountWidget extends ExtensionWidget { private clear(): void { if (this.element) { - removeNode(this.element); + this.element.remove(); } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 88f4a6f175..8cce5f8fe0 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -1216,7 +1216,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return changed; } - private _activityCallBack: (() => void) | null = null; + private _activityCallBack: ((value: void) => void) | null = null; private updateActivity(): void { if ((this.localExtensions && this.localExtensions.local.some(e => e.state === ExtensionState.Installing || e.state === ExtensionState.Uninstalling)) || (this.remoteExtensions && this.remoteExtensions.local.some(e => e.state === ExtensionState.Installing || e.state === ExtensionState.Uninstalling)) diff --git a/src/vs/workbench/contrib/extensions/common/extensionsUtils.ts b/src/vs/workbench/contrib/extensions/common/extensionsUtils.ts index 64eb19a098..cfe59071cc 100644 --- a/src/vs/workbench/contrib/extensions/common/extensionsUtils.ts +++ b/src/vs/workbench/contrib/extensions/common/extensionsUtils.ts @@ -3,7 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as arrays from 'vs/base/common/arrays'; import { localize } from 'vs/nls'; import { Event } from 'vs/base/common/event'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -44,7 +43,7 @@ export class KeymapExtensions extends Disposable implements IWorkbenchContributi private checkForOtherKeymaps(extensionIdentifier: IExtensionIdentifier): Promise { return this.instantiationService.invokeFunction(getInstalledExtensions).then(extensions => { const keymaps = extensions.filter(extension => isKeymapExtension(this.tipsService, extension)); - const extension = arrays.first(keymaps, extension => areSameExtensions(extension.identifier, extensionIdentifier)); + const extension = keymaps.find(extension => areSameExtensions(extension.identifier, extensionIdentifier)); if (extension && extension.globallyEnabled) { const otherKeymaps = keymaps.filter(extension => !areSameExtensions(extension.identifier, extensionIdentifier) && extension.globallyEnabled); if (otherKeymaps.length) { diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts index 9a1d2f5a4e..f336965a0c 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts @@ -21,8 +21,8 @@ import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/electron import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ExtensionsAutoProfiler } from 'vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; -import { OpenExtensionsFolderAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsActions'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; +import { OpenExtensionsFolderAction } from 'vs/workbench/contrib/extensions/electron-sandbox/extensionsActions'; import { ExtensionsLabel } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService'; import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; diff --git a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts index 4ad6495396..e140c5f529 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts @@ -17,7 +17,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IExtensionService, IExtensionsStatus, IExtensionHostProfile } from 'vs/workbench/services/extensions/common/extensions'; import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; -import { append, $, reset, addClass, toggleClass, Dimension, clearNode } from 'vs/base/browser/dom'; +import { append, $, reset, Dimension, clearNode } from 'vs/base/browser/dom'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -26,7 +26,6 @@ import { EnablementState } from 'vs/workbench/services/extensionManagement/commo import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron'; import { writeFile } from 'vs/base/node/pfs'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { memoize } from 'vs/base/common/decorators'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { Event } from 'vs/base/common/event'; @@ -38,9 +37,9 @@ import { randomPort } from 'vs/base/node/ports'; import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ILabelService } from 'vs/platform/label/common/label'; -import { renderCodiconsAsElement } from 'vs/base/browser/codicons'; +import { renderCodicons } from 'vs/base/browser/codicons'; import { ExtensionIdentifier, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; +import { Schemas } from 'vs/base/common/network'; import { SlowExtensionAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsSlowActions'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -258,7 +257,7 @@ export class RuntimeExtensionsEditor extends EditorPane { } protected createEditor(parent: HTMLElement): void { - addClass(parent, 'runtime-extensions-editor'); + parent.classList.add('runtime-extensions-editor'); const TEMPLATE_ID = 'runtimeExtensionElementTemplate'; @@ -329,7 +328,7 @@ export class RuntimeExtensionsEditor extends EditorPane { data.elementDisposables = dispose(data.elementDisposables); - toggleClass(data.root, 'odd', index % 2 === 1); + data.root.classList.toggle('odd', index % 2 === 1); const onError = Event.once(domEvent(data.icon, 'error')); onError(() => data.icon.src = element.marketplaceInfo.iconUrlFallback, null, data.elementDisposables); @@ -409,28 +408,28 @@ export class RuntimeExtensionsEditor extends EditorPane { clearNode(data.msgContainer); if (this._extensionHostProfileService.getUnresponsiveProfile(element.description.identifier)) { - const el = $('span', undefined, ...renderCodiconsAsElement(` $(alert) Unresponsive`)); + const el = $('span', undefined, ...renderCodicons(` $(alert) Unresponsive`)); el.title = nls.localize('unresponsive.title', "Extension has caused the extension host to freeze."); data.msgContainer.appendChild(el); } if (isNonEmptyArray(element.status.runtimeErrors)) { - const el = $('span', undefined, ...renderCodiconsAsElement(`$(bug) ${nls.localize('errors', "{0} uncaught errors", element.status.runtimeErrors.length)}`)); + const el = $('span', undefined, ...renderCodicons(`$(bug) ${nls.localize('errors', "{0} uncaught errors", element.status.runtimeErrors.length)}`)); data.msgContainer.appendChild(el); } if (element.status.messages && element.status.messages.length > 0) { - const el = $('span', undefined, ...renderCodiconsAsElement(`$(alert) ${element.status.messages[0].message}`)); + const el = $('span', undefined, ...renderCodicons(`$(alert) ${element.status.messages[0].message}`)); data.msgContainer.appendChild(el); } if (element.description.extensionLocation.scheme !== 'file') { - const el = $('span', undefined, ...renderCodiconsAsElement(`$(remote) ${element.description.extensionLocation.authority}`)); + const el = $('span', undefined, ...renderCodicons(`$(remote) ${element.description.extensionLocation.authority}`)); data.msgContainer.appendChild(el); - const hostLabel = this._labelService.getHostLabel(REMOTE_HOST_SCHEME, this._environmentService.configuration.remoteAuthority); + const hostLabel = this._labelService.getHostLabel(Schemas.vscodeRemote, this._environmentService.configuration.remoteAuthority); if (hostLabel) { - reset(el, ...renderCodiconsAsElement(`$(remote) ${hostLabel}`)); + reset(el, ...renderCodicons(`$(remote) ${hostLabel}`)); } } @@ -664,7 +663,7 @@ export class SaveExtensionHostProfileAction extends Action { constructor( id: string = SaveExtensionHostProfileAction.ID, label: string = SaveExtensionHostProfileAction.LABEL, @IElectronService private readonly _electronService: IElectronService, - @IEnvironmentService private readonly _environmentService: IEnvironmentService, + @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, @IExtensionHostProfileService private readonly _extensionHostProfileService: IExtensionHostProfileService, ) { super(id, label, undefined, false); diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsActions.ts similarity index 96% rename from src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts rename to src/vs/workbench/contrib/extensions/electron-sandbox/extensionsActions.ts index bdc20f8893..5157f94048 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsActions.ts @@ -8,7 +8,7 @@ import { Action } from 'vs/base/common/actions'; import { IFileService } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron'; import { Schemas } from 'vs/base/common/network'; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts index d4981b317b..5f89895bae 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts @@ -30,7 +30,6 @@ import { URI } from 'vs/base/common/uri'; import { testWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IPager } from 'vs/base/common/paging'; -import { assign } from 'vs/base/common/objects'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ConfigurationKey, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; @@ -166,10 +165,9 @@ const noAssets: IGalleryExtensionAssets = { }; function aGalleryExtension(name: string, properties: any = {}, galleryExtensionProperties: any = {}, assets: IGalleryExtensionAssets = noAssets): IGalleryExtension { - const galleryExtension = Object.create({}); - assign(galleryExtension, { name, publisher: 'pub', version: '1.0.0', properties: {}, assets: {} }, properties); - assign(galleryExtension.properties, { dependencies: [] }, galleryExtensionProperties); - assign(galleryExtension.assets, assets); + const galleryExtension = Object.create({ name, publisher: 'pub', version: '1.0.0', properties: {}, assets: {}, ...properties }); + galleryExtension.properties = { ...galleryExtension.properties, dependencies: [], ...galleryExtensionProperties }; + galleryExtension.assets = { ...galleryExtension.assets, ...assets }; galleryExtension.identifier = { id: getGalleryExtensionId(galleryExtension.publisher, galleryExtension.name), uuid: uuid.generateUuid() }; return galleryExtension; } diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts index d1f8f71fad..e45504395d 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { assign } from 'vs/base/common/objects'; import { generateUuid } from 'vs/base/common/uuid'; import { IExtensionsWorkbenchService, ExtensionContainers } from 'vs/workbench/contrib/extensions/common/extensions'; import * as ExtensionsActions from 'vs/workbench/contrib/extensions/browser/extensionsActions'; @@ -41,7 +40,6 @@ import { ILabelService, IFormatterChangeEvent } from 'vs/platform/label/common/l import { ExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService'; import { IProductService } from 'vs/platform/product/common/productService'; import { Schemas } from 'vs/base/common/network'; -import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IProgressService } from 'vs/platform/progress/common/progress'; import { ProgressService } from 'vs/workbench/services/progress/browser/progressService'; @@ -470,7 +468,7 @@ suite('ExtensionsActions', () => { testObject.extension = extensions[0]; instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: local.identifier, version: '1.0.1' }))); assert.ok(!testObject.enabled); - return new Promise(c => { + return new Promise(c => { testObject.onDidChange(() => { if (testObject.enabled) { c(); @@ -1026,7 +1024,7 @@ suite('ExtensionsActions', () => { .then(async () => { instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: local[0].identifier, version: '1.0.2' }), aGalleryExtension('b', { identifier: local[1].identifier, version: '1.0.2' }), aGalleryExtension('c', local[2].manifest))); assert.ok(!testObject.enabled); - return new Promise(c => { + return new Promise(c => { testObject.onDidChange(() => { if (testObject.enabled) { c(); @@ -1047,7 +1045,7 @@ suite('ExtensionsActions', () => { .then(async () => { instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(...gallery)); assert.ok(!testObject.enabled); - return new Promise(c => { + return new Promise(c => { installEvent.fire({ identifier: local[0].identifier, gallery: gallery[0] }); testObject.onDidChange(() => { if (testObject.enabled) { @@ -1247,7 +1245,7 @@ suite('ReloadAction', () => { const extensions = await workbenchService.queryLocal(); testObject.extension = extensions[0]; - return new Promise(c => { + return new Promise(c => { testObject.onDidChange(() => { if (testObject.enabled && testObject.tooltip === 'Please reload Azure Data Studio to enable the updated extension.') { // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio c(); @@ -2494,20 +2492,20 @@ suite('LocalInstallAction', () => { }); function aLocalExtension(name: string = 'someext', manifest: any = {}, properties: any = {}): ILocalExtension { - manifest = assign({ name, publisher: 'pub', version: '1.0.0' }, manifest); - properties = assign({ + manifest = { name, publisher: 'pub', version: '1.0.0', ...manifest }; + properties = { type: ExtensionType.User, location: URI.file(`pub.${name}`), - identifier: { id: getGalleryExtensionId(manifest.publisher, manifest.name) } - }, properties); + identifier: { id: getGalleryExtensionId(manifest.publisher, manifest.name) }, + ...properties + }; return Object.create({ manifest, ...properties }); } function aGalleryExtension(name: string, properties: any = {}, galleryExtensionProperties: any = {}, assets: any = {}): IGalleryExtension { - const galleryExtension = Object.create({}); - assign(galleryExtension, { name, publisher: 'pub', version: '1.0.0', properties: {}, assets: {} }, properties); - assign(galleryExtension.properties, { dependencies: [] }, galleryExtensionProperties); - assign(galleryExtension.assets, assets); + const galleryExtension = Object.create({ name, publisher: 'pub', version: '1.0.0', properties: {}, assets: {}, ...properties }); + galleryExtension.properties = { ...galleryExtension.properties, dependencies: [], ...galleryExtensionProperties }; + galleryExtension.assets = { ...galleryExtension.assets, ...assets }; galleryExtension.identifier = { id: getGalleryExtensionId(galleryExtension.publisher, galleryExtension.name), uuid: generateUuid() }; return galleryExtension; } @@ -2528,7 +2526,7 @@ function aSingleRemoteExtensionManagementServerService(instantiationService: Tes remoteExtensionManagementServer, webExtensionManagementServer: null, getExtensionManagementServer: (extension: IExtension) => { - if (extension.location.scheme === REMOTE_HOST_SCHEME) { + if (extension.location.scheme === Schemas.vscodeRemote) { return remoteExtensionManagementServer; } return null; @@ -2556,7 +2554,7 @@ function aMultiExtensionManagementServerService(instantiationService: TestInstan if (extension.location.scheme === Schemas.file) { return localExtensionManagementServer; } - if (extension.location.scheme === REMOTE_HOST_SCHEME) { + if (extension.location.scheme === Schemas.vscodeRemote) { return remoteExtensionManagementServer; } throw new Error(''); diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts index eac94d01bd..0e01b7a618 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { assign } from 'vs/base/common/objects'; import { generateUuid } from 'vs/base/common/uuid'; import { ExtensionsListView } from 'vs/workbench/contrib/extensions/browser/extensionsViews'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; @@ -525,21 +524,21 @@ suite('ExtensionsListView Tests', () => { }); function aLocalExtension(name: string = 'someext', manifest: any = {}, properties: any = {}): ILocalExtension { - manifest = assign({ name, publisher: 'pub', version: '1.0.0' }, manifest); - properties = assign({ + manifest = { name, publisher: 'pub', version: '1.0.0', ...manifest }; + properties = { type: ExtensionType.User, location: URI.file(`pub.${name}`), - identifier: { id: getGalleryExtensionId(manifest.publisher, manifest.name), uuid: undefined }, - metadata: { id: getGalleryExtensionId(manifest.publisher, manifest.name), publisherId: manifest.publisher, publisherDisplayName: 'somename' } - }, properties); + identifier: { id: getGalleryExtensionId(manifest.publisher, manifest.name) }, + metadata: { id: getGalleryExtensionId(manifest.publisher, manifest.name), publisherId: manifest.publisher, publisherDisplayName: 'somename' }, + ...properties + }; return Object.create({ manifest, ...properties }); } function aGalleryExtension(name: string, properties: any = {}, galleryExtensionProperties: any = {}, assets: any = {}): IGalleryExtension { - const galleryExtension = Object.create({}); - assign(galleryExtension, { name, publisher: 'pub', version: '1.0.0', properties: {}, assets: {} }, properties); - assign(galleryExtension.properties, { dependencies: [] }, galleryExtensionProperties); - assign(galleryExtension.assets, assets); + const galleryExtension = Object.create({ name, publisher: 'pub', version: '1.0.0', properties: {}, assets: {}, ...properties }); + galleryExtension.properties = { ...galleryExtension.properties, dependencies: [], ...galleryExtensionProperties }; + galleryExtension.assets = { ...galleryExtension.assets, ...assets }; galleryExtension.identifier = { id: getGalleryExtensionId(galleryExtension.publisher, galleryExtension.name), uuid: generateUuid() }; return galleryExtension; } diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index 9563b2361b..1a23cb0ac0 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -6,7 +6,6 @@ import * as sinon from 'sinon'; import * as assert from 'assert'; import * as fs from 'fs'; -import { assign } from 'vs/base/common/objects'; import { generateUuid } from 'vs/base/common/uuid'; import { IExtensionsWorkbenchService, ExtensionState, AutoCheckUpdatesConfigurationKey, AutoUpdateConfigurationKey } from 'vs/workbench/contrib/extensions/common/extensions'; import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService'; @@ -47,7 +46,6 @@ import { IExperimentService } from 'vs/workbench/contrib/experiments/common/expe import { TestExperimentService } from 'vs/workbench/contrib/experiments/test/electron-browser/experimentService.test'; import { ExtensionTipsService } from 'vs/platform/extensionManagement/node/extensionTipsService'; import { Schemas } from 'vs/base/common/network'; -import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; suite('ExtensionsWorkbenchServiceTest', () => { @@ -1368,12 +1366,13 @@ suite('ExtensionsWorkbenchServiceTest', () => { } function aLocalExtension(name: string = 'someext', manifest: any = {}, properties: any = {}): ILocalExtension { - manifest = assign({ name, publisher: 'pub', version: '1.0.0' }, manifest); - properties = assign({ + manifest = { name, publisher: 'pub', version: '1.0.0', ...manifest }; + properties = { type: ExtensionType.User, location: URI.file(`pub.${name}`), - identifier: { id: getGalleryExtensionId(manifest.publisher, manifest.name) } - }, properties); + identifier: { id: getGalleryExtensionId(manifest.publisher, manifest.name) }, + ...properties + }; return Object.create({ manifest, ...properties }); } @@ -1389,10 +1388,9 @@ suite('ExtensionsWorkbenchServiceTest', () => { }; function aGalleryExtension(name: string, properties: any = {}, galleryExtensionProperties: any = {}, assets: IGalleryExtensionAssets = noAssets): IGalleryExtension { - const galleryExtension = Object.create({}); - assign(galleryExtension, { name, publisher: 'pub', version: '1.0.0', properties: {}, assets: {} }, properties); - assign(galleryExtension.properties, { dependencies: [] }, galleryExtensionProperties); - assign(galleryExtension.assets, assets); + const galleryExtension = Object.create({ name, publisher: 'pub', version: '1.0.0', properties: {}, assets: {}, ...properties }); + galleryExtension.properties = { ...galleryExtension.properties, dependencies: [], ...galleryExtensionProperties }; + galleryExtension.assets = { ...galleryExtension.assets, ...assets }; galleryExtension.identifier = { id: getGalleryExtensionId(galleryExtension.publisher, galleryExtension.name), uuid: generateUuid() }; return galleryExtension; } @@ -1432,7 +1430,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { if (extension.location.scheme === Schemas.file) { return localExtensionManagementServer; } - if (extension.location.scheme === REMOTE_HOST_SCHEME) { + if (extension.location.scheme === Schemas.vscodeRemote) { return remoteExtensionManagementServer; } throw new Error(''); diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 3dfa7d72c6..649bbc57ae 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -5,7 +5,6 @@ import 'vs/css!./media/explorerviewlet'; import { localize } from 'vs/nls'; -import * as DOM from 'vs/base/browser/dom'; 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'; @@ -189,7 +188,7 @@ export class ExplorerViewPaneContainer extends ViewPaneContainer { create(parent: HTMLElement): void { super.create(parent); - DOM.addClass(parent, 'explorer-viewlet'); + parent.classList.add('explorer-viewlet'); } protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewPane { diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index 29bffd8306..c5ecc66615 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -10,7 +10,7 @@ import { revertLocalChangesCommand, acceptLocalChangesCommand, CONFLICT_RESOLUTI 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, OPEN_WITH_EXPLORER_COMMAND_ID, NEW_UNTITLED_FILE_COMMAND_ID, NEW_UNTITLED_FILE_LABEL, NEW_UNTITLED_PLAIN_FILE_COMMAND_ID } 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, OpenEditorsDirtyEditorContext, 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, OpenEditorsReadonlyEditorContext, OPEN_WITH_EXPLORER_COMMAND_ID, NEW_UNTITLED_FILE_COMMAND_ID, NEW_UNTITLED_FILE_LABEL, NEW_UNTITLED_PLAIN_FILE_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'; @@ -22,11 +22,11 @@ import { AutoSaveAfterShortDelayContext } from 'vs/workbench/services/filesConfi import { ResourceContextKey } from 'vs/workbench/common/resources'; import { WorkbenchListDoubleSelection } from 'vs/platform/list/browser/listService'; import { Schemas } from 'vs/base/common/network'; -import { WorkspaceFolderCountContext } from 'vs/workbench/browser/contextkeys'; +import { DirtyWorkingCopiesContext, 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 } from 'vs/workbench/common/editor'; +import { ActiveEditorContext } from 'vs/workbench/common/editor'; import { SidebarFocusContext } from 'vs/workbench/common/viewlet'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; @@ -220,7 +220,6 @@ appendToCommandPalette(SAVE_FILES_COMMAND_ID, { value: nls.localize('saveFiles', appendToCommandPalette(REVERT_FILE_COMMAND_ID, { value: nls.localize('revert', "Revert File"), original: 'Revert File' }, category); appendToCommandPalette(COMPARE_WITH_SAVED_COMMAND_ID, { value: nls.localize('compareActiveWithSaved', "Compare Active File with Saved"), original: 'Compare Active File with Saved' }, category); appendToCommandPalette(SAVE_FILE_AS_COMMAND_ID, { value: SAVE_FILE_AS_LABEL, original: 'Save As...' }, category); -appendToCommandPalette(CLOSE_EDITOR_COMMAND_ID, { value: nls.localize('closeEditor', "Close Editor"), original: 'Close Editor' }, { value: nls.localize('view', "View"), original: 'View' }); appendToCommandPalette(NEW_FILE_COMMAND_ID, { value: NEW_FILE_LABEL, original: 'New File' }, category, WorkspaceFolderCountContext.notEqualsTo('0')); appendToCommandPalette(NEW_FOLDER_COMMAND_ID, { value: NEW_FOLDER_LABEL, original: 'New Folder' }, category, WorkspaceFolderCountContext.notEqualsTo('0')); appendToCommandPalette(DOWNLOAD_COMMAND_ID, { value: DOWNLOAD_LABEL, original: 'Download' }, category, ContextKeyExpr.and(ResourceContextKey.Scheme.notEqualsTo(Schemas.file))); @@ -260,7 +259,7 @@ MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { command: { id: SAVE_FILE_COMMAND_ID, title: SAVE_FILE_LABEL, - precondition: DirtyEditorContext + precondition: OpenEditorsDirtyEditorContext }, when: ContextKeyExpr.or( // Untitled Editors @@ -270,7 +269,7 @@ MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { // Not: editor groups OpenEditorsGroupContext.toNegated(), // Not: readonly editors - ReadonlyEditorContext.toNegated(), + OpenEditorsReadonlyEditorContext.toNegated(), // Not: auto save after short delay AutoSaveAfterShortDelayContext.toNegated() ) @@ -283,13 +282,13 @@ MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { command: { id: REVERT_FILE_COMMAND_ID, title: nls.localize('revert', "Revert File"), - precondition: DirtyEditorContext + precondition: OpenEditorsDirtyEditorContext }, when: ContextKeyExpr.and( // Not: editor groups OpenEditorsGroupContext.toNegated(), // Not: readonly editors - ReadonlyEditorContext.toNegated(), + OpenEditorsReadonlyEditorContext.toNegated(), // Not: untitled editors (revert closes them) ResourceContextKey.Scheme.notEqualsTo(Schemas.untitled), // Not: auto save after short delay @@ -315,7 +314,7 @@ MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { command: { id: COMPARE_WITH_SAVED_COMMAND_ID, title: nls.localize('compareWithSaved', "Compare with Saved"), - precondition: DirtyEditorContext + precondition: OpenEditorsDirtyEditorContext }, when: ContextKeyExpr.and(ResourceContextKey.IsFileSystemResource, AutoSaveAfterShortDelayContext.toNegated(), WorkbenchListDoubleSelection.toNegated()) }); @@ -429,7 +428,7 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { id: OPEN_WITH_EXPLORER_COMMAND_ID, title: nls.localize('explorerOpenWith', "Open With..."), }, - when: ContextKeyExpr.and(ExplorerRootContext.toNegated(), ExplorerResourceAvailableEditorIdsContext), + when: ContextKeyExpr.and(ExplorerFolderContext.toNegated(), ExplorerResourceAvailableEditorIdsContext), }); MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { @@ -610,7 +609,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { command: { id: SAVE_FILE_COMMAND_ID, title: nls.localize({ key: 'miSave', comment: ['&& denotes a mnemonic'] }, "&&Save"), - precondition: ContextKeyExpr.or(ActiveEditorIsReadonlyContext.toNegated(), ContextKeyExpr.and(ExplorerViewletVisibleContext, SidebarFocusContext)) + precondition: ContextKeyExpr.or(ActiveEditorContext, ContextKeyExpr.and(ExplorerViewletVisibleContext, SidebarFocusContext)) }, order: 1 }); @@ -620,7 +619,6 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { command: { id: SAVE_FILE_AS_COMMAND_ID, title: nls.localize({ key: 'miSaveAs', comment: ['&& denotes a mnemonic'] }, "Save &&As..."), - // ActiveEditorContext is not 100% correct, but we lack a context for indicating "Save As..." support precondition: ContextKeyExpr.or(ActiveEditorContext, ContextKeyExpr.and(ExplorerViewletVisibleContext, SidebarFocusContext)) }, order: 2 @@ -689,7 +687,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { command: { id: REVERT_FILE_COMMAND_ID, title: nls.localize({ key: 'miRevert', comment: ['&& denotes a mnemonic'] }, "Re&&vert File"), - precondition: ContextKeyExpr.or(ActiveEditorIsReadonlyContext.toNegated(), ContextKeyExpr.and(ExplorerViewletVisibleContext, SidebarFocusContext)) + precondition: ContextKeyExpr.or(ActiveEditorContext, ContextKeyExpr.and(ExplorerViewletVisibleContext, SidebarFocusContext)) }, order: 1 }); @@ -698,7 +696,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { group: '6_close', command: { id: CLOSE_EDITOR_COMMAND_ID, - title: nls.localize({ key: 'miCloseEditor', comment: ['&& denotes a mnemonic'] }, "&&Close Editor") + title: nls.localize({ key: 'miCloseEditor', comment: ['&& denotes a mnemonic'] }, "&&Close Editor"), + precondition: ContextKeyExpr.or(ActiveEditorContext, ContextKeyExpr.and(ExplorerViewletVisibleContext, SidebarFocusContext)) }, order: 2 }); diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index a7c402cd9c..0d93020c8c 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -74,8 +74,8 @@ export const SAVE_ALL_IN_GROUP_COMMAND_ID = 'workbench.files.action.saveAllInGro export const SAVE_FILES_COMMAND_ID = 'workbench.action.files.saveFiles'; export const OpenEditorsGroupContext = new RawContextKey('groupFocusedInOpenEditors', false); -export const DirtyEditorContext = new RawContextKey('dirtyEditor', false); -export const ReadonlyEditorContext = new RawContextKey('readonlyEditor', false); +export const OpenEditorsDirtyEditorContext = new RawContextKey('dirtyEditorFocusedInOpenEditors', false); +export const OpenEditorsReadonlyEditorContext = new RawContextKey('readonlyEditorFocusedInOpenEditors', false); export const ResourceSelectedForCompareContext = new RawContextKey('resourceSelectedForCompare', false); export const REMOVE_ROOT_FOLDER_COMMAND_ID = 'removeRootFolder'; @@ -372,9 +372,14 @@ async function saveSelectedEditors(accessor: ServicesAccessor, options?: ISaveEd // Special treatment for side by side editors: if the active editor // has 2 sides, we consider both, to support saving both sides. - // We only allow this when saving, not for "Save As". + // We only allow this when saving, not for "Save As" and not if any + // editor is untitled which would bring up a "Save As" dialog too. // See also https://github.com/microsoft/vscode/issues/4180 - if (activeGroup.activeEditor instanceof SideBySideEditorInput && !options?.saveAs) { + // See also https://github.com/microsoft/vscode/issues/106330 + if ( + activeGroup.activeEditor instanceof SideBySideEditorInput && + !options?.saveAs && !(activeGroup.activeEditor.primary.isUntitled() || activeGroup.activeEditor.secondary.isUntitled()) + ) { editors.push({ groupId: activeGroup.id, editor: activeGroup.activeEditor.primary }); editors.push({ groupId: activeGroup.id, editor: activeGroup.activeEditor.secondary }); } else { diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 72635f3c07..1078bd1775 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -324,13 +324,13 @@ export class ExplorerView extends ViewPane { } this.horizontalScrolling = undefined; - DOM.removeClass(this.treeContainer, 'highlight'); + this.treeContainer.classList.remove('highlight'); } await this.refresh(false, stat.parent, false); if (isEditing) { - DOM.addClass(this.treeContainer, 'highlight'); + this.treeContainer.classList.add('highlight'); this.tree.reveal(stat); } else { this.tree.domFocus(); @@ -588,6 +588,13 @@ export class ExplorerView extends ViewPane { return this.tree.updateChildren(toRefresh, recursive); } + focusNextIfItemFocused(item: ExplorerItem): void { + const focus = this.tree.getFocus(); + if (focus.length === 1 && focus[0] === item) { + this.tree.focusNext(); + } + } + getOptimalWidth(): number { const parentNode = this.tree.getHTMLElement(); const childNodes = ([] as HTMLElement[]).slice.call(parentNode.querySelectorAll('.explorer-item .label-name')); // select all file labels @@ -811,8 +818,8 @@ export class ExplorerView extends ViewPane { } const newStyles = content.join('\n'); - if (newStyles !== this.styleElement.innerHTML) { - this.styleElement.innerHTML = newStyles; + if (newStyles !== this.styleElement.textContent) { + this.styleElement.textContent = newStyles; } } @@ -825,12 +832,12 @@ export class ExplorerView extends ViewPane { } function createFileIconThemableTreeContainerScope(container: HTMLElement, themeService: IThemeService): IDisposable { - DOM.addClass(container, 'file-icon-themable-tree'); - DOM.addClass(container, 'show-file-icons'); + container.classList.add('file-icon-themable-tree'); + container.classList.add('show-file-icons'); const onDidChangeFileIconTheme = (theme: IFileIconTheme) => { - DOM.toggleClass(container, 'align-icons-and-twisties', theme.hasFileIcons && !theme.hasFolderIcons); - DOM.toggleClass(container, 'hide-arrows', theme.hidesExplorerArrows === true); + container.classList.toggle('align-icons-and-twisties', theme.hasFileIcons && !theme.hasFolderIcons); + container.classList.toggle('hide-arrows', theme.hidesExplorerArrows === true); }; onDidChangeFileIconTheme(themeService.getFileIconTheme()); diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 1540cd4ab7..b0486ba8ed 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -171,7 +171,7 @@ export class CompressedNavigationController implements ICompressedNavigationCont this.updateCollapsed(this.collapsed); if (this._index < this.labels.length) { - DOM.addClass(this.labels[this._index], 'active'); + this.labels[this._index].classList.add('active'); } } @@ -212,9 +212,9 @@ export class CompressedNavigationController implements ICompressedNavigationCont return; } - DOM.removeClass(this.labels[this._index], 'active'); + this.labels[this._index].classList.remove('active'); this._index = index; - DOM.addClass(this.labels[this._index], 'active'); + this.labels[this._index].classList.add('active'); this._onDidChange.fire(); } @@ -285,7 +285,7 @@ export class FilesRenderer implements ICompressibleTreeRenderer { this.compressedDragOverElement = iconLabelName.element; this.compressedDropTargetDisposable.dispose(); this.compressedDropTargetDisposable = toDisposable(() => { - DOM.removeClass(iconLabelName.element, 'drop-target'); + iconLabelName.element.classList.remove('drop-target'); this.compressedDragOverElement = undefined; }); - DOM.addClass(iconLabelName.element, 'drop-target'); + iconLabelName.element.classList.add('drop-target'); } return typeof result === 'boolean' ? result : { ...result, feedback: [] }; @@ -1456,8 +1456,8 @@ function getIconLabelNameFromHTMLElement(target: HTMLElement | EventTarget | Ele let element: HTMLElement | null = target; - while (element && !DOM.hasClass(element, 'monaco-list-row')) { - if (DOM.hasClass(element, 'label-name') && element.hasAttribute('data-icon-label-count')) { + while (element && !element.classList.contains('monaco-list-row')) { + if (element.classList.contains('label-name') && element.hasAttribute('data-icon-label-count')) { const count = Number(element.getAttribute('data-icon-label-count')); const index = Number(element.getAttribute('data-icon-label-index')); diff --git a/src/vs/workbench/contrib/files/browser/views/media/openeditors.css b/src/vs/workbench/contrib/files/browser/views/media/openeditors.css index 92b0226bba..1476f1860b 100644 --- a/src/vs/workbench/contrib/files/browser/views/media/openeditors.css +++ b/src/vs/workbench/contrib/files/browser/views/media/openeditors.css @@ -17,7 +17,8 @@ color: inherit; } -.open-editors .monaco-list .monaco-list-row > .monaco-action-bar .codicon-close { +.open-editors .monaco-list .monaco-list-row > .monaco-action-bar .codicon-close, +.open-editors .monaco-list .monaco-list-row > .monaco-action-bar .codicon-pinned { width: 8px; height: 22px; display: flex; @@ -25,8 +26,9 @@ justify-content: center; } -.open-editors .monaco-list .monaco-list-row.dirty:not(:hover) > .monaco-action-bar .codicon-close::before { - content: "\ea71"; /* Close icon flips between black dot and "X" for dirty open editors */ +.open-editors .monaco-list .monaco-list-row.dirty:not(:hover) > .monaco-action-bar .codicon-close::before, +.open-editors .monaco-list .monaco-list-row.dirty:not(:hover) > .monaco-action-bar .codicon-pinned::before { + content: "\ea71"; /* Close/Unpin icon flips between black dot and normal for dirty open editors */ } .open-editors .monaco-list .monaco-list-row > .monaco-action-bar .action-close-all-files, diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 8d32e3a6d1..d8c98ea208 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -16,7 +16,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IEditorInput, Verbosity, toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { SaveAllAction, SaveAllInGroupAction, CloseGroupAction } from 'vs/workbench/contrib/files/browser/fileActions'; import { OpenEditorsFocusedContext, ExplorerFocusedContext, IFilesConfiguration, OpenEditor } from 'vs/workbench/contrib/files/common/files'; -import { CloseAllEditorsAction, CloseEditorAction } from 'vs/workbench/browser/parts/editor/editorActions'; +import { CloseAllEditorsAction, CloseEditorAction, UnpinEditorAction } from 'vs/workbench/browser/parts/editor/editorActions'; import { ToggleEditorLayoutAction } from 'vs/workbench/browser/actions/layoutActions'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { attachStylerCallback } from 'vs/platform/theme/common/styler'; @@ -31,7 +31,7 @@ import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; -import { DirtyEditorContext, OpenEditorsGroupContext, ReadonlyEditorContext } from 'vs/workbench/contrib/files/browser/fileCommands'; +import { OpenEditorsDirtyEditorContext, OpenEditorsGroupContext, OpenEditorsReadonlyEditorContext } from 'vs/workbench/contrib/files/browser/fileCommands'; import { ResourceContextKey } from 'vs/workbench/common/resources'; import { ResourcesDropHandler, fillResourceDataTransfers, CodeDataTransfers, containsDragType } from 'vs/workbench/browser/dnd'; import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; @@ -144,6 +144,7 @@ export class OpenEditorsView extends ViewPane { } case GroupChangeKind.EDITOR_DIRTY: case GroupChangeKind.EDITOR_LABEL: + case GroupChangeKind.EDITOR_STICKY: case GroupChangeKind.EDITOR_PIN: { this.list.splice(index, 1, [new OpenEditor(e.editor!, group)]); break; @@ -205,8 +206,8 @@ export class OpenEditorsView extends ViewPane { renderBody(container: HTMLElement): void { super.renderBody(container); - dom.addClass(container, 'open-editors'); - dom.addClass(container, 'show-file-icons'); + container.classList.add('open-editors'); + container.classList.add('show-file-icons'); const delegate = new OpenEditorsDelegate(); @@ -243,8 +244,8 @@ export class OpenEditorsView extends ViewPane { this.resourceContext = this.instantiationService.createInstance(ResourceContextKey); this._register(this.resourceContext); this.groupFocusedContext = OpenEditorsGroupContext.bindTo(this.contextKeyService); - this.dirtyEditorFocusedContext = DirtyEditorContext.bindTo(this.contextKeyService); - this.readonlyEditorFocusedContext = ReadonlyEditorContext.bindTo(this.contextKeyService); + this.dirtyEditorFocusedContext = OpenEditorsDirtyEditorContext.bindTo(this.contextKeyService); + this.readonlyEditorFocusedContext = OpenEditorsReadonlyEditorContext.bindTo(this.contextKeyService); this._register(this.list.onContextMenu(e => this.onListContextMenu(e))); this.list.onDidChangeFocus(e => { @@ -430,10 +431,10 @@ export class OpenEditorsView extends ViewPane { let dirty = this.workingCopyService.dirtyCount; if (dirty === 0) { - dom.addClass(this.dirtyCountElement, 'hidden'); + this.dirtyCountElement.classList.add('hidden'); } else { this.dirtyCountElement.textContent = nls.localize('dirtyCounter', "{0} unsaved", dirty); - dom.removeClass(this.dirtyCountElement, 'hidden'); + this.dirtyCountElement.classList.remove('hidden'); } } @@ -565,6 +566,9 @@ class EditorGroupRenderer implements IListRenderer { static readonly ID = 'openeditor'; + private readonly closeEditorAction = this.instantiationService.createInstance(CloseEditorAction, CloseEditorAction.ID, CloseEditorAction.LABEL); + private readonly unpinEditorAction = this.instantiationService.createInstance(UnpinEditorAction, UnpinEditorAction.ID, UnpinEditorAction.LABEL); + constructor( private labels: ResourceLabels, private instantiationService: IInstantiationService, @@ -583,11 +587,6 @@ class OpenEditorRenderer implements IListRenderer().explorer.decorations, title: editor.getTitle(Verbosity.LONG) }); + const editorAction = openedEditor.isSticky() ? this.unpinEditorAction : this.closeEditorAction; + if (!templateData.actionBar.hasAction(editorAction)) { + if (!templateData.actionBar.isEmpty()) { + templateData.actionBar.clear(); + } + templateData.actionBar.push(editorAction, { icon: true, label: false, keybinding: this.keybindingService.lookupKeybinding(editorAction.id)?.getLabel() }); + } } disposeTemplate(templateData: IOpenEditorTemplateData): void { diff --git a/src/vs/workbench/contrib/files/common/explorerModel.ts b/src/vs/workbench/contrib/files/common/explorerModel.ts index f8c7de5a24..4c241b740c 100644 --- a/src/vs/workbench/contrib/files/common/explorerModel.ts +++ b/src/vs/workbench/contrib/files/common/explorerModel.ts @@ -8,7 +8,7 @@ import { isEqual } from 'vs/base/common/extpath'; import { posix } from 'vs/base/common/path'; import { ResourceMap } from 'vs/base/common/map'; import { IFileStat, IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; -import { rtrim, startsWithIgnoreCase, startsWith, equalsIgnoreCase } from 'vs/base/common/strings'; +import { rtrim, startsWithIgnoreCase, equalsIgnoreCase } from 'vs/base/common/strings'; import { coalesce } from 'vs/base/common/arrays'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; @@ -363,7 +363,7 @@ export class ExplorerItem { // For performance reasons try to do the comparison as fast as possible const ignoreCase = !this.fileService.hasCapability(resource, FileSystemProviderCapabilities.PathCaseSensitive); if (resource && this.resource.scheme === resource.scheme && equalsIgnoreCase(this.resource.authority, resource.authority) && - (ignoreCase ? startsWithIgnoreCase(resource.path, this.resource.path) : startsWith(resource.path, this.resource.path))) { + (ignoreCase ? startsWithIgnoreCase(resource.path, this.resource.path) : resource.path.startsWith(this.resource.path))) { return this.findByPath(rtrim(resource.path, posix.sep), this.resource.path.length, ignoreCase); } diff --git a/src/vs/workbench/contrib/files/common/explorerService.ts b/src/vs/workbench/contrib/files/common/explorerService.ts index 0ea2d5a8a2..51471580cd 100644 --- a/src/vs/workbench/contrib/files/common/explorerService.ts +++ b/src/vs/workbench/contrib/files/common/explorerService.ts @@ -278,6 +278,7 @@ export class ExplorerService implements IExplorerService { const parent = element.parent; // Remove Element from Parent (Model) parent.removeChild(element); + this.view?.focusNextIfItemFocused(element); // Refresh Parent (View) await this.view?.refresh(false, parent); } diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts index bb1c5f8dc7..bd23ee3ce1 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -65,6 +65,7 @@ export interface IExplorerView { setTreeInput(): Promise; itemsCopied(tats: ExplorerItem[], cut: boolean, previousCut: ExplorerItem[] | undefined): void; setEditable(stat: ExplorerItem, isEditing: boolean): Promise; + focusNextIfItemFocused(item: ExplorerItem): void; } export const IExplorerService = createDecorator('explorerService'); @@ -257,6 +258,10 @@ export class OpenEditor implements IEditorIdentifier { return this._group.previewEditor === this.editor; } + isSticky(): boolean { + return this._group.isSticky(this.editor); + } + getResource(): URI | undefined { return toResource(this.editor, { supportSideBySide: SideBySideEditor.PRIMARY }); } diff --git a/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts b/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts index b3caa6dedd..79e8e7b76b 100644 --- a/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts +++ b/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts @@ -84,7 +84,7 @@ class DefaultFormatter extends Disposable implements IWorkbenchContribution { if (defaultFormatterId) { // good -> formatter configured - const [defaultFormatter] = formatter.filter(formatter => ExtensionIdentifier.equals(formatter.extensionId, defaultFormatterId)); + const defaultFormatter = formatter.find(formatter => ExtensionIdentifier.equals(formatter.extensionId, defaultFormatterId)); if (defaultFormatter) { // formatter available return defaultFormatter; @@ -115,13 +115,13 @@ class DefaultFormatter extends Disposable implements IWorkbenchContribution { Severity.Info, message, [{ label: nls.localize('do.config', "Configure..."), run: () => this._pickAndPersistDefaultFormatter(formatter, document).then(resolve, reject) }], - { silent, onCancel: resolve } + { silent, onCancel: () => resolve(undefined) } ); if (silent) { // don't wait when formatting happens without interaction // but pick some formatter... - resolve(formatter[0]); + resolve(undefined); } }); } diff --git a/src/vs/workbench/contrib/issue/electron-browser/issueService.ts b/src/vs/workbench/contrib/issue/electron-browser/issueService.ts index 32279bc586..dc3fab30fa 100644 --- a/src/vs/workbench/contrib/issue/electron-browser/issueService.ts +++ b/src/vs/workbench/contrib/issue/electron-browser/issueService.ts @@ -13,7 +13,7 @@ import { IWorkbenchExtensionEnablementService } from 'vs/workbench/services/exte import { getZoomLevel } from 'vs/base/browser/browser'; import { IWorkbenchIssueService } from 'vs/workbench/contrib/issue/electron-browser/issue'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { platform } from 'process'; import { IProductService } from 'vs/platform/product/common/productService'; diff --git a/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts b/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts index e80187887f..2674fa376a 100644 --- a/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts +++ b/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts @@ -12,7 +12,6 @@ import { IJSONEditingService } from 'vs/workbench/services/configuration/common/ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { language } from 'vs/base/common/platform'; -import { firstIndex } from 'vs/base/common/arrays'; import { IExtensionsViewPaneContainer, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; @@ -47,7 +46,7 @@ export class ConfigureLocaleAction extends Action { public async run(): Promise { const languageOptions = await this.getLanguageOptions(); - const currentLanguageIndex = firstIndex(languageOptions, l => l.label === language); + const currentLanguageIndex = languageOptions.findIndex(l => l.label === language); try { const selectedLanguage = await this.quickInputService.pick(languageOptions, diff --git a/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts b/src/vs/workbench/contrib/logs/electron-sandbox/logs.contribution.ts similarity index 95% rename from src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts rename to src/vs/workbench/contrib/logs/electron-sandbox/logs.contribution.ts index f1e21bf7fa..e8a02456ca 100644 --- a/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/electron-sandbox/logs.contribution.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { OpenLogsFolderAction, OpenExtensionLogsFolderAction } from 'vs/workbench/contrib/logs/electron-browser/logsActions'; +import { OpenLogsFolderAction, OpenExtensionLogsFolderAction } from 'vs/workbench/contrib/logs/electron-sandbox/logsActions'; const workbenchActionsRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); const devCategory = nls.localize({ key: 'developer', comment: ['A developer on Code itself or someone diagnosing issues in Code'] }, "Developer"); diff --git a/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts b/src/vs/workbench/contrib/logs/electron-sandbox/logsActions.ts similarity index 90% rename from src/vs/workbench/contrib/logs/electron-browser/logsActions.ts rename to src/vs/workbench/contrib/logs/electron-sandbox/logsActions.ts index 769b966c9a..adcf7072fb 100644 --- a/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts +++ b/src/vs/workbench/contrib/logs/electron-sandbox/logsActions.ts @@ -8,10 +8,9 @@ import { join } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { IFileService } from 'vs/platform/files/common/files'; -import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; export class OpenLogsFolderAction extends Action { @@ -19,7 +18,7 @@ export class OpenLogsFolderAction extends Action { static readonly LABEL = nls.localize('openLogsFolder', "Open Logs Folder"); constructor(id: string, label: string, - @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IElectronService private readonly electronService: IElectronService, ) { super(id, label); diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index 9c2bff5093..c6faa7879e 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -346,10 +346,10 @@ class MarkerWidget extends Disposable { if (viewModel) { const quickFixAction = viewModel.quickFixAction; this.actionBar.push([quickFixAction], { icon: true, label: false }); - dom.toggleClass(this.icon, 'quickFix', quickFixAction.enabled); + this.icon.classList.toggle('quickFix', quickFixAction.enabled); quickFixAction.onDidChange(({ enabled }) => { if (!isUndefinedOrNull(enabled)) { - dom.toggleClass(this.icon, 'quickFix', enabled); + this.icon.classList.toggle('quickFix', enabled); } }, this, this.disposables); quickFixAction.onShowQuickFixes(() => { @@ -393,7 +393,7 @@ class MarkerWidget extends Disposable { } private renderDetails(marker: IMarker, filterData: MarkerFilterData | undefined, parent: HTMLElement): void { - dom.addClass(parent, 'details-container'); + parent.classList.add('details-container'); if (marker.source || marker.code) { const source = new HighlightedLabel(dom.append(parent, dom.$('.marker-source')), false); diff --git a/src/vs/workbench/contrib/markers/browser/markersView.ts b/src/vs/workbench/contrib/markers/browser/markersView.ts index 076d01d0c1..d01e8de21d 100644 --- a/src/vs/workbench/contrib/markers/browser/markersView.ts +++ b/src/vs/workbench/contrib/markers/browser/markersView.ts @@ -144,7 +144,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { public renderBody(parent: HTMLElement): void { super.renderBody(parent); - dom.addClass(parent, 'markers-panel'); + parent.classList.add('markers-panel'); const container = dom.append(parent, dom.$('.markers-panel-container')); @@ -178,7 +178,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { this.smallLayout = width < 600 && height > 100; if (this.smallLayout !== wasSmallLayout) { if (this.filterActionBar) { - dom.toggleClass(this.filterActionBar.getContainer(), 'hide', !this.smallLayout); + this.filterActionBar.getContainer().classList.toggle('hide', !this.smallLayout); } } const contentHeight = this.smallLayout ? height - 44 : height; @@ -395,8 +395,8 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { private createFilterActionBar(parent: HTMLElement): void { this.filterActionBar = this._register(new ActionBar(parent, { actionViewItemProvider: action => this.getActionViewItem(action) })); - dom.addClass(this.filterActionBar.getContainer(), 'markers-panel-filter-container'); - dom.toggleClass(this.filterActionBar.getContainer(), 'hide', !this.smallLayout); + this.filterActionBar.getContainer().classList.add('markers-panel-filter-container'); + this.filterActionBar.getContainer().classList.toggle('hide', !this.smallLayout); } private createMessageBox(parent: HTMLElement): void { @@ -914,7 +914,7 @@ class MarkersTree extends WorkbenchObjectTree { } toggleVisibility(hide: boolean): void { - dom.toggleClass(this.container, 'hidden', hide); + this.container.classList.toggle('hidden', hide); } } diff --git a/src/vs/workbench/contrib/markers/browser/markersViewActions.ts b/src/vs/workbench/contrib/markers/browser/markersViewActions.ts index 61529e31ce..ff4548a39c 100644 --- a/src/vs/workbench/contrib/markers/browser/markersViewActions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersViewActions.ts @@ -255,7 +255,7 @@ class FiltersDropdownMenuActionViewItem extends DropdownMenuActionViewItem { } updateChecked(): void { - DOM.toggleClass(this.element!, 'checked', this._action.checked); + this.element!.classList.toggle('checked', this._action.checked); } } @@ -290,7 +290,7 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { render(container: HTMLElement): void { this.container = container; - DOM.addClass(this.container, 'markers-panel-action-filter-container'); + this.container.classList.add('markers-panel-action-filter-container'); this.element = DOM.append(this.container, DOM.$('')); this.element.className = this.class; @@ -397,7 +397,7 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { private updateBadge(): void { if (this.filterBadge) { const { total, filtered } = this.filterController.getFilterStats(); - DOM.toggleClass(this.filterBadge, 'hidden', total === filtered || filtered === 0); + this.filterBadge.classList.toggle('hidden', total === filtered || filtered === 0); this.filterBadge.textContent = localize('showing filtered problems', "Showing {0} of {1}", filtered, total); this.adjustInputBox(); } @@ -405,7 +405,7 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { private adjustInputBox(): void { if (this.element && this.filterInputBox && this.filterBadge) { - this.filterInputBox.inputElement.style.paddingRight = DOM.hasClass(this.element, 'small') || DOM.hasClass(this.filterBadge, 'hidden') ? '25px' : '150px'; + this.filterInputBox.inputElement.style.paddingRight = this.element.classList.contains('small') || this.filterBadge.classList.contains('hidden') ? '25px' : '150px'; } } @@ -435,7 +435,7 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { protected updateClass(): void { if (this.element && this.container) { this.element.className = this.class; - DOM.toggleClass(this.container, 'grow', DOM.hasClass(this.element, 'grow')); + this.container.classList.toggle('grow', this.element.classList.contains('grow')); this.adjustInputBox(); } } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts index 99a41d7b9b..e963269bbb 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts @@ -20,7 +20,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; import { BaseCellRenderTemplate, CellEditState, CellFocusMode, ICellViewModel, INotebookEditor, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_OUTPUT_FOCUSED, EXPAND_CELL_CONTENT_COMMAND_ID, NOTEBOOK_CELL_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; -import { CellKind, CellUri, NotebookCellRunState, NOTEBOOK_EDITOR_CURSOR_BOUNDARY } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, CellKind, NotebookCellMetadata, NotebookCellRunState, NOTEBOOK_EDITOR_CURSOR_BOUNDARY } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -145,7 +145,7 @@ abstract class NotebookAction extends Action2 { this.runWithContext(accessor, context); } - abstract async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise; + abstract runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise; private isNotebookActionContext(context?: unknown): context is INotebookActionContext { return !!context && !!(context as INotebookActionContext).notebookEditor; @@ -186,7 +186,7 @@ abstract class NotebookCellAction extends NotebookAction { this.runWithContext(accessor, context); } - abstract async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise; + abstract runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise; } registerAction2(class extends NotebookCellAction { @@ -483,7 +483,7 @@ registerAction2(class extends NotebookCellAction { }); export function getActiveNotebookEditor(editorService: IEditorService): INotebookEditor | undefined { - // TODO can `isNotebookEditor` be on INotebookEditor to avoid a circular dependency? + // TODO@roblourens can `isNotebookEditor` be on INotebookEditor to avoid a circular dependency? const activeEditorPane = editorService.activeEditorPane as unknown as { isNotebookEditor?: boolean } | undefined; return activeEditorPane?.isNotebookEditor ? (editorService.activeEditorPane?.getControl() as INotebookEditor) : undefined; } @@ -512,27 +512,33 @@ export async function changeCellToKind(kind: CellKind, context: INotebookCellAct return null; } + if (!notebookEditor.viewModel) { + return null; + } + const text = cell.getText(); - if (!notebookEditor.insertNotebookCell(cell, kind, 'below', text)) { - return null; - } + const idx = notebookEditor.viewModel.getCellIndex(cell); + notebookEditor.viewModel.notebookDocument.applyEdits(notebookEditor.viewModel.notebookDocument.versionId, [ + { + editType: CellEditType.Replace, + index: idx, + count: 1, + cells: [{ + cellKind: kind, + source: text, + language: language!, + outputs: cell.model.outputs, + metadata: cell.metadata, + }] + } + ], true, undefined, () => undefined, true); + const newCell = notebookEditor.viewModel.viewCells[idx]; - const idx = notebookEditor.viewModel?.getCellIndex(cell); - if (typeof idx !== 'number') { - return null; - } - - const newCell = notebookEditor.viewModel?.viewCells[idx + 1]; if (!newCell) { return null; } - if (language) { - newCell.model.language = language; - } - notebookEditor.focusNotebookCell(newCell, cell.editState === CellEditState.Editing ? 'editor' : 'container'); - notebookEditor.deleteNotebookCell(cell); return newCell; } @@ -963,22 +969,24 @@ registerAction2(class extends NotebookAction { let topPastedCell: CellViewModel | undefined = undefined; pasteCells.items.reverse().map(cell => { - const data = CellUri.parse(cell.uri); - - if (pasteCells.isCopy || data?.notebook.toString() !== viewModel.uri.toString()) { - return viewModel.notebookDocument.createCellTextModel( - cell.getValue(), - cell.language, - cell.cellKind, - [], - cell.metadata - ); - } else { - return cell; - } + return { + source: cell.getValue(), + language: cell.language, + cellKind: cell.cellKind, + outputs: cell.outputs, + metadata: { + editable: cell.metadata?.editable, + runnable: cell.metadata?.runnable, + breakpointMargin: cell.metadata?.breakpointMargin, + hasExecutionOrder: cell.metadata?.hasExecutionOrder, + inputCollapsed: cell.metadata?.inputCollapsed, + outputCollapsed: cell.metadata?.outputCollapsed, + custom: cell.metadata?.custom + } + }; }).forEach(pasteCell => { const newIdx = typeof currCellIndex === 'number' ? currCellIndex + 1 : 0; - topPastedCell = viewModel.insertCell(newIdx, pasteCell, true); + topPastedCell = viewModel.createCell(newIdx, pasteCell.source, pasteCell.language, pasteCell.cellKind, pasteCell.metadata, pasteCell.outputs, true); }); if (topPastedCell) { @@ -1019,21 +1027,23 @@ registerAction2(class extends NotebookCellAction { let topPastedCell: CellViewModel | undefined = undefined; pasteCells.items.reverse().map(cell => { - const data = CellUri.parse(cell.uri); - - if (pasteCells.isCopy || data?.notebook.toString() !== viewModel.uri.toString()) { - return viewModel.notebookDocument.createCellTextModel( - cell.getValue(), - cell.language, - cell.cellKind, - [], - cell.metadata - ); - } else { - return cell; - } + return { + source: cell.getValue(), + language: cell.language, + cellKind: cell.cellKind, + outputs: cell.outputs, + metadata: { + editable: cell.metadata?.editable, + runnable: cell.metadata?.runnable, + breakpointMargin: cell.metadata?.breakpointMargin, + hasExecutionOrder: cell.metadata?.hasExecutionOrder, + inputCollapsed: cell.metadata?.inputCollapsed, + outputCollapsed: cell.metadata?.outputCollapsed, + custom: cell.metadata?.custom + } + }; }).forEach(pasteCell => { - topPastedCell = viewModel.insertCell(currCellIndex, pasteCell, true); + topPastedCell = viewModel.createCell(currCellIndex, pasteCell.source, pasteCell.language, pasteCell.cellKind, pasteCell.metadata, pasteCell.outputs, true); return; }); @@ -1291,14 +1301,25 @@ registerAction2(class extends NotebookCellAction { return; } - editor.viewModel.notebookDocument.clearCellOutput(context.cell.handle); + const cell = context.cell; + const index = editor.viewModel.notebookDocument.cells.indexOf(cell.model); + + if (index < 0) { + return; + } + + editor.viewModel.notebookDocument.applyEdits(editor.viewModel.notebookDocument.versionId, [{ editType: CellEditType.Output, index, outputs: [] }], true, undefined, () => undefined); + if (context.cell.metadata && context.cell.metadata?.runState !== NotebookCellRunState.Running) { - context.notebookEditor.viewModel!.notebookDocument.deltaCellMetadata(context.cell.handle, { - runState: NotebookCellRunState.Idle, - runStartTime: undefined, - lastRunDuration: undefined, - statusMessage: undefined - }); + context.notebookEditor.viewModel!.notebookDocument.applyEdits(context.notebookEditor.viewModel!.notebookDocument.versionId, [{ + editType: CellEditType.Metadata, index, metadata: { + ...context.cell.metadata, + runState: NotebookCellRunState.Idle, + runStartTime: undefined, + lastRunDuration: undefined, + statusMessage: undefined + } + }], true, undefined, () => undefined); } } }); @@ -1374,10 +1395,15 @@ export class ChangeCellLanguageAction extends NotebookCellAction { if (newCell) { context.notebookEditor.focusNotebookCell(newCell, 'editor'); } - } else if (selection.languageId !== 'markdown' && context.cell?.language === 'markdown') { + } else if (selection.languageId !== 'markdown' && context.cell?.cellKind === CellKind.Markdown) { await changeCellToKind(CellKind.Code, { cell: context.cell, notebookEditor: context.notebookEditor }, selection.languageId); } else { - context.notebookEditor.viewModel!.notebookDocument.changeCellLanguage(context.cell.handle, selection.languageId); + const index = context.notebookEditor.viewModel!.notebookDocument.cells.indexOf(context.cell.model); + context.notebookEditor.viewModel!.notebookDocument.applyEdits( + context.notebookEditor.viewModel!.notebookDocument.versionId, + [{ editType: CellEditType.CellLanguage, index, language: selection.languageId }], + true, undefined, () => undefined + ); } } } @@ -1424,7 +1450,10 @@ registerAction2(class extends NotebookAction { return; } - editor.viewModel.notebookDocument.clearAllCellOutputs(); + editor.viewModel.notebookDocument.applyEdits(editor.viewModel.notebookDocument.versionId, + editor.viewModel.notebookDocument.cells.map((cell, index) => ({ + editType: CellEditType.Output, index, outputs: [] + })), true, undefined, () => undefined); } }); @@ -1544,7 +1573,27 @@ registerAction2(class extends NotebookCellAction { } }); -registerAction2(class extends NotebookCellAction { +abstract class ChangeNotebookCellMetadataAction extends NotebookCellAction { + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { + const cell = context.cell; + const textModel = context.notebookEditor.viewModel?.notebookDocument; + if (!textModel) { + return; + } + + const index = textModel.cells.indexOf(cell.model); + + if (index < 0) { + return; + } + + textModel.applyEdits(textModel.versionId, [{ editType: CellEditType.Metadata, index, metadata: { ...context.cell.metadata, ...this.getMetadataDelta() } }], true, undefined, () => undefined); + } + + abstract getMetadataDelta(): NotebookCellMetadata; +} + +registerAction2(class extends ChangeNotebookCellMetadataAction { constructor() { super({ id: COLLAPSE_CELL_INPUT_COMMAND_ID, @@ -1562,12 +1611,12 @@ registerAction2(class extends NotebookCellAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { - context.notebookEditor.viewModel!.notebookDocument.deltaCellMetadata(context.cell.handle, { inputCollapsed: true }); + getMetadataDelta(): NotebookCellMetadata { + return { inputCollapsed: true }; } }); -registerAction2(class extends NotebookCellAction { +registerAction2(class extends ChangeNotebookCellMetadataAction { constructor() { super({ id: EXPAND_CELL_CONTENT_COMMAND_ID, @@ -1585,12 +1634,12 @@ registerAction2(class extends NotebookCellAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { - context.notebookEditor.viewModel!.notebookDocument.deltaCellMetadata(context.cell.handle, { inputCollapsed: false }); + getMetadataDelta(): NotebookCellMetadata { + return { inputCollapsed: false }; } }); -registerAction2(class extends NotebookCellAction { +registerAction2(class extends ChangeNotebookCellMetadataAction { constructor() { super({ id: COLLAPSE_CELL_OUTPUT_COMMAND_ID, @@ -1608,12 +1657,12 @@ registerAction2(class extends NotebookCellAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { - context.notebookEditor.viewModel!.notebookDocument.deltaCellMetadata(context.cell.handle, { outputCollapsed: true }); + getMetadataDelta(): NotebookCellMetadata { + return { outputCollapsed: true }; } }); -registerAction2(class extends NotebookCellAction { +registerAction2(class extends ChangeNotebookCellMetadataAction { constructor() { super({ id: EXPAND_CELL_OUTPUT_COMMAND_ID, @@ -1631,8 +1680,8 @@ registerAction2(class extends NotebookCellAction { }); } - async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise { - context.notebookEditor.viewModel!.notebookDocument.deltaCellMetadata(context.cell.handle, { outputCollapsed: false }); + getMetadataDelta(): NotebookCellMetadata { + return { outputCollapsed: false }; } }); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/scm/scm.ts b/src/vs/workbench/contrib/notebook/browser/contrib/scm/scm.ts index 798acc1a4d..740acb24aa 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/scm/scm.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/scm/scm.ts @@ -43,11 +43,7 @@ export class SCMController extends Disposable implements INotebookEditorContribu this.update(); if (this._notebookEditor.textModel) { - this._localDisposable.add(this._notebookEditor.textModel.onDidChangeContent(() => { - this.update(); - })); - - this._localDisposable.add(this._notebookEditor.textModel.onDidChangeCells(() => { + this._localDisposable.add(this._notebookEditor.textModel.onDidChangeContent((e) => { this.update(); })); } diff --git a/src/vs/workbench/contrib/notebook/browser/diff/cellComponents.ts b/src/vs/workbench/contrib/notebook/browser/diff/cellComponents.ts index eca6546fe6..db7c87de09 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/cellComponents.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/cellComponents.ts @@ -12,12 +12,12 @@ import { CellDiffRenderTemplate, CellDiffViewModelLayoutChangeEvent, DIFF_CELL_M import { EDITOR_BOTTOM_PADDING, EDITOR_TOP_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget'; -import { renderCodiconsAsElement } from 'vs/base/browser/codicons'; +import { renderCodicons } from 'vs/base/browser/codicons'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { format } from 'vs/base/common/jsonFormatter'; import { applyEdits } from 'vs/base/common/jsonEdit'; -import { CellUri, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, CellUri, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { hash } from 'vs/base/common/hash'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -194,9 +194,9 @@ class PropertyHeader extends Disposable { private _updateFoldingIcon() { if (this.accessor.getFoldingState(this.cell) === PropertyFoldingState.Collapsed) { - DOM.reset(this._foldingIndicator, ...renderCodiconsAsElement('$(chevron-right)')); + DOM.reset(this._foldingIndicator, ...renderCodicons('$(chevron-right)')); } else { - DOM.reset(this._foldingIndicator, ...renderCodiconsAsElement('$(chevron-down)')); + DOM.reset(this._foldingIndicator, ...renderCodicons('$(chevron-down)')); } } } @@ -283,6 +283,14 @@ abstract class AbstractCellRenderer extends Disposable { this._metadataHeaderContainer = DOM.append(this._diffEditorContainer, DOM.$('.metadata-header-container')); this._metadataInfoContainer = DOM.append(this._diffEditorContainer, DOM.$('.metadata-info-container')); + const checkIfModified = (cell: CellDiffViewModel) => { + return cell.type !== 'delete' && cell.type !== 'insert' && hash(this._getFormatedMetadataJSON(cell.original?.metadata || {}, cell.original?.language)) !== hash(this._getFormatedMetadataJSON(cell.modified?.metadata ?? {}, cell.modified?.language)); + }; + + if (checkIfModified(this.cell)) { + this.cell.metadataFoldingState = PropertyFoldingState.Expanded; + } + this._metadataHeader = this.instantiationService.createInstance( PropertyHeader, this.cell, @@ -291,7 +299,7 @@ abstract class AbstractCellRenderer extends Disposable { { updateInfoRendering: this.updateMetadataRendering.bind(this), checkIfModified: (cell) => { - return cell.type !== 'delete' && cell.type !== 'insert' && hash(this._getFormatedMetadataJSON(cell.original?.metadata || {}, cell.original?.language)) !== hash(this._getFormatedMetadataJSON(cell.modified?.metadata ?? {}, cell.modified?.language)); + return checkIfModified(cell); }, getFoldingState: (cell) => { return cell.metadataFoldingState; @@ -308,9 +316,24 @@ abstract class AbstractCellRenderer extends Disposable { this._register(this._metadataHeader); this._metadataHeader.buildHeader(); + if (this.notebookEditor.textModel?.transientOptions.transientOutputs) { + this._layoutInfo.outputHeight = 0; + this._layoutInfo.outputStatusHeight = 0; + this.layout({}); + return; + } + this._outputHeaderContainer = DOM.append(this._diffEditorContainer, DOM.$('.output-header-container')); this._outputInfoContainer = DOM.append(this._diffEditorContainer, DOM.$('.output-info-container')); + const checkIfOutputsModified = (cell: CellDiffViewModel) => { + return cell.type !== 'delete' && cell.type !== 'insert' && !this.notebookEditor.textModel!.transientOptions.transientOutputs && cell.type === 'modified' && hash(cell.original?.outputs ?? []) !== hash(cell.modified?.outputs ?? []); + }; + + if (checkIfOutputsModified(this.cell)) { + this.cell.outputFoldingState = PropertyFoldingState.Expanded; + } + this._outputHeader = this.instantiationService.createInstance( PropertyHeader, this.cell, @@ -319,10 +342,10 @@ abstract class AbstractCellRenderer extends Disposable { { updateInfoRendering: this.updateOutputRendering.bind(this), checkIfModified: (cell) => { - return cell.type !== 'delete' && cell.type !== 'insert' && !this.notebookEditor.textModel!.transientOptions.transientOutputs && cell.type === 'modified' && hash(cell.original?.outputs ?? []) !== hash(cell.modified?.outputs ?? []); + return checkIfOutputsModified(cell); }, getFoldingState: (cell) => { - return this.cell.outputFoldingState; + return cell.outputFoldingState; }, updateFoldingState: (cell, state) => { cell.outputFoldingState = state; @@ -449,9 +472,25 @@ abstract class AbstractCellRenderer extends Disposable { } if (newLangauge !== undefined && newLangauge !== this.cell.modified!.language) { - this.notebookEditor.textModel!.changeCellLanguage(this.cell.modified!.handle, newLangauge); + const index = this.notebookEditor.textModel!.cells.indexOf(this.cell.modified!); + this.notebookEditor.textModel!.applyEdits( + this.notebookEditor.textModel!.versionId, + [{ editType: CellEditType.CellLanguage, index, language: newLangauge }], + true, + undefined, + () => undefined + ); } - this.notebookEditor.textModel!.changeCellMetadata(this.cell.modified!.handle, result, false); + + const index = this.notebookEditor.textModel!.cells.indexOf(this.cell.modified!); + + if (index < 0) { + return; + } + + this.notebookEditor.textModel!.applyEdits(this.notebookEditor.textModel!.versionId, [ + { editType: CellEditType.Metadata, index, metadata: result } + ], true, undefined, () => undefined); } catch { } } diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts index 47f8211040..cf385cb7b6 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts @@ -35,6 +35,8 @@ import { FileService } from 'vs/platform/files/common/fileService'; import { IFileService } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; +import { IDiffChange } from 'vs/base/common/diff/diff'; +import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; export const IN_NOTEBOOK_TEXT_DIFF_EDITOR = new RawContextKey('isInNotebookTextDiffEditor', false); @@ -215,6 +217,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD const diffResult = await this.notebookEditorWorkerService.computeDiff(this._model.original.resource, this._model.modified.resource); const cellChanges = diffResult.cellsDiff.changes; + console.log(cellChanges); const cellDiffViewModels: CellDiffViewModel[] = []; const originalModel = this._model.original.notebook; @@ -246,38 +249,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD } } - // modified cells - const modifiedLen = Math.min(change.originalLength, change.modifiedLength); - - for (let j = 0; j < modifiedLen; j++) { - cellDiffViewModels.push(new CellDiffViewModel( - originalModel.cells[change.originalStart + j], - modifiedModel.cells[change.modifiedStart + j], - 'modified', - this._eventDispatcher! - )); - } - - for (let j = modifiedLen; j < change.originalLength; j++) { - // deletion - cellDiffViewModels.push(new CellDiffViewModel( - originalModel.cells[change.originalStart + j], - undefined, - 'delete', - this._eventDispatcher! - )); - } - - for (let j = modifiedLen; j < change.modifiedLength; j++) { - // insertion - cellDiffViewModels.push(new CellDiffViewModel( - undefined, - modifiedModel.cells[change.modifiedStart + j], - 'insert', - this._eventDispatcher! - )); - } - + cellDiffViewModels.push(...this._computeModifiedLCS(change, originalModel, modifiedModel)); originalCellIndex = change.originalStart + change.originalLength; modifiedCellIndex = change.modifiedStart + change.modifiedLength; } @@ -294,6 +266,43 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD this._list.splice(0, this._list.length, cellDiffViewModels); } + private _computeModifiedLCS(change: IDiffChange, originalModel: NotebookTextModel, modifiedModel: NotebookTextModel) { + const result: CellDiffViewModel[] = []; + // modified cells + const modifiedLen = Math.min(change.originalLength, change.modifiedLength); + + for (let j = 0; j < modifiedLen; j++) { + result.push(new CellDiffViewModel( + originalModel.cells[change.originalStart + j], + modifiedModel.cells[change.modifiedStart + j], + 'modified', + this._eventDispatcher! + )); + } + + for (let j = modifiedLen; j < change.originalLength; j++) { + // deletion + result.push(new CellDiffViewModel( + originalModel.cells[change.originalStart + j], + undefined, + 'delete', + this._eventDispatcher! + )); + } + + for (let j = modifiedLen; j < change.modifiedLength; j++) { + // insertion + result.push(new CellDiffViewModel( + undefined, + modifiedModel.cells[change.modifiedStart + j], + 'insert', + this._eventDispatcher! + )); + } + + return result; + } + private pendingLayouts = new WeakMap(); @@ -321,7 +330,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD r(); })); - return new Promise(resolve => { r = resolve; }); + return new Promise(resolve => { r = resolve; }); } getDomNode() { diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts index 9ab2785a1f..984b6f3a8e 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts @@ -221,8 +221,8 @@ export class NotebookTextDiffList extends WorkbenchList imple } const newStyles = content.join('\n'); - if (newStyles !== this.styleElement.innerHTML) { - this.styleElement.innerHTML = newStyles; + if (newStyles !== this.styleElement.textContent) { + this.styleElement.textContent = newStyles; } } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 84a884bdba..fdd25e55e4 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -290,7 +290,7 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri private onEditorOpening2(originalInput: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup): IOpenEditorOverride | undefined { let id = typeof options?.override === 'string' ? options.override : undefined; - if (id === undefined && originalInput.isUntitled()) { + if (id === undefined && originalInput.resource?.scheme === Schemas.untitled) { return undefined; } @@ -564,9 +564,9 @@ class NotebookFileTracker implements IWorkbenchContribution { constructor( @INotebookService private readonly _notebookService: INotebookService, @IEditorService private readonly _editorService: IEditorService, - @IWorkingCopyService workingCopyService: IWorkingCopyService, + @IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService, ) { - this._dirtyListener = Event.debounce(workingCopyService.onDidChangeDirty, () => { }, 100)(() => { + this._dirtyListener = Event.debounce(_workingCopyService.onDidChangeDirty, () => { }, 100)(() => { const inputs = this._createMissingNotebookEditors(); this._editorService.openEditors(inputs); }); @@ -580,7 +580,7 @@ class NotebookFileTracker implements IWorkbenchContribution { const result: IResourceEditorInput[] = []; for (const notebook of this._notebookService.getNotebookTextModels()) { - if (notebook.isDirty && !this._editorService.isOpen({ resource: notebook.uri })) { + if (this._workingCopyService.isDirty(notebook.uri.with({ scheme: Schemas.vscodeNotebook })) && !this._editorService.isOpen({ resource: notebook.uri })) { result.push({ resource: notebook.uri, options: { inactive: true, preserveFocus: true, pinned: true } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index c9250a80c5..a17f346a05 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -233,6 +233,7 @@ export interface INotebookEditor extends IEditor { getDomNode(): HTMLElement; getOverflowContainerDomNode(): HTMLElement; getInnerWebview(): Webview | undefined; + getSelectionHandles(): number[]; /** * Focus the notebook editor cell list @@ -507,7 +508,7 @@ export interface INotebookCellList { layout(height?: number, width?: number): void; dispose(): void; - // TODO resolve differences between List and INotebookCellList + // TODO@roblourens resolve differences between List and INotebookCellList getFocus(): number[]; setFocus(indexes: number[]): void; setSelection(indexes: number[]): void; @@ -698,7 +699,7 @@ export function getVisibleCells(cells: CellViewModel[], hiddenRanges: ICellRange } export function getActiveNotebookEditor(editorService: IEditorService): INotebookEditor | undefined { - // TODO can `isNotebookEditor` be on INotebookEditor to avoid a circular dependency? + // TODO@rebornix can `isNotebookEditor` be on INotebookEditor to avoid a circular dependency? const activeEditorPane = editorService.activeEditorPane as unknown as { isNotebookEditor?: boolean } | undefined; return activeEditorPane?.isNotebookEditor ? (editorService.activeEditorPane?.getControl() as INotebookEditor) : undefined; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookDiffEditorInput.ts b/src/vs/workbench/contrib/notebook/browser/notebookDiffEditorInput.ts index a67cb51ce6..e5b95c7e14 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookDiffEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookDiffEditorInput.ts @@ -193,14 +193,14 @@ ${patterns} return; } - async resolve(editorId?: string): Promise { + async resolve(): Promise { if (!await this._notebookService.canResolve(this.viewType!)) { return null; } if (!this._textModel) { - this._textModel = await this._notebookModelResolverService.resolve(this.resource, this.viewType!, editorId); - this._originalTextModel = await this._notebookModelResolverService.resolve(this.originalResource, this.viewType!, editorId); + this._textModel = await this._notebookModelResolverService.resolve(this.resource, this.viewType!); + this._originalTextModel = await this._notebookModelResolverService.resolve(this.originalResource, this.viewType!); } return new NotebookDiffEditorModel(this._originalTextModel!.object as NotebookEditorModel, this._textModel.object as NotebookEditorModel); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index cf18ddfbff..2ac57bcc32 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -25,6 +25,7 @@ import { IEditorDropService } from 'vs/workbench/services/editor/browser/editorD import { IEditorGroup, IEditorGroupsService, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { NotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; const NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'NotebookEditorViewState'; @@ -54,6 +55,7 @@ export class NotebookEditor extends EditorPane { @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, @IEditorDropService private readonly _editorDropService: IEditorDropService, @INotificationService private readonly _notificationService: INotificationService, + @INotebookService private readonly _notebookService: INotebookService, @INotebookEditorWidgetService private readonly _notebookWidgetService: INotebookEditorWidgetService, ) { super(NotebookEditor.ID, telemetryService, themeService, storageService); @@ -152,7 +154,7 @@ export class NotebookEditor extends EditorPane { this._widget.value!.layout(this._dimension, this._rootElement); } - const model = await input.resolve(this._widget.value!.getId()); + const model = await input.resolve(); // Check for cancellation if (token.isCancellationRequested) { return undefined; @@ -174,6 +176,8 @@ export class NotebookEditor extends EditorPane { return; } + await this._notebookService.resolveNotebookEditor(model.viewType, model.resource, this._widget.value!.getId()); + const viewState = this._loadNotebookEditorViewState(input); await this._widget.value!.setModel(model.notebook, viewState); @@ -266,9 +270,9 @@ export class NotebookEditor extends EditorPane { super.dispose(); } - toJSON(): object { - return { - notebookHandle: this.viewModel?.handle - }; - } + // toJSON(): object { + // return { + // notebookHandle: this.viewModel?.handle + // }; + // } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts index bd18acae62..104e915481 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts @@ -168,13 +168,13 @@ ${patterns} return; } - async resolve(editorId?: string): Promise { + async resolve(): Promise { if (!await this._notebookService.canResolve(this.viewType!)) { return null; } if (!this._textModel) { - this._textModel = await this._notebookModelResolverService.resolve(this.resource, this.viewType!, editorId); + this._textModel = await this._notebookModelResolverService.resolve(this.resource, this.viewType!); this._register(this._textModel.object.onDidChangeDirty(() => { this._onDidChangeDirty.fire(); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index c42dbad42f..df79b8e86e 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -215,6 +215,9 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._cursorNavigationMode = v; } + private readonly _onDidChangeSelection = this._register(new Emitter()); + get onDidChangeSelection(): Event { return this._onDidChangeSelection.event; } + private readonly _onDidChangeVisibleRanges = this._register(new Emitter()); onDidChangeVisibleRanges: Event = this._onDidChangeVisibleRanges.event; @@ -268,6 +271,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return this._uuid; } + getSelectionHandles(): number[] { + return this.viewModel?.selectionHandles || []; + } + hasModel() { return !!this._notebookViewModel; } @@ -933,6 +940,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } } + this._localStore.add(this.viewModel.onDidChangeSelection(() => { + this._onDidChangeSelection.fire(); + })); + this._localStore.add(this._list!.onWillScroll(e => { this._onWillScroll.fire(e); if (!this._webviewResolved) { @@ -1269,7 +1280,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor (direction === 'above' ? index : nextIndex) : index; const focused = this._list?.getFocusedElements(); - const newCell = this._notebookViewModel!.createCell(insertIndex, initialText, language, type, undefined, true, undefined, focused); + const newCell = this._notebookViewModel!.createCell(insertIndex, initialText, language, type, undefined, [], true, undefined, focused); return newCell as CellViewModel; } @@ -1735,9 +1746,9 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor super.dispose(); } - toJSON(): object { + toJSON(): { notebookUri: URI | undefined } { return { - notebookHandle: this.viewModel?.handle + notebookUri: this.viewModel?.uri, }; } } @@ -1778,7 +1789,7 @@ export const notebookOutputContainerColor = registerColor('notebook.outputContai hc: null }, nls.localize('notebook.outputContainerBackgroundColor', "The Color of the notebook output container background.")); -// TODO currently also used for toolbar border, if we keep all of this, pick a generic name +// TODO@rebornix currently also used for toolbar border, if we keep all of this, pick a generic name export const CELL_TOOLBAR_SEPERATOR = registerColor('notebook.cellToolbarSeparator', { dark: Color.fromHex('#808080').transparent(0.35), light: Color.fromHex('#808080').transparent(0.35), diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index a9ad013bde..f058e1d18e 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -9,6 +9,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Iterable } from 'vs/base/common/iterator'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ResourceMap } from 'vs/base/common/map'; +import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { RedoCommand, UndoCommand } from 'vs/editor/browser/editorExtensions'; import { CopyAction, CutAction, PasteAction } from 'vs/editor/contrib/clipboard/clipboard'; @@ -26,7 +27,7 @@ import { NotebookKernelProviderAssociationRegistry, NotebookViewTypesExtensionRe import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, CellEditType, CellOutputKind, CellUri, DisplayOrderKey, ICellEditOperation, IDisplayOutput, INotebookKernelInfo2, INotebookKernelProvider, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, ITransformedDisplayOutputDto, mimeTypeSupportedByCore, NotebookCellOutputsSplice, notebookDocumentFilterMatch, NotebookEditorPriority, NOTEBOOK_DISPLAY_ORDER, sortMimeTypes } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, CellEditType, CellKind, CellOutputKind, DisplayOrderKey, ICellEditOperation, IDisplayOutput, INotebookKernelInfo2, INotebookKernelProvider, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, ITransformedDisplayOutputDto, mimeTypeSupportedByCore, NotebookCellOutputsSplice, notebookDocumentFilterMatch, NotebookEditorPriority, NOTEBOOK_DISPLAY_ORDER, sortMimeTypes } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer'; import { NotebookEditorDescriptor, NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; import { IMainNotebookController, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; @@ -225,7 +226,6 @@ class ModelData implements IDisposable { } export class NotebookService extends Disposable implements INotebookService, ICustomEditorViewTypesHandler { declare readonly _serviceBrand: undefined; - static mainthreadNotebookDocumentHandle: number = 0; private readonly _notebookProviders = new Map(); notebookProviderInfoStore: NotebookProviderInfoStore; notebookRenderersInfoStore: NotebookOutputRendererInfoStore = new NotebookOutputRendererInfoStore(); @@ -240,10 +240,12 @@ export class NotebookService extends Disposable implements INotebookService, ICu public readonly onNotebookEditorAdd: Event = this._onNotebookEditorAdd.event; private readonly _onNotebookEditorsRemove: Emitter = this._register(new Emitter()); public readonly onNotebookEditorsRemove: Event = this._onNotebookEditorsRemove.event; - private readonly _onNotebookDocumentAdd: Emitter = this._register(new Emitter()); - public readonly onNotebookDocumentAdd: Event = this._onNotebookDocumentAdd.event; - private readonly _onNotebookDocumentRemove: Emitter = this._register(new Emitter()); - public readonly onNotebookDocumentRemove: Event = this._onNotebookDocumentRemove.event; + + private readonly _onDidAddNotebookDocument = this._register(new Emitter()); + private readonly _onDidRemoveNotebookDocument = this._register(new Emitter()); + readonly onDidAddNotebookDocument = this._onDidAddNotebookDocument.event; + readonly onDidRemoveNotebookDocument = this._onDidRemoveNotebookDocument.event; + private readonly _onNotebookDocumentSaved: Emitter = this._register(new Emitter()); public readonly onNotebookDocumentSaved: Event = this._onNotebookDocumentSaved.event; private readonly _notebookEditors = new Map(); @@ -424,30 +426,24 @@ export class NotebookService extends Disposable implements INotebookService, ICu let topPastedCell: CellViewModel | undefined = undefined; pasteCells.items.reverse().map(cell => { - const data = CellUri.parse(cell.uri); - - if (pasteCells.isCopy || data?.notebook.toString() !== viewModel.uri.toString()) { - return viewModel.notebookDocument.createCellTextModel( - cell.getValue(), - cell.language, - cell.cellKind, - [], - { - editable: cell.metadata?.editable, - runnable: cell.metadata?.runnable, - breakpointMargin: cell.metadata?.breakpointMargin, - hasExecutionOrder: cell.metadata?.hasExecutionOrder, - inputCollapsed: cell.metadata?.inputCollapsed, - outputCollapsed: cell.metadata?.outputCollapsed, - custom: cell.metadata?.custom - } - ); - } else { - return cell; - } + return { + source: cell.getValue(), + language: cell.language, + cellKind: cell.cellKind, + outputs: cell.outputs, + metadata: { + editable: cell.metadata?.editable, + runnable: cell.metadata?.runnable, + breakpointMargin: cell.metadata?.breakpointMargin, + hasExecutionOrder: cell.metadata?.hasExecutionOrder, + inputCollapsed: cell.metadata?.inputCollapsed, + outputCollapsed: cell.metadata?.outputCollapsed, + custom: cell.metadata?.custom + } + }; }).forEach(pasteCell => { const newIdx = typeof currCellIndex === 'number' ? currCellIndex + 1 : 0; - topPastedCell = viewModel.insertCell(newIdx, pasteCell, true); + topPastedCell = viewModel.createCell(newIdx, pasteCell.source, pasteCell.language, pasteCell.cellKind, pasteCell.metadata, pasteCell.outputs, true); }); if (topPastedCell) { @@ -460,21 +456,23 @@ export class NotebookService extends Disposable implements INotebookService, ICu let topPastedCell: CellViewModel | undefined = undefined; pasteCells.items.reverse().map(cell => { - const data = CellUri.parse(cell.uri); - - if (pasteCells.isCopy || data?.notebook.toString() !== viewModel.uri.toString()) { - return viewModel.notebookDocument.createCellTextModel( - cell.getValue(), - cell.language, - cell.cellKind, - [], - cell.metadata - ); - } else { - return cell; - } + return { + source: cell.getValue(), + language: cell.language, + cellKind: cell.cellKind, + outputs: cell.outputs, + metadata: { + editable: cell.metadata?.editable, + runnable: cell.metadata?.runnable, + breakpointMargin: cell.metadata?.breakpointMargin, + hasExecutionOrder: cell.metadata?.hasExecutionOrder, + inputCollapsed: cell.metadata?.inputCollapsed, + outputCollapsed: cell.metadata?.outputCollapsed, + custom: cell.metadata?.custom + } + }; }).forEach(pasteCell => { - topPastedCell = viewModel.insertCell(0, pasteCell, true); + topPastedCell = viewModel.createCell(0, pasteCell.source, pasteCell.language, pasteCell.cellKind, pasteCell.metadata, pasteCell.outputs, true); }); if (topPastedCell) { @@ -543,14 +541,13 @@ export class NotebookService extends Disposable implements INotebookService, ICu return this._notebookProviders.has(viewType); } - registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController) { + registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController): IDisposable { this._notebookProviders.set(viewType, { extensionData, controller }); this._onDidChangeViewTypes.fire(); - } - - unregisterNotebookProvider(viewType: string): void { - this._notebookProviders.delete(viewType); - this._onDidChangeViewTypes.fire(); + return toDisposable(() => { + this._notebookProviders.delete(viewType); + this._onDidChangeViewTypes.fire(); + }); } registerNotebookKernelProvider(provider: INotebookKernelProvider): IDisposable { @@ -605,7 +602,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu return this.notebookRenderersInfoStore.get(id); } - async resolveNotebook(viewType: string, uri: URI, forceReload: boolean, editorId?: string, backupId?: string): Promise { + async resolveNotebook(viewType: string, uri: URI, forceReload: boolean, backupId?: string): Promise { if (!await this.canResolve(viewType)) { throw new Error(`CANNOT load notebook, no provider for '${viewType}'`); @@ -622,8 +619,16 @@ export class NotebookService extends Disposable implements INotebookService, ICu return notebookModel; } else { - notebookModel = this._instantiationService.createInstance(NotebookTextModel, NotebookService.mainthreadNotebookDocumentHandle++, viewType, provider.controller.supportBackup, uri); - await provider.controller.createNotebook(notebookModel, backupId); + const dataDto = await provider.controller.resolveNotebookDocument(viewType, uri, backupId); + let cells = dataDto.data.cells.length ? dataDto.data.cells : (uri.scheme === Schemas.untitled ? [{ + cellKind: CellKind.Code, + language: dataDto.data.languages.length ? dataDto.data.languages[0] : '', + outputs: [], + metadata: undefined, + source: '' + }] : []); + + notebookModel = this._instantiationService.createInstance(NotebookTextModel, viewType, provider.controller.supportBackup, uri, cells, dataDto.data.languages, dataDto.data.metadata, dataDto.transientOptions); } // new notebook model created @@ -633,14 +638,10 @@ export class NotebookService extends Disposable implements INotebookService, ICu ); this._models.set(uri, modelData); - this._onNotebookDocumentAdd.fire([notebookModel.uri]); + this._onDidAddNotebookDocument.fire(notebookModel); // after the document is added to the store and sent to ext host, we transform the ouputs await this.transformTextModelOutputs(notebookModel); - if (editorId) { - await provider.controller.resolveNotebookEditor(viewType, uri, editorId); - } - return modelData.model; } @@ -658,7 +659,7 @@ export class NotebookService extends Disposable implements INotebookService, ICu cell.outputs.forEach((output) => { if (output.outputKind === CellOutputKind.Rich) { - // TODO no string[] casting + // TODO@rebornix no string[] casting const ret = this._transformMimeTypes(output, output.outputId, textModel.metadata.displayOrder as string[] || []); const orderedMimeTypes = ret.orderedMimeTypes!; const pickedMimeTypeIndex = ret.pickedMimeTypeIndex!; @@ -786,6 +787,13 @@ export class NotebookService extends Disposable implements INotebookService, ICu return ret; } + async resolveNotebookEditor(viewType: string, uri: URI, editorId: string): Promise { + const entry = this._notebookProviders.get(viewType); + if (entry) { + entry.controller.resolveNotebookEditor(viewType, uri, editorId); + } + } + removeNotebookEditor(editor: INotebookEditor) { const editorCache = this._notebookEditors.get(editor.getId()); @@ -919,19 +927,12 @@ export class NotebookService extends Disposable implements INotebookService, ICu } }); + modelData.model.dispose(); + modelData.dispose(); + willRemovedEditors.forEach(e => this._notebookEditors.delete(e.getId())); - - const provider = this._notebookProviders.get(modelData!.model.viewType); - - if (provider) { - provider.controller.removeNotebookDocument(modelData!.model.uri); - modelData!.model.dispose(); - } - - this._onNotebookEditorsRemove.fire(willRemovedEditors.map(e => e)); - this._onNotebookDocumentRemove.fire([modelData.model.uri]); - modelData?.dispose(); + this._onDidRemoveNotebookDocument.fire(modelData.model.uri); } } } diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 67f66667b6..c86bae4bf4 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -760,7 +760,7 @@ export class NotebookCellList extends WorkbenchList implements ID upwards = true; } - const editorAttachedPromise = new Promise((resolve, reject) => { + const editorAttachedPromise = new Promise((resolve, reject) => { element.onDidChangeEditorAttachState(() => { element.editorAttached ? resolve() : reject(); }); @@ -1032,8 +1032,8 @@ export class NotebookCellList extends WorkbenchList implements ID } const newStyles = content.join('\n'); - if (newStyles !== this.styleElement.innerHTML) { - this.styleElement.innerHTML = newStyles; + if (newStyles !== this.styleElement.textContent) { + this.styleElement.textContent = newStyles; } } @@ -1052,7 +1052,7 @@ export class NotebookCellList extends WorkbenchList implements ID } function getEditorAttachedPromise(element: CellViewModel) { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { Event.once(element.onDidChangeEditorAttachState)(() => element.editorAttached ? resolve() : reject()); }); } 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 eb7668ce89..11ff2c306b 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -11,7 +11,6 @@ import * as path from 'vs/base/common/path'; import { isWeb } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import * as UUID from 'vs/base/common/uuid'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IOpenerService, matchesScheme } from 'vs/platform/opener/common/opener'; import { CELL_MARGIN, CELL_RUN_GUTTER, CODE_CELL_LEFT_MARGIN, CELL_OUTPUT_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -242,9 +241,8 @@ export class BackLayerWebView extends Disposable { @IWebviewService readonly webviewService: IWebviewService, @IOpenerService readonly openerService: IOpenerService, @INotebookService private readonly notebookService: INotebookService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IFileDialogService private readonly fileDialogService: IFileDialogService, @IFileService private readonly fileService: IFileService, ) { @@ -358,7 +356,7 @@ export class BackLayerWebView extends Disposable { async createWebview(): Promise { const pathsPath = getPathFromAmdModule(require, 'vs/loader.js'); - const loader = asWebviewUri(this.workbenchEnvironmentService, this.id, URI.file(pathsPath)); + const loader = asWebviewUri(this.environmentService, this.id, URI.file(pathsPath)); let coreDependencies = ''; let resolveFunc: () => void; @@ -367,7 +365,7 @@ export class BackLayerWebView extends Disposable { resolveFunc = resolve; }); - const baseUrl = asWebviewUri(this.workbenchEnvironmentService, this.id, dirname(this.documentUri)); + const baseUrl = asWebviewUri(this.environmentService, this.id, dirname(this.documentUri)); if (!isWeb) { coreDependencies = ``; @@ -797,7 +795,7 @@ ${loaderJs} if (this.environmentService.isExtensionDevelopment && (preload.scheme === 'http' || preload.scheme === 'https')) { return preload; } - return asWebviewUri(this.workbenchEnvironmentService, this.id, preload); + return asWebviewUri(this.environmentService, this.id, preload); }); preloads.forEach(e => { @@ -827,7 +825,7 @@ ${loaderJs} const extensionLocations: URI[] = []; for (const rendererInfo of renderers) { const preloads = [rendererInfo.entrypoint, ...rendererInfo.preloads] - .map(preload => asWebviewUri(this.workbenchEnvironmentService, this.id, preload)); + .map(preload => asWebviewUri(this.environmentService, this.id, preload)); extensionLocations.push(rendererInfo.extensionLocation); preloads.forEach(e => { 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 c9b6b165c6..567a16b894 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -10,7 +10,7 @@ import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/lis import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { IAction } from 'vs/base/common/actions'; -import { renderCodiconsAsElement } from 'vs/base/browser/codicons'; +import { renderCodicons } from 'vs/base/browser/codicons'; import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -48,7 +48,7 @@ import { StatefulMarkdownCell } from 'vs/workbench/contrib/notebook/browser/view 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, NotebookCellMetadata, NotebookCellRunState, ShowCellStatusBarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, CellKind, NotebookCellMetadata, NotebookCellRunState, ShowCellStatusBarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { createAndFillInActionBarActionsWithVerticalSeparators, VerticalSeparator, VerticalSeparatorViewItem } from './cellActionView'; const $ = DOM.$; @@ -332,16 +332,27 @@ abstract class AbstractCellRenderer { return; } + const textModel = this.notebookEditor.viewModel!.notebookDocument; + const index = textModel.cells.indexOf(templateData.currentRenderedCell.model); + + if (index < 0) { + return; + } + if (templateData.currentRenderedCell.metadata?.inputCollapsed) { - this.notebookEditor.viewModel!.notebookDocument.deltaCellMetadata(templateData.currentRenderedCell.handle, { inputCollapsed: false }); + textModel.applyEdits(textModel.versionId, [ + { editType: CellEditType.Metadata, index, metadata: { ...templateData.currentRenderedCell.metadata, inputCollapsed: false } } + ], true, undefined, () => undefined); } else if (templateData.currentRenderedCell.metadata?.outputCollapsed) { - this.notebookEditor.viewModel!.notebookDocument.deltaCellMetadata(templateData.currentRenderedCell.handle, { outputCollapsed: false }); + textModel.applyEdits(textModel.versionId, [ + { editType: CellEditType.Metadata, index, metadata: { ...templateData.currentRenderedCell.metadata, outputCollapsed: false } } + ], true, undefined, () => undefined); } })); } protected setupCollapsedPart(container: HTMLElement): { collapsedPart: HTMLElement, expandButton: HTMLElement } { - const collapsedPart = DOM.append(container, $('.cell.cell-collapsed-part', undefined, ...renderCodiconsAsElement('$(unfold)'))); + const collapsedPart = DOM.append(container, $('.cell.cell-collapsed-part', undefined, ...renderCodicons('$(unfold)'))); const expandButton = collapsedPart.querySelector('.codicon') as HTMLElement; const keybinding = this.keybindingService.lookupKeybinding(EXPAND_CELL_CONTENT_COMMAND_ID); let title = localize('cellExpandButtonLabel', "Expand"); @@ -586,8 +597,9 @@ class CodeCellDragImageRenderer { } private getDragImageImpl(templateData: BaseCellRenderTemplate, editor: ICodeEditor, type: 'code' | 'markdown'): HTMLElement | null { - const dragImageContainer = DOM.$(`.cell-drag-image.monaco-list-row.focused.${type}-cell-row`); - dragImageContainer.innerHTML = templateData.container.innerHTML; + const dragImageContainer = templateData.container.cloneNode(true) as HTMLElement; + dragImageContainer.classList.forEach(c => dragImageContainer.classList.remove(c)); + dragImageContainer.classList.add('cell-drag-image', 'monaco-list-row', 'focused', `${type}-cell-row`); const editorContainer = dragImageContainer.querySelector('.cell-editor-container'); if (!editorContainer) { @@ -948,11 +960,11 @@ export class RunStateRenderer { } if (runState === NotebookCellRunState.Success) { - DOM.reset(this.element, ...renderCodiconsAsElement('$(check)')); + DOM.reset(this.element, ...renderCodicons('$(check)')); } else if (runState === NotebookCellRunState.Error) { - DOM.reset(this.element, ...renderCodiconsAsElement('$(error)')); + DOM.reset(this.element, ...renderCodicons('$(error)')); } else if (runState === NotebookCellRunState.Running) { - DOM.reset(this.element, ...renderCodiconsAsElement('$(sync~spin)')); + DOM.reset(this.element, ...renderCodicons('$(sync~spin)')); this.spinnerTimer = setTimeout(() => { this.spinnerTimer = undefined; @@ -1010,6 +1022,8 @@ export class ListTopCellToolbar extends Disposable { this._modelDisposables.add(this.notebookEditor.viewModel.onDidChangeViewCells(() => { this.updateClass(); })); + + this.updateClass(); } })); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets.ts index 01bca607db..e7578473a1 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets.ts @@ -77,8 +77,8 @@ export class CellEditorStatusBar extends Disposable { return; } - this.leftContributedItemsContainer.innerHTML = ''; - this.rightContributedItemsContainer.innerHTML = ''; + DOM.clearNode(this.leftContributedItemsContainer); + DOM.clearNode(this.rightContributedItemsContainer); this.itemsDisposable.clear(); const items = this.notebookCellStatusBarService.getEntries(this.currentContext.cell.uri); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/commonViewComponents.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/commonViewComponents.ts index 6f66b8d30f..20add0d026 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/commonViewComponents.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/commonViewComponents.ts @@ -9,7 +9,7 @@ import { MenuItemAction } from 'vs/platform/actions/common/actions'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { renderCodiconsAsElement } from 'vs/base/browser/codicons'; +import { renderCodicons } from 'vs/base/browser/codicons'; export class CodiconActionViewItem extends MenuEntryActionViewItem { constructor( @@ -22,7 +22,7 @@ export class CodiconActionViewItem extends MenuEntryActionViewItem { } updateLabel(): void { if (this.options.label && this.label) { - DOM.reset(this.label, ...renderCodiconsAsElement(this._commandAction.label ?? '')); + DOM.reset(this.label, ...renderCodicons(this._commandAction.label ?? '')); } } } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/dnd.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/dnd.ts index d7040a6e9d..b935f5c21c 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/dnd.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/dnd.ts @@ -256,7 +256,7 @@ export class CellDragAndDropController extends Disposable { } private copyCells(draggedCells: ICellViewModel[], ontoCell: ICellViewModel, direction: 'above' | 'below') { - this.notebookEditor.textModel!.pushStackElement('Copy Cells'); + this.notebookEditor.textModel!.pushStackElement('Copy Cells', undefined); let firstNewCell: ICellViewModel | undefined = undefined; let firstNewCellState: CellEditState = CellEditState.Preview; for (let i = 0; i < draggedCells.length; i++) { @@ -273,6 +273,6 @@ export class CellDragAndDropController extends Disposable { this.notebookEditor.focusNotebookCell(firstNewCell, firstNewCellState === CellEditState.Editing ? 'editor' : 'container'); } - this.notebookEditor.textModel!.pushStackElement('Copy Cells'); + this.notebookEditor.textModel!.pushStackElement('Copy Cells', undefined); } } 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 baf76da88f..84a262478d 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts @@ -6,7 +6,7 @@ import * as DOM from 'vs/base/browser/dom'; import { raceCancellation } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { renderCodiconsAsElement } from 'vs/base/browser/codicons'; +import { renderCodicons } from 'vs/base/browser/codicons'; 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'; @@ -315,10 +315,10 @@ export class StatefulMarkdownCell extends Disposable { this.templateData.foldingIndicator.innerText = ''; break; case CellFoldingState.Collapsed: - DOM.reset(this.templateData.foldingIndicator, ...renderCodiconsAsElement('$(chevron-right)')); + DOM.reset(this.templateData.foldingIndicator, ...renderCodicons('$(chevron-right)')); break; case CellFoldingState.Expanded: - DOM.reset(this.templateData.foldingIndicator, ...renderCodiconsAsElement('$(chevron-down)')); + DOM.reset(this.templateData.foldingIndicator, ...renderCodicons('$(chevron-down)')); break; default: diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 4677177666..63a6b474f6 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -88,7 +88,7 @@ function webviewPreloads() { } } - // TODO: should script with src not be removed? + // TODO@connor4312: should script with src not be removed? container.appendChild(scriptTag).parentNode!.removeChild(scriptTag); } }; diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/cellEdit.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/cellEdit.ts index e6f35391bc..4337f9f2a0 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/cellEdit.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/cellEdit.ts @@ -5,7 +5,7 @@ import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; -import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, IProcessedOutput, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IResourceUndoRedoElement, UndoRedoElementType } from 'vs/platform/undoRedo/common/undoRedo'; import { URI } from 'vs/base/common/uri'; import { BaseCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel'; @@ -16,7 +16,7 @@ import { ITextCellEditingDelegate } from 'vs/workbench/contrib/notebook/common/m export interface IViewCellEditingDelegate extends ITextCellEditingDelegate { createCellViewModel?(cell: NotebookCellTextModel): BaseCellViewModel; - createCell?(index: number, source: string | string[], language: string, type: CellKind): BaseCellViewModel; + createCell?(index: number, source: string, language: string, type: CellKind, metadata: NotebookCellMetadata | undefined, outputs: IProcessedOutput[]): BaseCellViewModel; } export class JoinCellEdit implements IResourceUndoRedoElement { @@ -52,12 +52,10 @@ export class JoinCellEdit implements IResourceUndoRedoElement { const cell = this.editingDelegate.createCellViewModel(this._deletedRawCell); if (this.direction === 'above') { - this.editingDelegate.insertCell(this.index, this._deletedRawCell); - this.editingDelegate.emitSelections([cell.handle]); + this.editingDelegate.insertCell(this.index, this._deletedRawCell, [cell.handle]); cell.focusMode = CellFocusMode.Editor; } else { - this.editingDelegate.insertCell(this.index, cell.model); - this.editingDelegate.emitSelections([this.cell.handle]); + this.editingDelegate.insertCell(this.index, cell.model, [this.cell.handle]); this.cell.focusMode = CellFocusMode.Editor; } } @@ -72,8 +70,7 @@ export class JoinCellEdit implements IResourceUndoRedoElement { { range: this.inverseRange, text: this.insertContent } ]); - this.editingDelegate.deleteCell(this.index); - this.editingDelegate.emitSelections([this.cell.handle]); + this.editingDelegate.deleteCell(this.index, [this.cell.handle]); this.cell.focusMode = CellFocusMode.Editor; } } @@ -110,10 +107,9 @@ export class SplitCellEdit implements IResourceUndoRedoElement { this.cell.setSelections(this.selections); for (let j = 1; j < this.cellContents.length; j++) { - this.editingDelegate.deleteCell(this.index + 1); + this.editingDelegate.deleteCell(this.index + 1, j === this.cellContents.length - 1 ? [this.cell.handle] : undefined); } - this.editingDelegate.emitSelections([this.cell.handle]); this.cell.focusMode = CellFocusMode.Editor; } @@ -130,11 +126,10 @@ export class SplitCellEdit implements IResourceUndoRedoElement { let insertIndex = this.index + 1; let lastCell; for (let j = 1; j < this.cellContents.length; j++, insertIndex++) { - lastCell = this.editingDelegate.createCell(insertIndex, this.cellContents[j], this.language, this.cellKind); + lastCell = this.editingDelegate.createCell(insertIndex, this.cellContents[j], this.language, this.cellKind, {}, []); } if (lastCell) { - this.editingDelegate.emitSelections([lastCell.handle]); lastCell.focusMode = CellFocusMode.Editor; } } diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts index 9f9b3d0039..f8118d6863 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts @@ -23,7 +23,7 @@ import { NotebookEventDispatcher, NotebookMetadataChangedEvent } from 'vs/workbe import { CellFoldingState, EditorFoldingStateDelegate } from 'vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel'; import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; -import { CellKind, NotebookCellMetadata, INotebookSearchOptions, ICellRange } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, NotebookCellMetadata, INotebookSearchOptions, ICellRange, NotebookCellsChangeType, ICell, NotebookCellTextModelSplice, CellEditType, IProcessedOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { FoldingRegions } from 'vs/editor/contrib/folding/foldingRanges'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { MarkdownRenderer } from 'vs/workbench/contrib/notebook/browser/view/renderers/mdRenderer'; @@ -170,10 +170,6 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD return this._notebook; } - get handle() { - return this._notebook.handle; - } - get resolvedLanguages() { return this._notebook.resolvedLanguages; } @@ -218,7 +214,6 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD } this._selections = selections; - this._notebook.selections = selections; this._onDidChangeSelection.fire(); } @@ -252,8 +247,8 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD this.id = '$notebookViewModel' + MODEL_ID; this._instanceId = strings.singleLetterHash(MODEL_ID); - this._register(this._notebook.onDidChangeCells(e => { - const diffs = e.splices.map(splice => { + const compute = (changes: NotebookCellTextModelSplice[], synchronous: boolean) => { + const diffs = changes.map(splice => { return [splice[0], splice[1], splice[2].map(cell => { return createCellViewModel(this._instantiationService, this, cell as NotebookCellTextModel); })] as [number, number, CellViewModel[]]; @@ -276,7 +271,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD }); this._onDidChangeViewCells.fire({ - synchronous: e.synchronous, + synchronous: synchronous, splices: diffs }); @@ -307,17 +302,36 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD } this.selectionHandles = endSelectionHandles; + }; + + this._register(this._notebook.onDidChangeContent(e => { + for (let i = 0; i < e.rawEvents.length; i++) { + const change = e.rawEvents[i]; + let changes: NotebookCellTextModelSplice[] = []; + + if (change.kind === NotebookCellsChangeType.ModelChange || change.kind === NotebookCellsChangeType.Initialize) { + changes = change.changes; + compute(changes, e.synchronous); + continue; + } else if (change.kind === NotebookCellsChangeType.Move) { + compute([[change.index, change.length, []]], e.synchronous); + compute([[change.newIdx, 0, change.cells]], e.synchronous); + } else { + continue; + } + } })); - this._register(this._notebook.onDidChangeMetadata(e => { - this.eventDispatcher.emit([new NotebookMetadataChangedEvent(e)]); - })); + this._register(this._notebook.onDidChangeContent(contentChanges => { + contentChanges.rawEvents.forEach(e => { + if (e.kind === NotebookCellsChangeType.ChangeDocumentMetadata) { + this.eventDispatcher.emit([new NotebookMetadataChangedEvent(this._notebook.metadata)]); + } + }); - this._register(this._notebook.emitSelections(selections => { - // text model emit selection change (for example, undo/redo) - // we should update the selection handle wisely - // TODO, if the editor is note selected, undo/redo should not change the focused element selection - this.updateSelectionsFromEdits(selections); + if (contentChanges.endSelections) { + this.updateSelectionsFromEdits(contentChanges.endSelections); + } })); this._register(this.eventDispatcher.onDidChangeLayout((e) => { @@ -609,16 +623,24 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD return result; } - createCell(index: number, source: string, language: string, type: CellKind, metadata: NotebookCellMetadata | undefined, synchronous: boolean, pushUndoStop: boolean = true, previouslyFocused: ICellViewModel[] = []) { + createCell(index: number, source: string, language: string, type: CellKind, metadata: NotebookCellMetadata | undefined, outputs: IProcessedOutput[], synchronous: boolean, pushUndoStop: boolean = true, previouslyFocused: ICellViewModel[] = []): CellViewModel { const beforeSelections = previouslyFocused.map(e => e.handle); - this._notebook.createCell2(index, source, language, type, metadata, synchronous, pushUndoStop, beforeSelections, undefined); - // TODO, rely on createCell to be sync - return this.viewCells[index]; - } - - insertCell(index: number, cell: NotebookCellTextModel, synchronous: boolean, pushUndoStop: boolean = true): CellViewModel { - this._notebook.insertCell2(index, cell, synchronous, pushUndoStop); - // TODO, rely on createCell to be sync // this will trigger it to synchronous update + this._notebook.applyEdits(this._notebook.versionId, [ + { + editType: CellEditType.Replace, + index, + count: 0, + cells: [ + { + cellKind: type, + language: language, + outputs: outputs, + metadata: metadata, + source: source + } + ] + } + ], synchronous, beforeSelections, () => undefined); return this._viewCells[index]; } @@ -641,7 +663,18 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD } } - this._notebook.deleteCell2(index, synchronous, pushUndoStop, this.selectionHandles, endSelections); + this._notebook.applyEdits(this._notebook.versionId, [ + { + editType: CellEditType.Replace, + index: index, + count: 1, + cells: [] + }], + synchronous, + this.selectionHandles, + () => endSelections, + pushUndoStop + ); } /** @@ -658,7 +691,14 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD return false; } - this._notebook.moveCellToIdx2(index, length, newIdx, synchronous, pushedToUndoStack, undefined, [viewCell.handle]); + this._notebook.applyEdits(this._notebook.versionId, [ + { + editType: CellEditType.Move, + index, + length, + newIdx + } + ], synchronous, undefined, () => [viewCell.handle]); return true; } @@ -743,9 +783,28 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD const newLinesContents = this._computeCellLinesContents(cell, splitPoints); if (newLinesContents) { const editorSelections = cell.getSelections(); - this._notebook.splitNotebookCell(index, newLinesContents, this.selectionHandles); const language = cell.language; const kind = cell.cellKind; + this._notebook.applyEdits(this._notebook.versionId, [ + { + editType: CellEditType.CellContent, + index, + range: undefined, + text: newLinesContents[0] + }, + { + editType: CellEditType.Replace, + index: index + 1, + count: 0, + cells: newLinesContents.slice(1).map(line => ({ + cellKind: kind, + language, + source: line, + outputs: [], + metadata: {} + })) + } + ], true, undefined, () => this.selectionHandles, false); this._undoService.pushElement(new SplitCellEdit( this.uri, @@ -756,14 +815,11 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD language, kind, { - createCell: (index: number, source: string, language: string, type: CellKind) => { - return this.createCell(index, source, language, type, undefined, true, false) as BaseCellViewModel; + createCell: (index: number, source: string, language: string, type: CellKind, metadata: NotebookCellMetadata | undefined, outputs: IProcessedOutput[]) => { + return this.createCell(index, source, language, type, metadata, outputs, true, false) as BaseCellViewModel; }, deleteCell: (index: number) => { this.deleteCell(index, true, false); - }, - emitSelections: (selections: number[]) => { - this.updateSelectionsFromEdits(selections); } } )); @@ -832,17 +888,14 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD insertContent, cell, { - insertCell: (index: number, cell: NotebookCellTextModel) => { - this.insertCell(index, cell, true, false); + insertCell: (index: number, cell: NotebookCellTextModel, endSelections?: number[]) => { + this.createCell(index, cell.getValue(), cell.language, cell.cellKind, cell.metadata, cell.outputs, true, false); }, deleteCell: (index: number) => { this.deleteCell(index, true, false); }, createCellViewModel: (cell: NotebookCellTextModel) => { return createCellViewModel(this._instantiationService, this, cell); - }, - emitSelections: (selections: number[]) => { - this.updateSelectionsFromEdits(selections); } }) ); @@ -886,17 +939,14 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD insertContent, below, { - insertCell: (index: number, cell: NotebookCellTextModel) => { - this.insertCell(index, cell, true, false); + insertCell: (index: number, cell: NotebookCellTextModel, endSelections?: number[]) => { + this.createCell(index, cell.getValue(), cell.language, cell.cellKind, cell.metadata, cell.outputs, true, false); }, deleteCell: (index: number) => { this.deleteCell(index, true, false); }, createCellViewModel: (cell: NotebookCellTextModel) => { return createCellViewModel(this._instantiationService, this, cell); - }, - emitSelections: (selections: number[]) => { - this.updateSelectionsFromEdits(selections); } }) ); @@ -907,11 +957,15 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD getEditorViewState(): INotebookEditorViewState { const editingCells: { [key: number]: boolean } = {}; - this._viewCells.filter(cell => cell.editState === CellEditState.Editing).forEach(cell => editingCells[cell.model.handle] = true); + this._viewCells.forEach((cell, i) => { + if (cell.editState === CellEditState.Editing) { + editingCells[i] = true; + } + }); const editorViewStates: { [key: number]: editorCommon.ICodeEditorViewState } = {}; - this._viewCells.map(cell => ({ handle: cell.model.handle, state: cell.saveEditorViewState() })).forEach(viewState => { + this._viewCells.map(cell => ({ handle: cell.model.handle, state: cell.saveEditorViewState() })).forEach((viewState, i) => { if (viewState.state) { - editorViewStates[viewState.handle] = viewState.state; + editorViewStates[i] = viewState.state; } }); @@ -927,8 +981,8 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD } this._viewCells.forEach((cell, index) => { - const isEditing = viewState.editingCells && viewState.editingCells[cell.handle]; - const editorViewState = viewState.editorViewStates && viewState.editorViewStates[cell.handle]; + const isEditing = viewState.editingCells && viewState.editingCells[index]; + const editorViewState = viewState.editorViewStates && viewState.editorViewStates[index]; cell.editState = isEditing ? CellEditState.Editing : CellEditState.Preview; const cellHeight = viewState.cellTotalHeights ? viewState.cellTotalHeights[index] : undefined; diff --git a/src/vs/workbench/contrib/notebook/common/model/cellEdit.ts b/src/vs/workbench/contrib/notebook/common/model/cellEdit.ts index fbc9c78208..00fb14971c 100644 --- a/src/vs/workbench/contrib/notebook/common/model/cellEdit.ts +++ b/src/vs/workbench/contrib/notebook/common/model/cellEdit.ts @@ -12,11 +12,10 @@ import { NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/noteb * It should not modify Undo/Redo stack */ export interface ITextCellEditingDelegate { - insertCell?(index: number, cell: NotebookCellTextModel): void; - deleteCell?(index: number): void; + insertCell?(index: number, cell: NotebookCellTextModel, endSelections?: number[]): void; + deleteCell?(index: number, endSelections?: number[]): void; moveCell?(fromIndex: number, length: number, toIndex: number, beforeSelections: number[] | undefined, endSelections: number[] | undefined): void; updateCellMetadata?(index: number, newMetadata: NotebookCellMetadata): void; - emitSelections(selections: number[]): void; } @@ -38,20 +37,14 @@ export class InsertCellEdit implements IResourceUndoRedoElement { throw new Error('Notebook Delete Cell not implemented for Undo/Redo'); } - this.editingDelegate.deleteCell(this.insertIndex); - if (this.beforedSelections) { - this.editingDelegate.emitSelections(this.beforedSelections); - } + this.editingDelegate.deleteCell(this.insertIndex, this.beforedSelections); } redo(): void { if (!this.editingDelegate.insertCell) { throw new Error('Notebook Insert Cell not implemented for Undo/Redo'); } - this.editingDelegate.insertCell(this.insertIndex, this.cell); - if (this.endSelections) { - this.editingDelegate.emitSelections(this.endSelections); - } + this.editingDelegate.insertCell(this.insertIndex, this.cell, this.endSelections); } } @@ -77,10 +70,7 @@ export class DeleteCellEdit implements IResourceUndoRedoElement { throw new Error('Notebook Insert Cell not implemented for Undo/Redo'); } - this.editingDelegate.insertCell(this.insertIndex, this._cell); - if (this.beforedSelections) { - this.editingDelegate.emitSelections(this.beforedSelections); - } + this.editingDelegate.insertCell(this.insertIndex, this._cell, this.beforedSelections); } redo(): void { @@ -88,10 +78,7 @@ export class DeleteCellEdit implements IResourceUndoRedoElement { throw new Error('Notebook Delete Cell not implemented for Undo/Redo'); } - this.editingDelegate.deleteCell(this.insertIndex); - if (this.endSelections) { - this.editingDelegate.emitSelections(this.endSelections); - } + this.editingDelegate.deleteCell(this.insertIndex, this.endSelections); } } @@ -116,9 +103,6 @@ export class MoveCellEdit implements IResourceUndoRedoElement { } this.editingDelegate.moveCell(this.toIndex, this.length, this.fromIndex, this.endSelections, this.beforedSelections); - if (this.beforedSelections) { - this.editingDelegate.emitSelections(this.beforedSelections); - } } redo(): void { @@ -127,9 +111,6 @@ export class MoveCellEdit implements IResourceUndoRedoElement { } this.editingDelegate.moveCell(this.fromIndex, this.length, this.toIndex, this.beforedSelections, this.endSelections); - if (this.endSelections) { - this.editingDelegate.emitSelections(this.endSelections); - } } } @@ -152,17 +133,13 @@ export class SpliceCellsEdit implements IResourceUndoRedoElement { this.diffs.forEach(diff => { for (let i = 0; i < diff[2].length; i++) { - this.editingDelegate.deleteCell!(diff[0]); + this.editingDelegate.deleteCell!(diff[0], this.beforeHandles); } diff[1].reverse().forEach(cell => { - this.editingDelegate.insertCell!(diff[0], cell); + this.editingDelegate.insertCell!(diff[0], cell, this.beforeHandles); }); }); - - if (this.beforeHandles) { - this.editingDelegate.emitSelections(this.beforeHandles); - } } redo(): void { @@ -172,17 +149,13 @@ export class SpliceCellsEdit implements IResourceUndoRedoElement { this.diffs.reverse().forEach(diff => { for (let i = 0; i < diff[1].length; i++) { - this.editingDelegate.deleteCell!(diff[0]); + this.editingDelegate.deleteCell!(diff[0], this.endHandles); } diff[2].reverse().forEach(cell => { - this.editingDelegate.insertCell!(diff[0], cell); + this.editingDelegate.insertCell!(diff[0], cell, this.endHandles); }); }); - - if (this.endHandles) { - this.editingDelegate.emitSelections(this.endHandles); - } } } diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts index aa03ba69c5..3e8ad898aa 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts @@ -108,8 +108,8 @@ export class NotebookCellTextModel extends Disposable implements ICell { return this._hash; } - // TODO, raw outputs - this._hash = hash([hash(this.getValue()), this._getPersisentMetadata, this.transientOptions.transientOutputs ? [] : this._outputs]); + // TODO@rebornix, raw outputs + this._hash = hash([hash(this.language), hash(this.getValue()), this._getPersisentMetadata, this.transientOptions.transientOutputs ? [] : this._outputs]); return this._hash; } diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index 0d7cb947b0..93a53ef908 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -5,26 +5,22 @@ import * as nls from 'vs/nls'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, dispose, 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, NotebookCellsChangedEvent, CellKind, IProcessedOutput, notebookDocumentMetadataDefaults, diff, NotebookCellsChangeType, ICellDto2, IMainCellDto, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookTextModel, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, CellEditType, CellUri, notebookDocumentMetadataDefaults, diff, NotebookCellsChangeType, ICellDto2, TransientOptions, NotebookTextModelChangedEvent, NotebookRawContentEvent } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ITextSnapshot } from 'vs/editor/common/model'; import { IUndoRedoService, UndoRedoElementType, IUndoRedoElement, IResourceUndoRedoElement } from 'vs/platform/undoRedo/common/undoRedo'; -import { InsertCellEdit, DeleteCellEdit, MoveCellEdit, SpliceCellsEdit, CellMetadataEdit } from 'vs/workbench/contrib/notebook/common/model/cellEdit'; +import { MoveCellEdit, SpliceCellsEdit, CellMetadataEdit } from 'vs/workbench/contrib/notebook/common/model/cellEdit'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { IModeService } from 'vs/editor/common/services/modeService'; +import { IRange } from 'vs/editor/common/core/range'; export class NotebookTextModelSnapshot implements ITextSnapshot { - // private readonly _pieces: Ce[] = []; + private _index: number = -1; - constructor(private _model: NotebookTextModel) { - // for (let i = 0; i < this._model.cells.length; i++) { - // const cell = this._model.cells[i]; - // this._pieces.push(this._model.cells[i].textBuffer.createSnapshot(true)); - // } - } + constructor(private _model: NotebookTextModel) { } read(): string | null { @@ -63,42 +59,62 @@ class StackOperation implements IResourceUndoRedoElement { type: UndoRedoElementType.Resource; private _operations: IUndoRedoElement[] = []; + private _beginSelectionState: number[] | undefined = undefined; + private _resultSelectionState: number[] | undefined = undefined; - constructor(readonly resource: URI, readonly label: string) { + constructor(readonly resource: URI, readonly label: string, private _delayedEmitter: DelayedEmitter, selectionState: number[] | undefined) { this.type = UndoRedoElementType.Resource; + this._beginSelectionState = selectionState; } - pushEditOperation(element: IUndoRedoElement) { + pushEndSelectionState(selectionState: number[] | undefined) { + this._resultSelectionState = selectionState; + } + pushEditOperation(element: IUndoRedoElement, beginSelectionState: number[] | undefined, resultSelectionState: number[] | undefined) { + if (this._operations.length === 0) { + this._beginSelectionState = this._beginSelectionState || beginSelectionState; + } this._operations.push(element); + this._resultSelectionState = resultSelectionState; } - undo(): void { - this._operations.reverse().forEach(o => o.undo()); + async undo(): Promise { + this._delayedEmitter.beginDeferredEmit(); + for (let i = this._operations.length - 1; i >= 0; i--) { + await this._operations[i].undo(); + } + this._delayedEmitter.endDeferredEmit(this._beginSelectionState); } - redo(): void | Promise { - this._operations.forEach(o => o.redo()); + + async redo(): Promise { + this._delayedEmitter.beginDeferredEmit(); + for (let i = 0; i < this._operations.length; i++) { + await this._operations[i].redo(); + } + this._delayedEmitter.endDeferredEmit(this._resultSelectionState); } } export class NotebookOperationManager { private _pendingStackOperation: StackOperation | null = null; - constructor(private _undoService: IUndoRedoService, private _resource: URI) { + constructor(private _undoService: IUndoRedoService, private _resource: URI, private _delayedEmitter: DelayedEmitter) { } - pushStackElement(label: string) { + pushStackElement(label: string, selectionState: number[] | undefined) { if (this._pendingStackOperation) { + this._pendingStackOperation.pushEndSelectionState(selectionState); this._undoService.pushElement(this._pendingStackOperation); this._pendingStackOperation = null; return; } - this._pendingStackOperation = new StackOperation(this._resource, label); + this._pendingStackOperation = new StackOperation(this._resource, label, this._delayedEmitter, selectionState); } - pushEditOperation(element: IUndoRedoElement) { + pushEditOperation(element: IUndoRedoElement, beginSelectionState: number[] | undefined, resultSelectionState: number[] | undefined) { if (this._pendingStackOperation) { - this._pendingStackOperation.pushEditOperation(element); + this._pendingStackOperation.pushEditOperation(element, beginSelectionState, resultSelectionState); return; } @@ -106,27 +122,86 @@ export class NotebookOperationManager { } } +class DelayedEmitter { + private _deferredCnt: number = 0; + private _notebookTextModelChangedEvent: NotebookTextModelChangedEvent | null = null; + constructor( + private readonly _onDidChangeContent: Emitter, + private readonly _computeEndState: () => void, + private readonly _textModel: NotebookTextModel + + ) { + + } + + beginDeferredEmit(): void { + this._deferredCnt++; + } + + endDeferredEmit(endSelections: number[] | undefined): void { + this._deferredCnt--; + if (this._deferredCnt === 0) { + this._computeEndState(); + + if (this._notebookTextModelChangedEvent) { + this._onDidChangeContent.fire( + { + rawEvents: this._notebookTextModelChangedEvent.rawEvents, + versionId: this._textModel.versionId, + endSelections: endSelections || this._notebookTextModelChangedEvent.endSelections, + synchronous: this._notebookTextModelChangedEvent.synchronous + } + ); + } + + this._notebookTextModelChangedEvent = null; + } + } + + + emit(data: NotebookRawContentEvent, synchronous: boolean, endSelections?: number[]) { + + if (this._deferredCnt === 0) { + this._computeEndState(); + this._onDidChangeContent.fire( + { + rawEvents: [data], + versionId: this._textModel.versionId, + synchronous, + endSelections + } + ); + } else { + if (!this._notebookTextModelChangedEvent) { + this._notebookTextModelChangedEvent = { + rawEvents: [data], + versionId: this._textModel.versionId, + endSelections: endSelections, + synchronous: synchronous + }; + } else { + // merge + this._notebookTextModelChangedEvent = { + rawEvents: [...this._notebookTextModelChangedEvent.rawEvents, data], + versionId: this._textModel.versionId, + endSelections: endSelections ? endSelections : this._notebookTextModelChangedEvent.endSelections, + synchronous: synchronous + }; + } + } + } +} + export class NotebookTextModel extends Disposable implements INotebookTextModel { - private _cellhandlePool: number = 0; - private readonly _onWillDispose: Emitter = this._register(new Emitter()); + private readonly _onDidChangeContent = this._register(new Emitter()); readonly onWillDispose: Event = this._onWillDispose.event; - private readonly _onDidChangeCells = this._register(new Emitter<{ synchronous: boolean, splices: NotebookCellTextModelSplice[] }>()); - get onDidChangeCells() { return this._onDidChangeCells.event; } - private readonly _emitSelections = this._register(new Emitter()); - get emitSelections() { return this._emitSelections.event; } - private _onDidModelChangeProxy = this._register(new Emitter()); - get onDidModelChangeProxy(): Event { return this._onDidModelChangeProxy.event; } - private _onDidSelectionChangeProxy = this._register(new Emitter()); - get onDidSelectionChange(): Event { return this._onDidSelectionChangeProxy.event; } - private _onDidChangeContent = this._register(new Emitter()); - onDidChangeContent: Event = this._onDidChangeContent.event; - private _onDidChangeMetadata = this._register(new Emitter()); - onDidChangeMetadata: Event = this._onDidChangeMetadata.event; + readonly onDidChangeContent = this._onDidChangeContent.event; + private _cellhandlePool: number = 0; private _mapping: Map = new Map(); private _cellListeners: Map = new Map(); - cells: NotebookCellTextModel[]; + private _cells: NotebookCellTextModel[] = []; private _languages: string[] = []; private _allLanguages: boolean = false; @@ -144,70 +219,47 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel metadata: NotebookDocumentMetadata = notebookDocumentMetadataDefaults; transientOptions: TransientOptions = { transientMetadata: {}, transientOutputs: false }; - private _isUntitled: boolean | undefined = undefined; private _versionId = 0; + private _operationManager: NotebookOperationManager; + private _eventEmitter: DelayedEmitter; + + get cells(): readonly NotebookCellTextModel[] { + return this._cells; + } get versionId() { return this._versionId; } - private _selections: number[] = []; - - get selections() { - return this._selections; - } - - set selections(selections: number[]) { - this._selections = selections; - this._onDidSelectionChangeProxy.fire(this._selections); - } - - private _dirty = false; - protected readonly _onDidChangeDirty = this._register(new Emitter()); - readonly onDidChangeDirty = this._onDidChangeDirty.event; - - private _operationManager: NotebookOperationManager; - constructor( - public handle: number, - public viewType: string, - public supportBackup: boolean, - public uri: URI, + readonly viewType: string, + readonly supportBackup: boolean, + readonly uri: URI, + cells: ICellDto2[], + languages: string[], + metadata: NotebookDocumentMetadata, + options: TransientOptions, @IUndoRedoService private _undoService: IUndoRedoService, @ITextModelService private _modelService: ITextModelService, @IModeService private readonly _modeService: IModeService, ) { super(); - this.cells = []; + this.transientOptions = options; + this.metadata = metadata; + this.updateLanguages(languages); + this._initialize(cells); - this._operationManager = new NotebookOperationManager(this._undoService, uri); + this._eventEmitter = new DelayedEmitter( + this._onDidChangeContent, + () => { this._increaseVersionId(); }, + this + ); + + this._operationManager = new NotebookOperationManager(this._undoService, uri, this._eventEmitter); } - get isDirty() { - return this._dirty; - } - - setDirty(newState: boolean) { - if (this._dirty !== newState) { - this._dirty = newState; - this._onDidChangeDirty.fire(); - } - } - - createCellTextModel( - source: string, - language: string, - cellKind: CellKind, - outputs: IProcessedOutput[], - metadata: NotebookCellMetadata | undefined - ) { - const cellHandle = this._cellhandlePool++; - const cellUri = CellUri.generate(this.uri, cellHandle); - return new NotebookCellTextModel(cellUri, cellHandle, source, language, cellKind, outputs || [], metadata || {}, this.transientOptions, this._modelService); - } - - initialize(cells: ICellDto2[]) { - this.cells = []; + private _initialize(cells: ICellDto2[]) { + this._cells = []; this._versionId = 0; const mainCells = cells.map(cell => { @@ -216,111 +268,150 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel return new NotebookCellTextModel(cellUri, cellHandle, cell.source, cell.language, cell.cellKind, cell.outputs || [], cell.metadata, this.transientOptions, this._modelService); }); - this._isUntitled = false; - for (let i = 0; i < mainCells.length; i++) { this._mapping.set(mainCells[i].handle, mainCells[i]); const dirtyStateListener = mainCells[i].onDidChangeContent(() => { - this.setDirty(true); - this._increaseVersionId(); - this._onDidChangeContent.fire(); + this._eventEmitter.emit({ kind: NotebookCellsChangeType.ChangeCellContent, transient: false }, true); }); this._cellListeners.set(mainCells[i].handle, dirtyStateListener); } - this.cells.splice(0, 0, ...mainCells); - this._increaseVersionId(); + this._cells.splice(0, 0, ...mainCells); } - pushStackElement(label: string) { - this._operationManager.pushStackElement(label); + dispose() { + this._onWillDispose.fire(); + dispose(this._cellListeners.values()); + dispose(this._cells); + super.dispose(); } - applyEdit(modelVersionId: number, rawEdits: ICellEditOperation[], synchronous: boolean): boolean { + pushStackElement(label: string, selectionState: number[] | undefined) { + this._operationManager.pushStackElement(label, selectionState); + } + + applyEdits(modelVersionId: number, rawEdits: ICellEditOperation[], synchronous: boolean, beginSelectionState: number[] | undefined, endSelectionsComputer: () => number[] | undefined, computeUndoRedo: boolean = true): boolean { if (modelVersionId !== this._versionId) { return false; } - const oldViewCells = this.cells.slice(0); - const oldMap = new Map(this._mapping); + this._eventEmitter.beginDeferredEmit(); + this.pushStackElement('edit', beginSelectionState); const edits = rawEdits.map((edit, index) => { return { edit, - end: edit.editType === CellEditType.Replace ? edit.index + edit.count : edit.index, + end: + (edit.editType === CellEditType.DocumentMetadata || edit.editType === CellEditType.Unknown) + ? undefined + : (edit.editType === CellEditType.Replace ? edit.index + edit.count : edit.index), originalIndex: index, }; }).sort((a, b) => { + if (a.end === undefined) { + return -1; + } + + if (b.end === undefined) { + return -1; + } + return b.end - a.end || b.originalIndex - a.originalIndex; }); for (const { edit } of edits) { switch (edit.editType) { case CellEditType.Replace: - this._replaceCells(edit.index, edit.count, edit.cells); + this._replaceCells(edit.index, edit.count, edit.cells, synchronous, computeUndoRedo); break; case CellEditType.Output: //TODO@joh,@rebornix no event, no undo stop (?) - this.assertIndex(edit.index); - const cell = this.cells[edit.index]; - this.spliceNotebookCellOutputs(cell.handle, [[0, cell.outputs.length, edit.outputs]]); + this._assertIndex(edit.index); + const cell = this._cells[edit.index]; + // TODO@rebornix, we should do diff first + this._spliceNotebookCellOutputs(cell.handle, [[0, cell.outputs.length, edit.outputs]], computeUndoRedo); break; + case CellEditType.OutputsSplice: + { + //TODO@joh,@rebornix no event, no undo stop (?) + this._assertIndex(edit.index); + const cell = this._cells[edit.index]; + this._spliceNotebookCellOutputs(cell.handle, edit.splices, computeUndoRedo); + break; + } case CellEditType.Metadata: - this.assertIndex(edit.index); - this.deltaCellMetadata(this.cells[edit.index].handle, edit.metadata); + this._assertIndex(edit.index); + this._changeCellMetadata(this._cells[edit.index].handle, edit.metadata, computeUndoRedo); + break; + case CellEditType.CellLanguage: + this._assertIndex(edit.index); + this._changeCellLanguage(this._cells[edit.index].handle, edit.language, computeUndoRedo); + break; + case CellEditType.DocumentMetadata: + this._updateNotebookMetadata(edit.metadata, computeUndoRedo); + break; + case CellEditType.Move: + this._moveCellToIdx(edit.index, edit.length, edit.newIdx, synchronous, computeUndoRedo, undefined, undefined); + break; + case CellEditType.CellContent: + // TODO@rebornix, _replaceCellContent is async and does not push undo element + this._replaceCellContent(this._cells[edit.index], edit.range, edit.text); + break; + case CellEditType.Unknown: + this._handleUnknownChange(); break; } } - const diffs = diff(oldViewCells, this.cells, cell => { - return oldMap.has(cell.handle); - }).map(diff => { - return [diff.start, diff.deleteCount, diff.toInsert] as [number, number, NotebookCellTextModel[]]; - }); - - this._onDidModelChangeProxy.fire({ - kind: NotebookCellsChangeType.ModelChange, - versionId: this._versionId, - changes: diffs.map(diff => [diff[0], diff[1], diff[2].map(cell => ({ - handle: cell.handle, - uri: cell.uri, - source: cell.textBuffer.getLinesContent(), - eol: cell.textBuffer.getEOL(), - language: cell.language, - cellKind: cell.cellKind, - outputs: cell.outputs, - metadata: cell.metadata - }))] as [number, number, IMainCellDto[]]) - }); - - const undoDiff = diffs.map(diff => { - const deletedCells = this.cells.slice(diff[0], diff[0] + diff[1]); - - return [diff[0], deletedCells, diff[2]] as [number, NotebookCellTextModel[], NotebookCellTextModel[]]; - }); - - this._operationManager.pushEditOperation(new SpliceCellsEdit(this.uri, undoDiff, { - insertCell: this._insertCellDelegate.bind(this), - deleteCell: this._deleteCellDelegate.bind(this), - emitSelections: this._emitSelectionsDelegate.bind(this) - }, undefined, undefined)); - - this._onDidChangeCells.fire({ synchronous: synchronous, splices: diffs }); + const endSelections = endSelectionsComputer(); + this.pushStackElement('edit', endSelections); + this._eventEmitter.endDeferredEmit(endSelections); return true; } - private _replaceCells(index: number, count: number, cellDtos: ICellDto2[]): void { + createSnapshot(preserveBOM?: boolean): ITextSnapshot { + return new NotebookTextModelSnapshot(this); + } + + handleUnknownUndoableEdit(label: string | undefined, undo: () => void, redo: () => void): void { + this._operationManager.pushEditOperation({ + type: UndoRedoElementType.Resource, + resource: this.uri, + label: label ?? nls.localize('defaultEditLabel', "Edit"), + undo: async () => { + undo(); + }, + redo: async () => { + redo(); + }, + }, undefined, undefined); + + this._eventEmitter.emit({ + kind: NotebookCellsChangeType.Unknown, + transient: false + }, true); + } + + private _handleUnknownChange() { + this._eventEmitter.emit({ + kind: NotebookCellsChangeType.Unknown, + transient: false + }, true); + } + + private _replaceCells(index: number, count: number, cellDtos: ICellDto2[], synchronous: boolean, computeUndoRedo: boolean): void { if (count === 0 && cellDtos.length === 0) { return; } - this._isUntitled = false; //TODO@rebornix fishy? + const oldViewCells = this._cells.slice(0); + const oldMap = new Map(this._mapping); // prepare remove for (let i = index; i < index + count; i++) { - const cell = this.cells[i]; + const cell = this._cells[i]; this._cellListeners.get(cell.handle)?.dispose(); this._cellListeners.delete(cell.handle); } @@ -335,9 +426,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel this._modelService ); const dirtyStateListener = cell.onDidChangeContent(() => { - this.setDirty(true); - this._increaseVersionId(); - this._onDidChangeContent.fire(); + this._eventEmitter.emit({ kind: NotebookCellsChangeType.ChangeCellContent, transient: false }, true); }); this._cellListeners.set(cell.handle, dirtyStateListener); this._mapping.set(cell.handle, cell); @@ -345,224 +434,106 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel }); // make change - this.cells.splice(index, count, ...cells); - this.setDirty(true); - this._increaseVersionId(); - this._onDidChangeContent.fire(); - } - - handleEdit(label: string | undefined, undo: () => void, redo: () => void): void { - this._operationManager.pushEditOperation({ - type: UndoRedoElementType.Resource, - resource: this.uri, - label: label ?? nls.localize('defaultEditLabel', "Edit"), - undo: async () => { - undo(); - }, - redo: async () => { - redo(); - }, + this._cells.splice(index, count, ...cells); + const diffs = diff(oldViewCells, this._cells, cell => { + return oldMap.has(cell.handle); + }).map(diff => { + return [diff.start, diff.deleteCount, diff.toInsert] as [number, number, NotebookCellTextModel[]]; }); - this.setDirty(true); - } - createSnapshot(preserveBOM?: boolean): ITextSnapshot { - return new NotebookTextModelSnapshot(this); + const undoDiff = diffs.map(diff => { + const deletedCells = oldViewCells.slice(diff[0], diff[0] + diff[1]); + + return [diff[0], deletedCells, diff[2]] as [number, NotebookCellTextModel[], NotebookCellTextModel[]]; + }); + + if (computeUndoRedo) { + this._operationManager.pushEditOperation(new SpliceCellsEdit(this.uri, undoDiff, { + insertCell: (index, cell, endSelections?: number[]) => { this._insertNewCell(index, [cell], true, endSelections); }, + deleteCell: (index, endSelections?: number[]) => { this._removeCell(index, 1, true, endSelections); }, + }, undefined, undefined), undefined, undefined); + } + + // should be deferred + this._eventEmitter.emit({ + kind: NotebookCellsChangeType.ModelChange, + changes: diffs, + transient: false + }, synchronous); } private _increaseVersionId(): void { this._versionId = this._versionId + 1; } - handleUnknownChange() { - this.setDirty(true); - } - updateLanguages(languages: string[]) { const allLanguages = languages.find(lan => lan === '*'); this._allLanguages = allLanguages !== undefined; this._languages = languages; const resolvedLanguages = this.resolvedLanguages; - if (this._isUntitled && resolvedLanguages.length && this.cells.length) { - this.cells[0].language = resolvedLanguages[0]; + if (resolvedLanguages.length && this._cells.length) { + this._cells[0].language = resolvedLanguages[0]; } } - updateNotebookMetadata(metadata: NotebookDocumentMetadata) { + private _updateNotebookMetadata(metadata: NotebookDocumentMetadata, computeUndoRedo: boolean) { + const oldMetadata = this.metadata; this.metadata = metadata; - this._onDidChangeMetadata.fire(this.metadata); - } - insertTemplateCell(cell: NotebookCellTextModel) { - if (this.cells.length > 0 || this._isUntitled !== undefined) { - return; + if (computeUndoRedo) { + const that = this; + this._operationManager.pushEditOperation(new class implements IResourceUndoRedoElement { + readonly type: UndoRedoElementType.Resource = UndoRedoElementType.Resource; + get resource() { + return that.uri; + } + readonly label = 'Update Notebook Metadata'; + undo() { + that._updateNotebookMetadata(oldMetadata, false); + } + redo() { + that._updateNotebookMetadata(metadata, false); + } + }(), undefined, undefined); } - this._isUntitled = true; - this.cells = [cell]; - this._mapping.set(cell.handle, cell); - - const dirtyStateListener = cell.onDidChangeContent(() => { - this._isUntitled = false; - this.setDirty(true); - this._increaseVersionId(); - this._onDidChangeContent.fire(); - }); - - this._cellListeners.set(cell.handle, dirtyStateListener); - this.setDirty(false); - this._onDidChangeContent.fire(); - - this._onDidModelChangeProxy.fire({ - kind: NotebookCellsChangeType.ModelChange, - versionId: this._versionId, changes: - [[ - 0, - 0, - [{ - handle: cell.handle, - uri: cell.uri, - source: cell.textBuffer.getLinesContent(), - eol: cell.textBuffer.getEOL(), - language: cell.language, - cellKind: cell.cellKind, - outputs: cell.outputs, - metadata: cell.metadata - }] - ]] - }); - - return; + this._eventEmitter.emit({ kind: NotebookCellsChangeType.ChangeDocumentMetadata, metadata: this.metadata, transient: false }, true); } - insertNewCell(index: number, cells: NotebookCellTextModel[], emitToExtHost: boolean = true): void { - this._isUntitled = false; - + private _insertNewCell(index: number, cells: NotebookCellTextModel[], synchronous: boolean, endSelections?: number[]): void { for (let i = 0; i < cells.length; i++) { this._mapping.set(cells[i].handle, cells[i]); const dirtyStateListener = cells[i].onDidChangeContent(() => { - this.setDirty(true); - this._increaseVersionId(); - this._onDidChangeContent.fire(); + this._eventEmitter.emit({ kind: NotebookCellsChangeType.ChangeCellContent, transient: false }, true); }); this._cellListeners.set(cells[i].handle, dirtyStateListener); } - this.cells.splice(index, 0, ...cells); - this.setDirty(true); - this._onDidChangeContent.fire(); - - this._increaseVersionId(); - - if (emitToExtHost) { - this._onDidModelChangeProxy.fire({ - kind: NotebookCellsChangeType.ModelChange, - versionId: this._versionId, changes: - [[ - index, - 0, - cells.map(cell => ({ - handle: cell.handle, - uri: cell.uri, - source: cell.textBuffer.getLinesContent(), - eol: cell.textBuffer.getEOL(), - language: cell.language, - cellKind: cell.cellKind, - outputs: cell.outputs, - metadata: cell.metadata - })) - ]] - }); - } + this._cells.splice(index, 0, ...cells); + this._eventEmitter.emit({ + kind: NotebookCellsChangeType.ModelChange, + changes: + [[ + index, + 0, + cells + ]], + transient: false + }, synchronous, endSelections); return; } - removeCell(index: number, count: number, emitToExtHost: boolean = true) { - this._isUntitled = false; - + private _removeCell(index: number, count: number, synchronous: boolean, endSelections?: number[]) { for (let i = index; i < index + count; i++) { - const cell = this.cells[i]; + const cell = this._cells[i]; this._cellListeners.get(cell.handle)?.dispose(); this._cellListeners.delete(cell.handle); } - this.cells.splice(index, count); - this.setDirty(true); - this._onDidChangeContent.fire(); - - this._increaseVersionId(); - if (emitToExtHost) { - this._onDidModelChangeProxy.fire({ kind: NotebookCellsChangeType.ModelChange, versionId: this._versionId, changes: [[index, count, []]] }); - } - } - - moveCellToIdx(index: number, length: number, newIdx: number, emitToExtHost: boolean = true) { - this.assertIndex(index); - this.assertIndex(newIdx); - - const cells = this.cells.splice(index, length); - this.cells.splice(newIdx, 0, ...cells); - this.setDirty(true); - this._onDidChangeContent.fire(); - - this._increaseVersionId(); - - if (emitToExtHost) { - 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? - spliceNotebookCellOutputs(cellHandle: number, splices: NotebookCellOutputsSplice[]): void { - const cell = this._mapping.get(cellHandle); - if (cell) { - - cell.spliceNotebookCellOutputs(splices); - - if (!this.transientOptions.transientOutputs) { - this._increaseVersionId(); - this.setDirty(true); - this._onDidChangeContent.fire(); - } - - this._onDidModelChangeProxy.fire({ - kind: NotebookCellsChangeType.Output, - versionId: this.versionId, - index: this.cells.indexOf(cell), - outputs: cell.outputs ?? [] - }); - } - - } - - clearCellOutput(handle: number) { - const 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) { - const cell = this._mapping.get(handle); - if (cell && cell.language !== languageId) { - cell.language = languageId; - - this._increaseVersionId(); - this._onDidModelChangeProxy.fire({ kind: NotebookCellsChangeType.ChangeLanguage, versionId: this._versionId, index: this.cells.indexOf(cell), language: languageId }); - } + this._cells.splice(index, count); + this._eventEmitter.emit({ kind: NotebookCellsChangeType.ModelChange, changes: [[index, count, []]], transient: false }, synchronous, endSelections); } private _isCellMetadataChanged(a: NotebookCellMetadata, b: NotebookCellMetadata) { @@ -580,8 +551,8 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel return false; } - changeCellMetadata(handle: number, metadata: NotebookCellMetadata, pushUndoStop: boolean) { - const cell = this.cells.find(cell => cell.handle === handle); + private _changeCellMetadata(handle: number, metadata: NotebookCellMetadata, computeUndoRedo: boolean) { + const cell = this._cells.find(cell => cell.handle === handle); if (!cell) { return; @@ -590,172 +561,103 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel const triggerDirtyChange = this._isCellMetadataChanged(cell.metadata, metadata); if (triggerDirtyChange) { - if (pushUndoStop) { - const index = this.cells.indexOf(cell); + if (computeUndoRedo) { + const index = this._cells.indexOf(cell); this._operationManager.pushEditOperation(new CellMetadataEdit(this.uri, index, Object.freeze(cell.metadata), Object.freeze(metadata), { updateCellMetadata: (index, newMetadata) => { - const cell = this.cells[index]; + const cell = this._cells[index]; if (!cell) { return; } - this.changeCellMetadata(cell.handle, newMetadata, false); - }, - emitSelections: this._emitSelectionsDelegate.bind(this) - })); + this._changeCellMetadata(cell.handle, newMetadata, false); + } + }), undefined, undefined); } + // should be deferred cell.metadata = metadata; - this.setDirty(true); - this._onDidChangeContent.fire(); } else { cell.metadata = metadata; } - this._increaseVersionId(); - this._onDidModelChangeProxy.fire({ kind: NotebookCellsChangeType.ChangeMetadata, versionId: this._versionId, index: this.cells.indexOf(cell), metadata: cell.metadata }); + this._eventEmitter.emit({ kind: NotebookCellsChangeType.ChangeCellMetadata, index: this._cells.indexOf(cell), metadata: cell.metadata, transient: !triggerDirtyChange }, true); } - deltaCellMetadata(handle: number, newMetadata: NotebookCellMetadata) { + private _changeCellLanguage(handle: number, languageId: string, computeUndoRedo: boolean) { const cell = this._mapping.get(handle); + if (!cell || cell.language === languageId) { + return; + } + + const oldLanguage = cell.language; + cell.language = languageId; + + if (computeUndoRedo) { + const that = this; + this._operationManager.pushEditOperation(new class implements IResourceUndoRedoElement { + readonly type: UndoRedoElementType.Resource = UndoRedoElementType.Resource; + get resource() { + return that.uri; + } + readonly label = 'Update Cell Language'; + undo() { + that._changeCellLanguage(cell.handle, oldLanguage, false); + } + redo() { + that._changeCellLanguage(cell.handle, languageId, false); + } + }(), undefined, undefined); + } + + this._eventEmitter.emit({ kind: NotebookCellsChangeType.ChangeLanguage, index: this._cells.indexOf(cell), language: languageId, transient: false }, true); + } + + private _spliceNotebookCellOutputs(cellHandle: number, splices: NotebookCellOutputsSplice[], computeUndoRedo: boolean): void { + const cell = this._mapping.get(cellHandle); if (cell) { - this.changeCellMetadata(handle, { - ...cell.metadata, - ...newMetadata + cell.spliceNotebookCellOutputs(splices); + + this._eventEmitter.emit({ + kind: NotebookCellsChangeType.Output, + index: this._cells.indexOf(cell), + outputs: cell.outputs ?? [], + transient: this.transientOptions.transientOutputs, }, true); } } - clearAllCellOutputs() { - this.cells.forEach(cell => { - cell.spliceNotebookCellOutputs([ - [0, cell.outputs.length, []] - ]); - }); - this._increaseVersionId(); - this._onDidModelChangeProxy.fire({ kind: NotebookCellsChangeType.CellsClearOutput, versionId: this._versionId }); - } - - //#region Notebook Text Model Edit API - - private _insertCellDelegate(insertIndex: number, insertCell: NotebookCellTextModel) { - this.insertNewCell(insertIndex, [insertCell]); - this._onDidChangeCells.fire({ synchronous: true, splices: [[insertIndex, 0, [insertCell]]] }); - } - - private _deleteCellDelegate(deleteIndex: number) { - this.removeCell(deleteIndex, 1); - this._onDidChangeCells.fire({ synchronous: true, splices: [[deleteIndex, 1, []]] }); - } - - private _emitSelectionsDelegate(selections: number[]) { - this._emitSelections.fire(selections); - } - - createCell2(index: number, source: string, language: string, type: CellKind, metadata: NotebookCellMetadata | undefined, synchronous: boolean, pushUndoStop: boolean, beforeSelections: number[] | undefined, endSelections: number[] | undefined) { - const cell = this.createCellTextModel(source, language, type, [], metadata); - - if (pushUndoStop) { - this._operationManager.pushEditOperation(new InsertCellEdit(this.uri, index, cell, { - insertCell: this._insertCellDelegate.bind(this), - deleteCell: this._deleteCellDelegate.bind(this), - emitSelections: this._emitSelectionsDelegate.bind(this) - }, beforeSelections, endSelections)); - } - - - this.insertNewCell(index, [cell]); - - this._onDidChangeCells.fire({ synchronous, splices: [[index, 0, [cell]]] }); - - if (endSelections) { - this._emitSelections.fire(endSelections); - } - return cell; - } - - insertCell2(index: number, cell: NotebookCellTextModel, synchronous: boolean, pushUndoStop: boolean): void { - if (pushUndoStop) { - this._operationManager.pushEditOperation(new InsertCellEdit(this.uri, index, cell, { - insertCell: this._insertCellDelegate.bind(this), - deleteCell: this._deleteCellDelegate.bind(this), - emitSelections: this._emitSelectionsDelegate.bind(this) - }, undefined, undefined)); - } - - this.insertNewCell(index, [cell]); - this._onDidChangeCells.fire({ synchronous: synchronous, splices: [[index, 0, [cell]]] }); - } - - deleteCell2(index: number, synchronous: boolean, pushUndoStop: boolean, beforeSelections: number[] | undefined, endSelections: number[] | undefined) { - const cell = this.cells[index]; - if (pushUndoStop) { - this._operationManager.pushEditOperation(new DeleteCellEdit(this.uri, index, cell, { - insertCell: this._insertCellDelegate.bind(this), - deleteCell: this._deleteCellDelegate.bind(this), - emitSelections: this._emitSelectionsDelegate.bind(this) - }, beforeSelections, endSelections)); - } - - this.removeCell(index, 1); - this._onDidChangeCells.fire({ synchronous: synchronous, splices: [[index, 1, []]] }); - if (endSelections) { - this._emitSelections.fire(endSelections); - } - } - - moveCellToIdx2(index: number, length: number, newIdx: number, synchronous: boolean, pushedToUndoStack: boolean, beforeSelections: number[] | undefined, endSelections: number[] | undefined): boolean { - const cells = this.cells.slice(index, index + length); + private _moveCellToIdx(index: number, length: number, newIdx: number, synchronous: boolean, pushedToUndoStack: boolean, beforeSelections: number[] | undefined, endSelections: number[] | undefined): boolean { if (pushedToUndoStack) { this._operationManager.pushEditOperation(new MoveCellEdit(this.uri, index, length, newIdx, { moveCell: (fromIndex: number, length: number, toIndex: number, beforeSelections: number[] | undefined, endSelections: number[] | undefined) => { - this.moveCellToIdx2(fromIndex, length, toIndex, true, false, beforeSelections, endSelections); + this._moveCellToIdx(fromIndex, length, toIndex, true, false, beforeSelections, endSelections); }, - emitSelections: this._emitSelectionsDelegate.bind(this) - }, beforeSelections, endSelections)); + }, beforeSelections, endSelections), beforeSelections, endSelections); } - this.moveCellToIdx(index, length, newIdx); - // todo, we can't emit this change as it will create a new view model and that will hold - // a new reference to the document, thus - this._onDidChangeCells.fire({ synchronous: synchronous, splices: [[index, length, []]] }); - this._onDidChangeCells.fire({ synchronous: synchronous, splices: [[newIdx, 0, cells]] }); - if (endSelections) { - this._emitSelections.fire(endSelections); - } + this._assertIndex(index); + this._assertIndex(newIdx); + + const cells = this._cells.splice(index, length); + this._cells.splice(newIdx, 0, ...cells); + this._eventEmitter.emit({ kind: NotebookCellsChangeType.Move, index, length, newIdx, cells, transient: false }, synchronous, endSelections); return true; } - async splitNotebookCell(index: number, newLinesContents: string[], endSelections: number[]) { - const cell = this.cells[index]; - + async _replaceCellContent(cell: NotebookCellTextModel, range: IRange | undefined, text: string) { const ref = await cell.resolveTextModelRef(); const textModel = ref.object.textEditorModel; textModel.applyEdits([ - { range: textModel.getFullModelRange(), text: newLinesContents[0] } + { range: range || textModel.getFullModelRange(), text: text } ], false); ref.dispose(); - - // create new cells based on the new text models - const language = cell.language; - const kind = cell.cellKind; - let insertIndex = index + 1; - const newCells = []; - for (let j = 1; j < newLinesContents.length; j++, insertIndex++) { - newCells.push(this.createCell2(insertIndex, newLinesContents[j], language, kind, undefined, true, false, undefined, undefined)); - } - - if (endSelections) { - this._emitSelections.fire(endSelections); - } } - //#endregion - dispose() { - this._onWillDispose.fire(); - this._cellListeners.forEach(val => val.dispose()); - this.cells.forEach(cell => cell.dispose()); - super.dispose(); + private _assertIndex(index: number) { + if (index < 0 || index >= this._cells.length) { + throw new Error(`model index out of range ${index}`); + } } } diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index a1767ec49e..a74293a3c2 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -23,6 +23,7 @@ import { IRevertOptions } from 'vs/workbench/common/editor'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IFileStatWithMetadata } from 'vs/platform/files/common/files'; +import { IRange } from 'vs/editor/common/core/range'; export enum CellKind { Markdown = 1, @@ -270,15 +271,12 @@ export interface IMetadata { } export interface INotebookTextModel { - handle: number; - viewType: string; + readonly viewType: string; metadata: NotebookDocumentMetadata readonly uri: URI; readonly versionId: number; languages: string[]; - cells: ICell[]; - onDidChangeCells?: Event<{ synchronous: boolean, splices: NotebookCellTextModelSplice[] }>; - onDidChangeContent: Event; + readonly cells: readonly ICell[]; onWillDispose(listener: () => void): IDisposable; } @@ -312,10 +310,10 @@ export type IRenderOutput = IRenderNoOutput | IInsetRenderOutput; export const outputHasDynamicHeight = (o: IRenderOutput) => o.type !== RenderOutputType.Extension && o.hasDynamicHeight; -export type NotebookCellTextModelSplice = [ +export type NotebookCellTextModelSplice = [ number /* start */, number, - ICell[] + T[] ]; export type NotebookCellOutputsSplice = [ @@ -348,67 +346,87 @@ export enum NotebookCellsChangeType { CellsClearOutput = 4, ChangeLanguage = 5, Initialize = 6, - ChangeMetadata = 7, + ChangeCellMetadata = 7, Output = 8, + ChangeCellContent = 9, + ChangeDocumentMetadata = 10, + Unknown = 11 } -export interface NotebookCellsInitializeEvent { +export interface NotebookCellsInitializeEvent { readonly kind: NotebookCellsChangeType.Initialize; - readonly changes: NotebookCellsSplice2[]; - readonly versionId: number; + readonly changes: NotebookCellTextModelSplice[]; } -export interface NotebookCellsModelChangedEvent { +export interface NotebookCellContentChangeEvent { + readonly kind: NotebookCellsChangeType.ChangeCellContent; +} + +export interface NotebookCellsModelChangedEvent { readonly kind: NotebookCellsChangeType.ModelChange; - readonly changes: NotebookCellsSplice2[]; - readonly versionId: number; + readonly changes: NotebookCellTextModelSplice[]; } -export interface NotebookCellsModelMoveEvent { +export interface NotebookCellsModelMoveEvent { readonly kind: NotebookCellsChangeType.Move; readonly index: number; + readonly length: number; readonly newIdx: number; - readonly versionId: number; + readonly cells: T[]; } export interface NotebookOutputChangedEvent { readonly kind: NotebookCellsChangeType.Output; readonly index: number; - readonly versionId: number; readonly outputs: IProcessedOutput[]; } -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 interface NotebookCellsChangeMetadataEvent { - readonly kind: NotebookCellsChangeType.ChangeMetadata; - readonly versionId: number; + readonly kind: NotebookCellsChangeType.ChangeCellMetadata; readonly index: number; readonly metadata: NotebookCellMetadata | undefined; } -export type NotebookCellsChangedEvent = NotebookCellsInitializeEvent | NotebookCellsModelChangedEvent | NotebookCellsModelMoveEvent | NotebookOutputChangedEvent | NotebookCellClearOutputEvent | NotebookCellsClearOutputEvent | NotebookCellsChangeLanguageEvent | NotebookCellsChangeMetadataEvent; +export interface NotebookDocumentChangeMetadataEvent { + readonly kind: NotebookCellsChangeType.ChangeDocumentMetadata; + readonly metadata: NotebookDocumentMetadata | undefined; +} + +export interface NotebookDocumentUnknownChangeEvent { + readonly kind: NotebookCellsChangeType.Unknown; +} + +export type NotebookRawContentEventDto = NotebookCellsInitializeEvent | NotebookDocumentChangeMetadataEvent | NotebookCellContentChangeEvent | NotebookCellsModelChangedEvent | NotebookCellsModelMoveEvent | NotebookOutputChangedEvent | NotebookCellsChangeLanguageEvent | NotebookCellsChangeMetadataEvent | NotebookDocumentUnknownChangeEvent; + +export type NotebookCellsChangedEventDto = { + readonly rawEvents: NotebookRawContentEventDto[]; + readonly versionId: number; +}; + +export type NotebookRawContentEvent = (NotebookCellsInitializeEvent | NotebookDocumentChangeMetadataEvent | NotebookCellContentChangeEvent | NotebookCellsModelChangedEvent | NotebookCellsModelMoveEvent | NotebookOutputChangedEvent | NotebookCellsChangeLanguageEvent | NotebookCellsChangeMetadataEvent | NotebookDocumentUnknownChangeEvent) & { transient: boolean; }; +export type NotebookTextModelChangedEvent = { + readonly rawEvents: NotebookRawContentEvent[]; + readonly versionId: number; + readonly synchronous: boolean; + readonly endSelections?: number[]; +}; export const enum CellEditType { Replace = 1, Output = 2, Metadata = 3, + CellLanguage = 4, + DocumentMetadata = 5, + OutputsSplice = 6, + Move = 7, + Unknown = 8, + CellContent = 9 } export interface ICellDto2 { @@ -438,11 +456,47 @@ export interface ICellMetadataEdit { metadata: NotebookCellMetadata; } -export type ICellEditOperation = ICellReplaceEdit | ICellOutputEdit | ICellMetadataEdit; + +export interface ICellLanguageEdit { + editType: CellEditType.CellLanguage; + index: number; + language: string; +} + +export interface IDocumentMetadataEdit { + editType: CellEditType.DocumentMetadata; + metadata: NotebookDocumentMetadata; +} + +export interface ICellOutputsSpliceEdit { + editType: CellEditType.OutputsSplice; + index: number; + splices: NotebookCellOutputsSplice[]; +} + +export interface ICellMoveEdit { + editType: CellEditType.Move; + index: number; + length: number; + newIdx: number; +} + +export interface ICellContentEdit { + editType: CellEditType.CellContent; + index: number; + range: IRange | undefined; + text: string; +} + +export interface IDocumentUnknownEdit { + editType: CellEditType.Unknown; +} + +export type ICellEditOperation = ICellReplaceEdit | ICellOutputEdit | ICellMetadataEdit | ICellLanguageEdit | IDocumentMetadataEdit | ICellOutputsSpliceEdit | ICellMoveEdit | ICellContentEdit | IDocumentUnknownEdit; export interface INotebookEditData { documentVersionId: number; - edits: ICellEditOperation[]; + cellEdits: ICellEditOperation[]; } export interface NotebookDataDto { @@ -688,6 +742,8 @@ export interface IEditor extends editorCommon.ICompositeCodeEditor { readonly onDidChangeModel: Event; readonly onDidFocusEditorWidget: Event; readonly onDidChangeVisibleRanges: Event; + readonly onDidChangeSelection: Event; + getSelectionHandles(): number[]; isNotebookEditor: boolean; visibleRanges: ICellRange[]; uri?: URI; diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts index cc7e03c176..49dceceb3f 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { EditorModel, IRevertOptions } from 'vs/workbench/common/editor'; import { Emitter, Event } from 'vs/base/common/event'; -import { INotebookEditorModel, NotebookDocumentBackupData } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookEditorModel, NotebookCellsChangeType, NotebookDocumentBackupData } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { URI } from 'vs/base/common/uri'; @@ -24,30 +24,25 @@ export interface INotebookLoadOptions { * Go to disk bypassing any cache of the model if any. */ forceReadFromDisk?: boolean; - - editorId?: string; } export class NotebookEditorModel extends EditorModel implements INotebookEditorModel { - protected readonly _onDidChangeDirty = this._register(new Emitter()); - readonly onDidChangeDirty = this._onDidChangeDirty.event; + + private readonly _onDidChangeDirty = this._register(new Emitter()); private readonly _onDidChangeContent = this._register(new Emitter()); - readonly onDidChangeContent: Event = this._onDidChangeContent.event; + + readonly onDidChangeDirty = this._onDidChangeDirty.event; + readonly onDidChangeContent = this._onDidChangeContent.event; + private _notebook!: NotebookTextModel; - private _lastResolvedFileStat: IFileStatWithMetadata | undefined; - - get lastResolvedFileStat() { - return this._lastResolvedFileStat; - } - - get notebook() { - return this._notebook; - } + private _lastResolvedFileStat?: IFileStatWithMetadata; private readonly _name: string; private readonly _workingCopyResource: URI; + private _dirty = false; + constructor( readonly resource: URI, readonly viewType: string, @@ -62,23 +57,38 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM this._name = labelService.getUriBasenameLabel(resource); - const input = this; + const that = this; this._workingCopyResource = resource.with({ scheme: Schemas.vscodeNotebook }); const workingCopyAdapter = new class implements IWorkingCopy { - readonly resource = input._workingCopyResource; - get name() { return input._name; } - readonly capabilities = input.isUntitled() ? WorkingCopyCapabilities.Untitled : WorkingCopyCapabilities.None; - readonly onDidChangeDirty = input.onDidChangeDirty; - readonly onDidChangeContent = input.onDidChangeContent; - isDirty(): boolean { return input.isDirty(); } - backup(): Promise { return input.backup(); } - save(): Promise { return input.save(); } - revert(options?: IRevertOptions): Promise { return input.revert(options); } + readonly resource = that._workingCopyResource; + get name() { return that._name; } + readonly capabilities = that.isUntitled() ? WorkingCopyCapabilities.Untitled : WorkingCopyCapabilities.None; + readonly onDidChangeDirty = that.onDidChangeDirty; + readonly onDidChangeContent = that.onDidChangeContent; + isDirty(): boolean { return that.isDirty(); } + backup(): Promise { return that.backup(); } + save(): Promise { return that.save(); } + revert(options?: IRevertOptions): Promise { return that.revert(options); } }; this._register(this._workingCopyService.registerWorkingCopy(workingCopyAdapter)); } + get lastResolvedFileStat() { + return this._lastResolvedFileStat; + } + + get notebook() { + return this._notebook; + } + + setDirty(newState: boolean) { + if (this._dirty !== newState) { + this._dirty = newState; + this._onDidChangeDirty.fire(); + } + } + async backup(): Promise> { if (this._notebook.supportBackup) { const tokenSource = new CancellationTokenSource(); @@ -115,13 +125,13 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM const newStats = await this._resolveStats(this.resource); this._lastResolvedFileStat = newStats; - this._notebook.setDirty(false); + this.setDirty(false); this._onDidChangeDirty.fire(); } async load(options?: INotebookLoadOptions): Promise { if (options?.forceReadFromDisk) { - return this._loadFromProvider(true, undefined, undefined); + return this._loadFromProvider(true, undefined); } if (this.isResolved()) { @@ -134,31 +144,38 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM return this; // Make sure meanwhile someone else did not succeed in loading } - return this._loadFromProvider(false, options?.editorId, backup?.meta?.backupId); + return this._loadFromProvider(false, backup?.meta?.backupId); } - private async _loadFromProvider(forceReloadFromDisk: boolean, editorId: string | undefined, backupId: string | undefined) { - this._notebook = await this._notebookService.resolveNotebook(this.viewType!, this.resource, forceReloadFromDisk, editorId, backupId); + private async _loadFromProvider(forceReloadFromDisk: boolean, backupId: string | undefined) { + this._notebook = await this._notebookService.resolveNotebook(this.viewType!, this.resource, forceReloadFromDisk, backupId); const newStats = await this._resolveStats(this.resource); this._lastResolvedFileStat = newStats; this._register(this._notebook); - this._register(this._notebook.onDidChangeContent(() => { - this._onDidChangeContent.fire(); - })); - this._register(this._notebook.onDidChangeDirty(() => { - this._onDidChangeDirty.fire(); + this._register(this._notebook.onDidChangeContent(e => { + let triggerDirty = false; + for (let i = 0; i < e.rawEvents.length; i++) { + if (e.rawEvents[i].kind !== NotebookCellsChangeType.Initialize) { + this._onDidChangeContent.fire(); + triggerDirty = triggerDirty || !e.rawEvents[i].transient; + } + } + + if (triggerDirty) { + this.setDirty(true); + } })); if (forceReloadFromDisk) { - this._notebook.setDirty(false); + this.setDirty(false); } if (backupId) { await this._backupFileService.discardBackup(this._workingCopyResource); - this._notebook.setDirty(true); + this.setDirty(true); } return this; @@ -169,7 +186,7 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM } isDirty() { - return this._notebook?.isDirty; + return this._dirty; } isUntitled() { @@ -221,7 +238,7 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM await this._notebookService.save(this.notebook.viewType, this.notebook.uri, tokenSource.token); const newStats = await this._resolveStats(this.resource); this._lastResolvedFileStat = newStats; - this._notebook.setDirty(false); + this.setDirty(false); return true; } @@ -241,7 +258,7 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM await this._notebookService.saveAs(this.notebook.viewType, this.notebook.uri, targetResource, tokenSource.token); const newStats = await this._resolveStats(this.resource); this._lastResolvedFileStat = newStats; - this._notebook.setDirty(false); + this.setDirty(false); return true; } diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts index a4c9d9a395..2cc06c2d35 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverService.ts @@ -16,7 +16,7 @@ export const INotebookEditorModelResolverService = createDecorator>; + resolve(resource: URI, viewType?: string): Promise>; } @@ -30,19 +30,10 @@ export class NotebookModelReferenceCollection extends ReferenceCollection { - const resource = URI.parse(key); - - let [viewType, editorId] = args as [string | undefined, string | undefined]; - if (!viewType) { - viewType = this._notebookService.getContributedNotebookProviders(resource)[0]?.id; - } - if (!viewType) { - throw new Error('Missing viewType'); - } - - const model = this._instantiationService.createInstance(NotebookEditorModel, resource, viewType); - const promise = model.load({ editorId }); + protected createReferencedObject(key: string, viewType: string): Promise { + const uri = URI.parse(key); + const model = this._instantiationService.createInstance(NotebookEditorModel, uri, viewType); + const promise = model.load(); return promise; } @@ -63,15 +54,29 @@ export class NotebookModelResolverService implements INotebookEditorModelResolve private readonly _data: NotebookModelReferenceCollection; constructor( - @IInstantiationService instantiationService: IInstantiationService + @IInstantiationService instantiationService: IInstantiationService, + @INotebookService private readonly _notebookService: INotebookService ) { this._data = instantiationService.createInstance(NotebookModelReferenceCollection); } - async resolve(resource: URI, viewType?: string, editorId?: string | undefined): Promise> { - const reference = this._data.acquire(resource.toString(), viewType, editorId); + async resolve(resource: URI, viewType?: string): Promise> { + + if (!viewType) { + viewType = this._notebookService.getContributedNotebookProviders(resource)[0]?.id; + } + if (!viewType) { + throw new Error(`Missing viewType for '${resource}'`); + } + + const existing = this._notebookService.getNotebookTextModel(resource); + if (existing && existing.viewType !== viewType) { + throw new Error(`A notebook with view type '${existing.viewType}' already exists for '${resource}', CANNOT create another notebook with view type ${viewType}`); + } + + const reference = this._data.acquire(resource.toString(), viewType); const model = await reference.object; - NotebookModelResolverService._autoReferenceDirtyModel(model, () => this._data.acquire(resource.toString(), viewType, editorId)); + NotebookModelResolverService._autoReferenceDirtyModel(model, () => this._data.acquire(resource.toString(), viewType)); return { object: model, dispose() { reference.dispose(); } @@ -81,8 +86,8 @@ export class NotebookModelResolverService implements INotebookEditorModelResolve private static _autoReferenceDirtyModel(model: INotebookEditorModel, ref: () => IDisposable) { const references = new DisposableStore(); - const listener = model.notebook.onDidChangeDirty(() => { - if (model.notebook.isDirty) { + const listener = model.onDidChangeDirty(() => { + if (model.isDirty()) { references.add(ref()); } else { references.clear(); diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts index b867a86b06..2a7d727f1a 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -10,7 +10,7 @@ import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.pr import { Event } from 'vs/base/common/event'; import { INotebookTextModel, INotebookRendererInfo, - IEditor, ICellEditOperation, NotebookCellOutputsSplice, INotebookKernelProvider, INotebookKernelInfo2, TransientMetadata + IEditor, ICellEditOperation, NotebookCellOutputsSplice, INotebookKernelProvider, INotebookKernelInfo2, TransientMetadata, NotebookDataDto, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -24,11 +24,10 @@ export const INotebookService = createDecorator('notebookServi export interface IMainNotebookController { supportBackup: boolean; options: { transientOutputs: boolean; transientMetadata: TransientMetadata; }; - createNotebook(textModel: NotebookTextModel, editorId?: string, backupId?: string): Promise; + resolveNotebookDocument(viewType: string, uri: URI, backupId?: string): Promise<{ data: NotebookDataDto, transientOptions: TransientOptions }>; reloadNotebook(mainthreadTextModel: NotebookTextModel): Promise; resolveNotebookEditor(viewType: string, uri: URI, editorId: string): Promise; onDidReceiveMessage(editorId: string, rendererType: string | undefined, message: any): void; - removeNotebookDocument(uri: URI): Promise; save(uri: URI, token: CancellationToken): Promise; saveAs(uri: URI, target: URI, token: CancellationToken): Promise; backup(uri: URI, token: CancellationToken): Promise; @@ -41,13 +40,13 @@ export interface INotebookService { onDidChangeVisibleEditors: Event; onNotebookEditorAdd: Event; onNotebookEditorsRemove: Event; - onNotebookDocumentRemove: Event; - onNotebookDocumentAdd: Event; + onDidRemoveNotebookDocument: Event; + onDidAddNotebookDocument: Event; onNotebookDocumentSaved: Event; onDidChangeKernels: Event; onDidChangeNotebookActiveKernel: Event<{ uri: URI, providerHandle: number | undefined, kernelId: string | undefined }>; - registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController): void; - unregisterNotebookProvider(viewType: string): void; + registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController): IDisposable; + transformEditsOutputs(textModel: NotebookTextModel, edits: ICellEditOperation[]): void; transformSpliceOutputs(textModel: NotebookTextModel, splices: NotebookCellOutputsSplice[]): void; registerNotebookKernelProvider(provider: INotebookKernelProvider): IDisposable; @@ -55,7 +54,7 @@ export interface INotebookService { getContributedNotebookOutputRenderers(id: string): NotebookOutputRendererInfo | undefined; getRendererInfo(id: string): INotebookRendererInfo | undefined; - resolveNotebook(viewType: string, uri: URI, forceReload: boolean, editorId?: string, backupId?: string): Promise; + resolveNotebook(viewType: string, uri: URI, forceReload: boolean, backupId?: string): Promise; getNotebookTextModel(uri: URI): NotebookTextModel | undefined; getNotebookTextModels(): Iterable; getContributedNotebookProviders(resource: URI): readonly NotebookProviderInfo[]; @@ -72,6 +71,7 @@ export interface INotebookService { getToCopy(): { items: NotebookCellTextModel[], isCopy: boolean; } | undefined; // editor events + resolveNotebookEditor(viewType: string, uri: URI, editorId: string): Promise; addNotebookEditor(editor: IEditor): void; removeNotebookEditor(editor: IEditor): void; getNotebookEditor(editorId: string): IEditor | undefined; diff --git a/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.ts b/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.ts index a2292346d4..44e63425b7 100644 --- a/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.ts +++ b/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { IRequestHandler } from 'vs/base/common/worker/simpleWorker'; import * as model from 'vs/editor/common/model'; import { PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder'; -import { CellKind, ICellDto2, IMainCellDto, INotebookDiffResult, IProcessedOutput, NotebookCellMetadata, NotebookDataDto, NotebookDocumentMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, ICellDto2, IMainCellDto, INotebookDiffResult, IProcessedOutput, NotebookCellMetadata, NotebookCellsChangedEventDto, NotebookCellsChangeType, NotebookCellsSplice2, NotebookDataDto, NotebookDocumentMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Range } from 'vs/editor/common/core/range'; import { EditorWorkerHost } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerServiceImpl'; @@ -43,10 +43,10 @@ class MirrorCell { constructor( readonly handle: number, private _source: string | string[], - readonly language: string, - readonly cellKind: CellKind, - readonly outputs: IProcessedOutput[], - readonly metadata?: NotebookCellMetadata + public language: string, + public cellKind: CellKind, + public outputs: IProcessedOutput[], + public metadata?: NotebookCellMetadata ) { } @@ -87,11 +87,52 @@ class MirrorCell { class MirrorNotebookDocument { constructor( readonly uri: URI, - readonly cells: MirrorCell[], - readonly languages: string[], - readonly metadata: NotebookDocumentMetadata, + public cells: MirrorCell[], + public languages: string[], + public metadata: NotebookDocumentMetadata, ) { } + + acceptModelChanged(event: NotebookCellsChangedEventDto) { + // note that the cell content change is not applied to the MirrorCell + // but it's fine as if a cell content is modified after the first diff, its position will not change any more + // TODO@rebornix, but it might lead to interesting bugs in the future. + event.rawEvents.forEach(e => { + if (e.kind === NotebookCellsChangeType.ModelChange) { + this._spliceNotebookCells(e.changes); + } else if (e.kind === NotebookCellsChangeType.Move) { + const cells = this.cells.splice(e.index, 1); + this.cells.splice(e.newIdx, 0, ...cells); + } else if (e.kind === NotebookCellsChangeType.Output) { + const cell = this.cells[e.index]; + cell.outputs = e.outputs; + } else if (e.kind === NotebookCellsChangeType.ChangeLanguage) { + const cell = this.cells[e.index]; + cell.language = e.language; + } else if (e.kind === NotebookCellsChangeType.ChangeCellMetadata) { + const cell = this.cells[e.index]; + cell.metadata = e.metadata; + } + }); + } + + _spliceNotebookCells(splices: NotebookCellsSplice2[]) { + splices.reverse().forEach(splice => { + const cellDtos = splice[2]; + const newCells = cellDtos.map(cell => { + return new MirrorCell( + (cell as unknown as IMainCellDto).handle, + cell.source, + cell.language, + cell.cellKind, + cell.outputs, + cell.metadata + ); + }); + + this.cells.splice(splice[0], splice[1], ...newCells); + }); + } } export class CellSequence implements ISequence { @@ -137,6 +178,13 @@ export class NotebookEditorSimpleWorker implements IRequestHandler, IDisposable )), data.languages, data.metadata); } + public acceptModelChanged(strURL: string, event: NotebookCellsChangedEventDto) { + const model = this._models[strURL]; + if (model) { + model.acceptModelChanged(event); + } + } + public acceptRemovedModel(strURL: string): void { if (!this._models[strURL]) { return; diff --git a/src/vs/workbench/contrib/notebook/common/services/notebookWorkerServiceImpl.ts b/src/vs/workbench/contrib/notebook/common/services/notebookWorkerServiceImpl.ts index 96b8fa837e..f68549c937 100644 --- a/src/vs/workbench/contrib/notebook/common/services/notebookWorkerServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/common/services/notebookWorkerServiceImpl.ts @@ -7,7 +7,8 @@ import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from import { URI } from 'vs/base/common/uri'; import { SimpleWorkerClient } from 'vs/base/common/worker/simpleWorker'; import { DefaultWorkerFactory } from 'vs/base/worker/defaultWorkerFactory'; -import { INotebookDiffResult } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; +import { IMainCellDto, INotebookDiffResult, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { NotebookEditorSimpleWorker } from 'vs/workbench/contrib/notebook/common/services/notebookSimpleWorker'; import { INotebookEditorWorkerService } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerService'; @@ -113,11 +114,50 @@ export class NotebookEditorModelManager extends Disposable { const toDispose = new DisposableStore(); - // TODO, accept Model change + const cellToDto = (cell: NotebookCellTextModel): IMainCellDto => { + return { + handle: cell.handle, + uri: cell.uri, + source: cell.textBuffer.getLinesContent(), + eol: cell.textBuffer.getEOL(), + language: cell.language, + cellKind: cell.cellKind, + outputs: cell.outputs, + metadata: cell.metadata + }; + }; + + toDispose.add(model.onDidChangeContent((event) => { + const dto = event.rawEvents.map(e => { + const data = + e.kind === NotebookCellsChangeType.ModelChange || e.kind === NotebookCellsChangeType.Initialize + ? { + kind: e.kind, + versionId: event.versionId, + changes: e.changes.map(diff => [diff[0], diff[1], diff[2].map(cell => cellToDto(cell as NotebookCellTextModel))] as [number, number, IMainCellDto[]]) + } + : ( + e.kind === NotebookCellsChangeType.Move + ? { + kind: e.kind, + index: e.index, + length: e.length, + newIdx: e.newIdx, + versionId: event.versionId, + cells: e.cells.map(cell => cellToDto(cell as NotebookCellTextModel)) + } + : e + ); + + return data; + }); + + this._proxy.acceptModelChanged(modelUrl.toString(), { + rawEvents: dto, + versionId: event.versionId + }); + })); - // toDispose.add(model.onDidChangeContent((e) => { - // this._proxy.acceptModelChanged(modelUrl.toString(), e); - // })); toDispose.add(model.onWillDispose(() => { this._stopModelSync(modelUrl); })); diff --git a/src/vs/workbench/contrib/notebook/test/notebookTextModel.test.ts b/src/vs/workbench/contrib/notebook/test/notebookTextModel.test.ts index 0319886a2d..cfbd144b97 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookTextModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookTextModel.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { CellKind, CellEditType, CellOutputKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, CellEditType, CellOutputKind, NotebookTextModelChangedEvent } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { withTestNotebook, TestCell, setupInstantiationService } from 'vs/workbench/contrib/notebook/test/testNotebookEditor'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; @@ -29,10 +29,10 @@ suite('NotebookTextModel', () => { ['var d = 4;', 'javascript', CellKind.Code, [], { editable: false }] ], (editor, viewModel, textModel) => { - textModel.applyEdit(textModel.versionId, [ + textModel.applyEdits(textModel.versionId, [ { editType: CellEditType.Replace, index: 1, count: 0, cells: [new TestCell(viewModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], textModelService)] }, { editType: CellEditType.Replace, index: 3, count: 0, cells: [new TestCell(viewModel.viewType, 6, 'var f = 6;', 'javascript', CellKind.Code, [], textModelService)] }, - ], true); + ], true, undefined, () => undefined); assert.equal(textModel.cells.length, 6); @@ -54,10 +54,10 @@ suite('NotebookTextModel', () => { ['var d = 4;', 'javascript', CellKind.Code, [], { editable: false }] ], (editor, viewModel, textModel) => { - textModel.applyEdit(textModel.versionId, [ + textModel.applyEdits(textModel.versionId, [ { editType: CellEditType.Replace, index: 1, count: 0, cells: [new TestCell(viewModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], textModelService)] }, { editType: CellEditType.Replace, index: 1, count: 0, cells: [new TestCell(viewModel.viewType, 6, 'var f = 6;', 'javascript', CellKind.Code, [], textModelService)] }, - ], true); + ], true, undefined, () => undefined); assert.equal(textModel.cells.length, 6); @@ -79,10 +79,10 @@ suite('NotebookTextModel', () => { ['var d = 4;', 'javascript', CellKind.Code, [], { editable: false }] ], (editor, viewModel, textModel) => { - textModel.applyEdit(textModel.versionId, [ + textModel.applyEdits(textModel.versionId, [ { editType: CellEditType.Replace, index: 1, count: 1, cells: [] }, { editType: CellEditType.Replace, index: 3, count: 1, cells: [] }, - ], true); + ], true, undefined, () => undefined); assert.equal(textModel.cells[0].getValue(), 'var a = 1;'); assert.equal(textModel.cells[1].getValue(), 'var c = 3;'); @@ -102,10 +102,10 @@ suite('NotebookTextModel', () => { ['var d = 4;', 'javascript', CellKind.Code, [], { editable: false }] ], (editor, viewModel, textModel) => { - textModel.applyEdit(textModel.versionId, [ + textModel.applyEdits(textModel.versionId, [ { editType: CellEditType.Replace, index: 1, count: 1, cells: [] }, { editType: CellEditType.Replace, index: 3, count: 0, cells: [new TestCell(viewModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], textModelService)] }, - ], true); + ], true, undefined, () => undefined); assert.equal(textModel.cells.length, 4); @@ -127,10 +127,10 @@ suite('NotebookTextModel', () => { ['var d = 4;', 'javascript', CellKind.Code, [], { editable: false }] ], (editor, viewModel, textModel) => { - textModel.applyEdit(textModel.versionId, [ + textModel.applyEdits(textModel.versionId, [ { editType: CellEditType.Replace, index: 1, count: 1, cells: [] }, { editType: CellEditType.Replace, index: 1, count: 0, cells: [new TestCell(viewModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], textModelService)] }, - ], true); + ], true, undefined, () => undefined); assert.equal(textModel.cells.length, 4); assert.equal(textModel.cells[0].getValue(), 'var a = 1;'); @@ -152,9 +152,9 @@ suite('NotebookTextModel', () => { ['var d = 4;', 'javascript', CellKind.Code, [], { editable: false }] ], (editor, viewModel, textModel) => { - textModel.applyEdit(textModel.versionId, [ + textModel.applyEdits(textModel.versionId, [ { editType: CellEditType.Replace, index: 1, count: 1, cells: [new TestCell(viewModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], textModelService)] }, - ], true); + ], true, undefined, () => undefined); assert.equal(textModel.cells.length, 4); assert.equal(textModel.cells[0].getValue(), 'var a = 1;'); @@ -176,23 +176,23 @@ suite('NotebookTextModel', () => { // invalid index 1 assert.throws(() => { - textModel.applyEdit(textModel.versionId, [{ + textModel.applyEdits(textModel.versionId, [{ index: Number.MAX_VALUE, editType: CellEditType.Output, outputs: [] - }], true); + }], true, undefined, () => undefined); }); // invalid index 2 assert.throws(() => { - textModel.applyEdit(textModel.versionId, [{ + textModel.applyEdits(textModel.versionId, [{ index: -1, editType: CellEditType.Output, outputs: [] - }], true); + }], true, undefined, () => undefined); }); - textModel.applyEdit(textModel.versionId, [{ + textModel.applyEdits(textModel.versionId, [{ index: 0, editType: CellEditType.Output, outputs: [{ @@ -200,7 +200,7 @@ suite('NotebookTextModel', () => { outputId: 'someId', data: { 'text/markdown': '_Hello_' } }] - }], true); + }], true, undefined, () => undefined); assert.equal(textModel.cells.length, 1); assert.equal(textModel.cells[0].outputs.length, 1); @@ -221,31 +221,104 @@ suite('NotebookTextModel', () => { // invalid index 1 assert.throws(() => { - textModel.applyEdit(textModel.versionId, [{ + textModel.applyEdits(textModel.versionId, [{ index: Number.MAX_VALUE, editType: CellEditType.Metadata, metadata: { editable: false } - }], true); + }], true, undefined, () => undefined); }); // invalid index 2 assert.throws(() => { - textModel.applyEdit(textModel.versionId, [{ + textModel.applyEdits(textModel.versionId, [{ index: -1, editType: CellEditType.Metadata, metadata: { editable: false } - }], true); + }], true, undefined, () => undefined); }); - textModel.applyEdit(textModel.versionId, [{ + textModel.applyEdits(textModel.versionId, [{ index: 0, editType: CellEditType.Metadata, metadata: { editable: false }, - }], true); + }], true, undefined, () => undefined); assert.equal(textModel.cells.length, 1); assert.equal(textModel.cells[0].metadata?.editable, false); } ); }); + + test('multiple inserts in one edit', function () { + withTestNotebook( + instantiationService, + blukEditService, + undoRedoService, + [ + ['var a = 1;', 'javascript', CellKind.Code, [], { editable: true }], + ['var b = 2;', 'javascript', CellKind.Code, [], { editable: false }], + ['var c = 3;', 'javascript', CellKind.Code, [], { editable: false }], + ['var d = 4;', 'javascript', CellKind.Code, [], { editable: false }] + ], + (editor, viewModel, textModel) => { + let changeEvent: NotebookTextModelChangedEvent | undefined = undefined; + const eventListener = textModel.onDidChangeContent(e => { + changeEvent = e; + }); + const version = textModel.versionId; + + textModel.applyEdits(textModel.versionId, [ + { editType: CellEditType.Replace, index: 1, count: 1, cells: [] }, + { editType: CellEditType.Replace, index: 1, count: 0, cells: [new TestCell(viewModel.viewType, 5, 'var e = 5;', 'javascript', CellKind.Code, [], textModelService)] }, + ], true, undefined, () => [0]); + + assert.equal(textModel.cells.length, 4); + assert.equal(textModel.cells[0].getValue(), 'var a = 1;'); + assert.equal(textModel.cells[1].getValue(), 'var e = 5;'); + assert.equal(textModel.cells[2].getValue(), 'var c = 3;'); + + assert.notEqual(changeEvent, undefined); + assert.equal(changeEvent!.rawEvents.length, 2); + assert.deepEqual(changeEvent!.endSelections, [0]); + assert.equal(textModel.versionId, version + 1); + eventListener.dispose(); + } + ); + }); + + test('insert and metadata change in one edit', function () { + withTestNotebook( + instantiationService, + blukEditService, + undoRedoService, + [ + ['var a = 1;', 'javascript', CellKind.Code, [], { editable: true }], + ['var b = 2;', 'javascript', CellKind.Code, [], { editable: false }], + ['var c = 3;', 'javascript', CellKind.Code, [], { editable: false }], + ['var d = 4;', 'javascript', CellKind.Code, [], { editable: false }] + ], + (editor, viewModel, textModel) => { + let changeEvent: NotebookTextModelChangedEvent | undefined = undefined; + const eventListener = textModel.onDidChangeContent(e => { + changeEvent = e; + }); + const version = textModel.versionId; + + textModel.applyEdits(textModel.versionId, [ + { editType: CellEditType.Replace, index: 1, count: 1, cells: [] }, + { + index: 0, + editType: CellEditType.Metadata, + metadata: { editable: false }, + } + ], true, undefined, () => [0]); + + assert.notEqual(changeEvent, undefined); + assert.equal(changeEvent!.rawEvents.length, 2); + assert.deepEqual(changeEvent!.endSelections, [0]); + assert.equal(textModel.versionId, version + 1); + eventListener.dispose(); + } + ); + }); }); diff --git a/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts b/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts index ca1a923e6c..4d91bea0ed 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookViewModel.test.ts @@ -6,8 +6,8 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; -import { CellKind, NotebookCellMetadata, diff, ICellRange } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { withTestNotebook, TestCell, NotebookEditorTestModel, setupInstantiationService } from 'vs/workbench/contrib/notebook/test/testNotebookEditor'; +import { CellKind, NotebookCellMetadata, diff, ICellRange, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { withTestNotebook, NotebookEditorTestModel, setupInstantiationService } from 'vs/workbench/contrib/notebook/test/testNotebookEditor'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; @@ -25,7 +25,7 @@ suite('NotebookViewModel', () => { const modeService = instantiationService.get(IModeService); test('ctor', function () { - const notebook = new NotebookTextModel(0, 'notebook', false, URI.parse('test'), undoRedoService, textModelService, modeService); + const notebook = new NotebookTextModel('notebook', false, URI.parse('test'), [], [], notebookDocumentMetadataDefaults, { transientMetadata: {}, transientOutputs: false }, undoRedoService, textModelService, modeService); const model = new NotebookEditorTestModel(notebook); const eventDispatcher = new NotebookEventDispatcher(); const viewModel = new NotebookViewModel('notebook', model.notebook, eventDispatcher, null, instantiationService, blukEditService, undoRedoService); @@ -45,7 +45,7 @@ suite('NotebookViewModel', () => { assert.equal(viewModel.viewCells[0].metadata?.editable, true); assert.equal(viewModel.viewCells[1].metadata?.editable, false); - const cell = viewModel.insertCell(1, new TestCell(viewModel.viewType, 0, 'var c = 3;', 'javascript', CellKind.Code, [], textModelService), true); + const cell = viewModel.createCell(1, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true, []); assert.equal(viewModel.viewCells.length, 3); assert.equal(viewModel.notebookDocument.cells.length, 3); assert.equal(viewModel.getCellIndex(cell), 1); @@ -69,18 +69,18 @@ suite('NotebookViewModel', () => { ['//c', 'javascript', CellKind.Code, [], { editable: true }], ], (editor, viewModel) => { - viewModel.moveCellToIdx(0, 1, 0, false); + viewModel.moveCellToIdx(0, 1, 0, true); // no-op assert.equal(viewModel.viewCells[0].getText(), '//a'); assert.equal(viewModel.viewCells[1].getText(), '//b'); - viewModel.moveCellToIdx(0, 1, 1, false); + viewModel.moveCellToIdx(0, 1, 1, true); // b, a, c assert.equal(viewModel.viewCells[0].getText(), '//b'); assert.equal(viewModel.viewCells[1].getText(), '//a'); assert.equal(viewModel.viewCells[2].getText(), '//c'); - viewModel.moveCellToIdx(0, 1, 2, false); + viewModel.moveCellToIdx(0, 1, 2, true); // a, c, b assert.equal(viewModel.viewCells[0].getText(), '//a'); assert.equal(viewModel.viewCells[1].getText(), '//c'); @@ -100,12 +100,12 @@ suite('NotebookViewModel', () => { ['//c', 'javascript', CellKind.Code, [], { editable: true }], ], (editor, viewModel) => { - viewModel.moveCellToIdx(1, 1, 0, false); + viewModel.moveCellToIdx(1, 1, 0, true); // b, a, c assert.equal(viewModel.viewCells[0].getText(), '//b'); assert.equal(viewModel.viewCells[1].getText(), '//a'); - viewModel.moveCellToIdx(2, 1, 0, false); + viewModel.moveCellToIdx(2, 1, 0, true); // c, b, a assert.equal(viewModel.viewCells[0].getText(), '//c'); assert.equal(viewModel.viewCells[1].getText(), '//b'); @@ -128,13 +128,13 @@ suite('NotebookViewModel', () => { const lastViewCell = viewModel.viewCells[viewModel.viewCells.length - 1]; const insertIndex = viewModel.getCellIndex(firstViewCell) + 1; - const cell = viewModel.insertCell(insertIndex, new TestCell(viewModel.viewType, 3, 'var c = 3;', 'javascript', CellKind.Code, [], textModelService), true); + const cell = viewModel.createCell(insertIndex, 'var c = 3;', 'javascript', CellKind.Code, {}, [], true); const addedCellIndex = viewModel.getCellIndex(cell); viewModel.deleteCell(addedCellIndex, true); const secondInsertIndex = viewModel.getCellIndex(lastViewCell) + 1; - const cell2 = viewModel.insertCell(secondInsertIndex, new TestCell(viewModel.viewType, 4, 'var d = 4;', 'javascript', CellKind.Code, [], textModelService), true); + const cell2 = viewModel.createCell(secondInsertIndex, 'var d = 4;', 'javascript', CellKind.Code, {}, [], true); assert.equal(viewModel.viewCells.length, 3); assert.equal(viewModel.notebookDocument.cells.length, 3); @@ -261,7 +261,6 @@ function getVisibleCells(cells: T[], hiddenRanges: ICellRange[]) { suite('NotebookViewModel Decorations', () => { const instantiationService = setupInstantiationService(); - const textModelService = instantiationService.get(ITextModelService); const blukEditService = instantiationService.get(IBulkEditService); const undoRedoService = instantiationService.get(IUndoRedoService); @@ -285,7 +284,7 @@ suite('NotebookViewModel Decorations', () => { end: 2, }); - viewModel.insertCell(0, new TestCell(viewModel.viewType, 5, 'var d = 6;', 'javascript', CellKind.Code, [], textModelService), true); + viewModel.createCell(0, 'var d = 6;', 'javascript', CellKind.Code, {}, [], true); assert.deepEqual(viewModel.getTrackedRange(trackedId!), { start: 2, @@ -299,7 +298,7 @@ suite('NotebookViewModel Decorations', () => { end: 2 }); - viewModel.insertCell(3, new TestCell(viewModel.viewType, 6, 'var d = 7;', 'javascript', CellKind.Code, [], textModelService), true); + viewModel.createCell(3, 'var d = 7;', 'javascript', CellKind.Code, {}, [], true); assert.deepEqual(viewModel.getTrackedRange(trackedId!), { start: 1, @@ -345,14 +344,14 @@ suite('NotebookViewModel Decorations', () => { end: 3 }); - viewModel.insertCell(5, new TestCell(viewModel.viewType, 8, 'var d = 9;', 'javascript', CellKind.Code, [], textModelService), true); + viewModel.createCell(5, 'var d = 9;', 'javascript', CellKind.Code, {}, [], true); assert.deepEqual(viewModel.getTrackedRange(trackedId!), { start: 1, end: 3 }); - viewModel.insertCell(4, new TestCell(viewModel.viewType, 9, 'var d = 10;', 'javascript', CellKind.Code, [], textModelService), true); + viewModel.createCell(4, 'var d = 10;', 'javascript', CellKind.Code, {}, [], true); assert.deepEqual(viewModel.getTrackedRange(trackedId!), { start: 1, diff --git a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts index cc6a24c931..8bed5a478b 100644 --- a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts @@ -18,7 +18,7 @@ import { NotebookEventDispatcher } from 'vs/workbench/contrib/notebook/browser/v import { CellViewModel, IModelDecorationsChangeAccessor, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { CellKind, CellUri, INotebookEditorModel, IProcessedOutput, NotebookCellMetadata, IInsetRenderOutput, ICellRange, INotebookKernelInfo2 } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, CellUri, INotebookEditorModel, IProcessedOutput, NotebookCellMetadata, IInsetRenderOutput, ICellRange, INotebookKernelInfo2, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; import { ICompositeCodeEditor, IEditor } from 'vs/editor/common/editorCommon'; import { NotImplementedError } from 'vs/base/common/errors'; @@ -66,6 +66,9 @@ export class TestNotebookEditor implements INotebookEditor { constructor( ) { } + getSelectionHandles(): number[] { + return []; + } setOptions(options: NotebookEditorOptions | undefined): Promise { @@ -82,6 +85,7 @@ export class TestNotebookEditor implements INotebookEditor { onDidScroll = new Emitter().event; onWillDispose = new Emitter().event; onDidChangeVisibleRanges: Event = new Emitter().event; + onDidChangeSelection: Event = new Emitter().event; visibleRanges: ICellRange[] = []; uri?: URI | undefined; textModel?: NotebookTextModel | undefined; @@ -333,7 +337,7 @@ export class NotebookEditorTestModel extends EditorModel implements INotebookEdi ) { super(); - if (_notebook && _notebook.onDidChangeCells) { + if (_notebook && _notebook.onDidChangeContent) { this._register(_notebook.onDidChangeContent(() => { this._dirty = true; this._onDidChangeDirty.fire(); @@ -393,8 +397,7 @@ export function withTestNotebook(instantiationService: TestInstantiationService, const viewType = 'notebook'; const editor = new TestNotebookEditor(); - const notebook = new NotebookTextModel(0, viewType, false, URI.parse('test'), undoRedoService, textModelService, modeService); - notebook.initialize(cells.map(cell => { + const notebook = new NotebookTextModel(viewType, false, URI.parse('test'), cells.map(cell => { return { source: cell[0], language: cell[1], @@ -402,7 +405,7 @@ export function withTestNotebook(instantiationService: TestInstantiationService, outputs: cell[3], metadata: cell[4] }; - })); + }), [], notebookDocumentMetadataDefaults, { transientMetadata: {}, transientOutputs: false }, undoRedoService, textModelService, modeService); const model = new NotebookEditorTestModel(notebook); const eventDispatcher = new NotebookEventDispatcher(); const viewModel = new NotebookViewModel(viewType, model.notebook, eventDispatcher, null, instantiationService, blukEditService, undoRedoService); diff --git a/src/vs/workbench/contrib/outline/browser/outlinePane.ts b/src/vs/workbench/contrib/outline/browser/outlinePane.ts index 66a80e3e51..d99a657993 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePane.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePane.ts @@ -10,7 +10,7 @@ import { createCancelablePromise, TimeoutTimer } from 'vs/base/common/async'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { defaultGenerator } from 'vs/base/common/idGenerator'; -import { dispose, IDisposable, toDisposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { LRUCache } from 'vs/base/common/map'; import { escape } from 'vs/base/common/strings'; import 'vs/css!./outlinePane'; @@ -280,9 +280,9 @@ export class OutlinePane extends ViewPane { } dispose(): void { - dispose(this._disposables); - dispose(this._requestOracle); - dispose(this._editorDisposables); + this._disposables.dispose(); + this._requestOracle?.dispose(); + this._editorDisposables.dispose(); super.dispose(); } @@ -395,7 +395,7 @@ export class OutlinePane extends ViewPane { if (visible && !this._requestOracle) { this._requestOracle = this._instantiationService.createInstance(RequestOracle, (editor, event) => this._doUpdate(editor, event), DocumentSymbolProviderRegistry); } else if (!visible) { - dispose(this._requestOracle); + this._requestOracle?.dispose(); this._requestOracle = undefined; this._doUpdate(undefined, undefined); } @@ -495,7 +495,7 @@ export class OutlinePane extends ViewPane { this._progressBar.infinite().show(requestDelay); const createdModel = await OutlinePane._createOutlineModel(textModel, this._editorDisposables); - dispose(loadingMessage); + loadingMessage?.dispose(); if (!createdModel) { return; } diff --git a/src/vs/workbench/contrib/output/browser/outputView.ts b/src/vs/workbench/contrib/output/browser/outputView.ts index b56bcc94ce..932b0cc32c 100644 --- a/src/vs/workbench/contrib/output/browser/outputView.ts +++ b/src/vs/workbench/contrib/output/browser/outputView.ts @@ -35,7 +35,6 @@ import { ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox'; import { groupBy } from 'vs/base/common/arrays'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { editorBackground, selectBorder } from 'vs/platform/theme/common/colorRegistry'; -import { addClass } from 'vs/base/browser/dom'; import { SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; export class OutputViewPane extends ViewPane { @@ -90,7 +89,7 @@ export class OutputViewPane extends ViewPane { renderBody(container: HTMLElement): void { super.renderBody(container); this.editor.create(container); - addClass(container, 'output-view'); + container.classList.add('output-view'); const codeEditor = this.editor.getControl(); codeEditor.setAriaOptions({ role: 'document', activeDescendant: undefined }); this._register(codeEditor.onDidChangeModelContent(() => { @@ -293,7 +292,7 @@ class SwitchOutputActionViewItem extends SelectActionViewItem { render(container: HTMLElement): void { super.render(container); - addClass(container, 'switch-output'); + container.classList.add('switch-output'); this._register(attachStylerCallback(this.themeService, { selectBorder }, colors => { container.style.borderColor = colors.selectBorder ? `${colors.selectBorder}` : ''; })); diff --git a/src/vs/workbench/contrib/output/common/outputLinkComputer.ts b/src/vs/workbench/contrib/output/common/outputLinkComputer.ts index c21851e11b..f32f905a6a 100644 --- a/src/vs/workbench/contrib/output/common/outputLinkComputer.ts +++ b/src/vs/workbench/contrib/output/common/outputLinkComputer.ts @@ -12,7 +12,6 @@ import * as strings from 'vs/base/common/strings'; import { Range } from 'vs/editor/common/core/range'; import { isWindows } from 'vs/base/common/platform'; import { Schemas } from 'vs/base/common/network'; -import { find } from 'vs/base/common/arrays'; export interface ICreateData { workspaceFolders: string[]; @@ -47,7 +46,7 @@ export class OutputLinkComputer { private getModel(uri: string): IMirrorModel | undefined { const models = this.ctx.getMirrorModels(); - return find(models, model => model.uri.toString() === uri); + return models.find(model => model.uri.toString() === uri); } computeLinks(uri: string): ILink[] { diff --git a/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts b/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts index 221bd43cc1..0537b89873 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts @@ -8,8 +8,8 @@ import { exists, readdir, readFile, rimraf } from 'vs/base/node/pfs'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { localize } from 'vs/nls'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { PerfviewInput } from 'vs/workbench/contrib/performance/browser/perfviewEditor'; @@ -55,7 +55,7 @@ export class StartupProfiler implements IWorkbenchContribution { const removeArgs: string[] = ['--prof-startup']; const markerFile = readFile(profileFilenamePrefix).then(value => removeArgs.push(...value.toString().split('|'))) .then(() => rimraf(profileFilenamePrefix)) // (1) delete the file to tell the main process to stop profiling - .then(() => new Promise(resolve => { // (2) wait for main that recreates the fail to signal profiling has stopped + .then(() => new Promise(resolve => { // (2) wait for main that recreates the fail to signal profiling has stopped const check = () => { exists(profileFilenamePrefix).then(exists => { if (exists) { diff --git a/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts b/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts index c74380c60b..92f9acbf27 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts @@ -9,7 +9,7 @@ import { promisify } from 'util'; import { onUnexpectedError } from 'vs/base/common/errors'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { ILifecycleService, StartupKind, StartupKindToString } from 'vs/platform/lifecycle/common/lifecycle'; import { IProductService } from 'vs/platform/product/common/productService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index 15bc97a97a..446beba8f3 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts @@ -358,7 +358,7 @@ export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorP this._register(this.recordKeysAction.onDidChange(e => { if (e.checked !== undefined) { - DOM.toggleClass(recordingBadge, 'disabled', !e.checked); + recordingBadge.classList.toggle('disabled', !e.checked); if (e.checked) { this.searchWidget.inputBox.setPlaceHolder(keybindingsSearchPlaceholder); this.searchWidget.inputBox.setAriaLabel(keybindingsSearchPlaceholder); @@ -424,7 +424,7 @@ export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorP private layoutSearchWidget(dimension: DOM.Dimension): void { this.searchWidget.layout(dimension); - DOM.toggleClass(this.headerContainer, 'small', dimension.width < 400); + this.headerContainer.classList.toggle('small', dimension.width < 400); this.searchWidget.inputBox.inputElement.style.paddingRight = `${DOM.getTotalWidth(this.actionsContainer) + 12}px`; } @@ -474,10 +474,10 @@ export class KeybindingsEditor extends EditorPane implements IKeybindingsEditorP this._register(this.keybindingsList.onContextMenu(e => this.onContextMenu(e))); this._register(this.keybindingsList.onDidChangeFocus(e => this.onFocusChange(e))); this._register(this.keybindingsList.onDidFocus(() => { - DOM.addClass(this.keybindingsList.getHTMLElement(), 'focused'); + this.keybindingsList.getHTMLElement().classList.add('focused'); })); this._register(this.keybindingsList.onDidBlur(() => { - DOM.removeClass(this.keybindingsList.getHTMLElement(), 'focused'); + this.keybindingsList.getHTMLElement().classList.remove('focused'); this.keybindingFocusContextKey.reset(); })); this._register(this.keybindingsList.onMouseDblClick(() => { @@ -821,7 +821,7 @@ class KeybindingItemRenderer implements IListRenderer { const remoteAuthority = this.environmentService.configuration.remoteAuthority; - const hostLabel = this.labelService.getHostLabel(REMOTE_HOST_SCHEME, remoteAuthority) || remoteAuthority; + const hostLabel = this.labelService.getHostLabel(Schemas.vscodeRemote, remoteAuthority) || remoteAuthority; const label = nls.localize('openRemoteSettings', "Open Remote Settings ({0})", hostLabel); registerAction2(class extends Action2 { constructor() { diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts index 8ef366f46d..373fa7c2e1 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts @@ -6,7 +6,6 @@ import * as DOM from 'vs/base/browser/dom'; import { Orientation, Sizing, SplitView } from 'vs/base/browser/ui/splitview/splitview'; import { Widget } from 'vs/base/browser/ui/widget'; -import * as arrays from 'vs/base/common/arrays'; import { Delayer, ThrottledDelayer } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { IStringDictionary } from 'vs/base/common/collections'; @@ -104,7 +103,7 @@ export class PreferencesEditor extends EditorPane { } createEditor(parent: HTMLElement): void { - DOM.addClass(parent, 'preferences-editor'); + parent.classList.add('preferences-editor'); this.headerContainer = DOM.append(parent, DOM.$('.preferences-header')); this.searchWidget = this._register(this.instantiationService.createInstance(SearchWidget, this.headerContainer, { @@ -646,7 +645,7 @@ class PreferencesRenderersController extends Disposable { const current = this._settingsNavigator && this._settingsNavigator.current(); const navigatorSettings = this._lastQuery ? consolidatedSettings : []; const currentIndex = current ? - arrays.firstIndex(navigatorSettings, s => s.key === current.key) : + navigatorSettings.findIndex(s => s.key === current.key) : -1; this._settingsNavigator = new SettingsNavigator(navigatorSettings, Math.max(currentIndex, 0)); @@ -691,7 +690,7 @@ class PreferencesRenderersController extends Disposable { if (nlpMetadata) { const sortedKeys = Object.keys(nlpMetadata.scoredResults).sort((a, b) => nlpMetadata.scoredResults[b].score - nlpMetadata.scoredResults[a].score); const suffix = '##' + key; - data['nlpIndex'] = arrays.firstIndex(sortedKeys, key => key.endsWith(suffix)); + data['nlpIndex'] = sortedKeys.findIndex(key => key.endsWith(suffix)); } const settingLocation = this._findSetting(this.lastFilterResult, key); @@ -791,7 +790,7 @@ class SideBySidePreferencesWidget extends Widget { ) { super(); - DOM.addClass(parentElement, 'side-by-side-preferences-editor'); + parentElement.classList.add('side-by-side-preferences-editor'); this.splitview = new SplitView(parentElement, { orientation: Orientation.HORIZONTAL }); this._register(this.splitview); diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts index e6a24138f6..2351193c60 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts @@ -31,7 +31,6 @@ import { DefaultSettingsEditorModel, SettingsEditorModel, WorkspaceConfiguration import { IMarkerService, IMarkerData, MarkerSeverity, MarkerTag } from 'vs/platform/markers/common/markers'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; -import { find } from 'vs/base/common/arrays'; export interface IPreferencesRenderer extends IDisposable { readonly preferencesModel: IPreferencesEditorModel; @@ -321,7 +320,7 @@ export class DefaultSettingsRenderer extends Disposable implements IPreferencesR const { key, overrideOf } = setting; if (overrideOf) { const setting = this.getSetting(overrideOf); - return find(setting!.overrides!, override => override.key === key); + return setting!.overrides!.find(override => override.key === key); } const settingsGroups = this.filterResult ? this.filterResult.filteredGroups : this.preferencesModel.settingsGroups; return this.getPreference(key, settingsGroups); diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts index e0fe1ff1d2..d3cbb06251 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts @@ -25,7 +25,7 @@ import { IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; -import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; +import { Schemas } from 'vs/base/common/network'; import { activeContrastBorder, badgeBackground, badgeForeground, contrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry'; import { attachInputBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; @@ -192,15 +192,15 @@ export class SettingsGroupTitleWidget extends Widget implements IViewZone { } toggleCollapse(collapse: boolean) { - DOM.toggleClass(this.titleContainer, 'collapsed', collapse); + this.titleContainer.classList.toggle('collapsed', collapse); } toggleFocus(focus: boolean): void { - DOM.toggleClass(this.titleContainer, 'focused', focus); + this.titleContainer.classList.toggle('focused', focus); } isCollapsed(): boolean { - return DOM.hasClass(this.titleContainer, 'collapsed'); + return this.titleContainer.classList.contains('collapsed'); } private layout(): void { @@ -256,7 +256,7 @@ export class SettingsGroupTitleWidget extends Widget implements IViewZone { private collapse(collapse: boolean) { if (collapse !== this.isCollapsed()) { - DOM.toggleClass(this.titleContainer, 'collapsed', collapse); + this.titleContainer.classList.toggle('collapsed', collapse); this._onToggled.fire(collapse); } } @@ -412,17 +412,17 @@ export class FolderSettingsActionViewItem extends BaseActionViewItem { this.anchorElement.title = (await this.preferencesService.getEditableSettingsURI(ConfigurationTarget.WORKSPACE_FOLDER, this._folder.uri))?.fsPath || ''; const detailsText = this.labelWithCount(this._action.label, total); this.detailsElement.textContent = detailsText; - DOM.toggleClass(this.dropDownElement, 'hide', workspace.folders.length === 1 || !this._action.checked); + this.dropDownElement.classList.toggle('hide', workspace.folders.length === 1 || !this._action.checked); } else { const labelText = this.labelWithCount(this._action.label, total); this.labelElement.textContent = labelText; this.detailsElement.textContent = ''; this.anchorElement.title = this._action.label; - DOM.removeClass(this.dropDownElement, 'hide'); + this.dropDownElement.classList.remove('hide'); } - DOM.toggleClass(this.anchorElement, 'checked', this._action.checked); - DOM.toggleClass(this.container, 'disabled', !this._action.enabled); + this.anchorElement.classList.toggle('checked', this._action.checked); + this.container.classList.toggle('disabled', !this._action.enabled); } private showMenu(): void { @@ -516,7 +516,7 @@ export class SettingsTargetsWidget extends Widget { }); const remoteAuthority = this.environmentService.configuration.remoteAuthority; - const hostLabel = remoteAuthority && this.labelService.getHostLabel(REMOTE_HOST_SCHEME, remoteAuthority); + const hostLabel = remoteAuthority && this.labelService.getHostLabel(Schemas.vscodeRemote, remoteAuthority); const remoteSettingsLabel = localize('userSettingsRemote', "Remote") + (hostLabel ? ` [${hostLabel}]` : ''); this.userRemoteSettings = new Action('userSettingsRemote', remoteSettingsLabel, '.settings-tab', true, () => this.updateTarget(ConfigurationTarget.USER_REMOTE)); @@ -595,7 +595,7 @@ export class SettingsTargetsWidget extends Widget { } private async update(): Promise { - DOM.toggleClass(this.settingsSwitcherBar.domNode, 'empty-workbench', this.contextService.getWorkbenchState() === WorkbenchState.EMPTY); + this.settingsSwitcherBar.domNode.classList.toggle('empty-workbench', this.contextService.getWorkbenchState() === WorkbenchState.EMPTY); this.userRemoteSettings.enabled = !!(this.options.enableRemoteSettings && this.environmentService.configuration.remoteAuthority); this.workspaceSettings.enabled = this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY; this.folderSettings.getAction().enabled = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE && this.contextService.getWorkspace().folders.length > 0; @@ -697,13 +697,13 @@ export class SearchWidget extends Widget { layout(dimension: DOM.Dimension) { if (dimension.width < 400) { if (this.countElement) { - DOM.addClass(this.countElement, 'hide'); + this.countElement.classList.add('hide'); } this.inputBox.inputElement.style.paddingRight = '0px'; } else { if (this.countElement) { - DOM.removeClass(this.countElement, 'hide'); + this.countElement.classList.remove('hide'); } this.inputBox.inputElement.style.paddingRight = this.getControlsWidth() + 'px'; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 5902e0097d..981cf7d66b 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -9,7 +9,6 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Button } from 'vs/base/browser/ui/button/button'; import { ITreeElement } from 'vs/base/browser/ui/tree/tree'; import { Action } from 'vs/base/common/actions'; -import * as arrays from 'vs/base/common/arrays'; import { Delayer, IntervalTimer, ThrottledDelayer, timeout } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import * as collections from 'vs/base/common/collections'; @@ -349,7 +348,10 @@ export class SettingsEditor2 extends EditorPane { super.setEditorVisible(visible, group); if (!visible) { - this.searchWidget.onHide(); + // Wait for editor to be removed from DOM #106303 + setTimeout(() => { + this.searchWidget.onHide(); + }, 0); } } @@ -871,19 +873,19 @@ export class SettingsEditor2 extends EditorPane { const remoteResult = props.searchResults[SearchResultIdx.Remote]; const localResult = props.searchResults[SearchResultIdx.Local]; - const localIndex = arrays.firstIndex(localResult!.filterMatches, m => m.setting.key === props.key); + const localIndex = localResult!.filterMatches.findIndex(m => m.setting.key === props.key); groupId = localIndex >= 0 ? 'local' : 'remote'; displayIndex = localIndex >= 0 ? localIndex : - remoteResult && (arrays.firstIndex(remoteResult.filterMatches, m => m.setting.key === props.key) + localResult.filterMatches.length); + remoteResult && (remoteResult.filterMatches.findIndex(m => m.setting.key === props.key) + localResult.filterMatches.length); if (this.searchResultModel) { const rawResults = this.searchResultModel.getRawResults(); if (rawResults[SearchResultIdx.Remote]) { - const _nlpIndex = arrays.firstIndex(rawResults[SearchResultIdx.Remote].filterMatches, m => m.setting.key === props.key); + const _nlpIndex = rawResults[SearchResultIdx.Remote].filterMatches.findIndex(m => m.setting.key === props.key); nlpIndex = _nlpIndex >= 0 ? _nlpIndex : undefined; } } diff --git a/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts b/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts index 84f9b0ce7f..f9851327f0 100644 --- a/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts +++ b/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts @@ -4,8 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as dom from 'vs/base/browser/dom'; - import { IAction, Action } from 'vs/base/common/actions'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; @@ -13,7 +11,6 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView import { IRemoteExplorerService, REMOTE_EXPLORER_TYPE_KEY } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox'; import { IViewDescriptor } from 'vs/workbench/common/views'; -import { startsWith } from 'vs/base/common/strings'; import { isStringArray } from 'vs/base/common/types'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; @@ -73,7 +70,7 @@ export class SwitchRemoteViewItem extends SelectActionViewItem { render(container: HTMLElement) { if (this.optionsItems.length > 1) { super.render(container); - dom.addClass(container, 'switch-remote'); + container.classList.add('switch-remote'); } } @@ -84,7 +81,7 @@ export class SwitchRemoteViewItem extends SelectActionViewItem { static createOptionItems(views: IViewDescriptor[], contextKeyService: IContextKeyService): IRemoteSelectItem[] { let options: IRemoteSelectItem[] = []; views.forEach(view => { - if (view.group && startsWith(view.group, 'targets') && view.remoteAuthority && (!view.when || contextKeyService.contextMatchesRules(view.when))) { + if (view.group && view.group.startsWith('targets') && view.remoteAuthority && (!view.when || contextKeyService.contextMatchesRules(view.when))) { options.push({ text: view.name, authority: isStringArray(view.remoteAuthority) ? view.remoteAuthority : [view.remoteAuthority] }); } }); diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index 182e667082..7f78d884ff 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -274,7 +274,7 @@ class HelpItemValue { constructor(private commandService: ICommandService, public extensionDescription: IExtensionDescription, public remoteAuthority: string[] | undefined, private urlOrCommand?: string) { } get url(): Promise { - return new Promise(async (resolve) => { + return new Promise(async (resolve) => { if (this._url === undefined) { if (this.urlOrCommand) { let url = URI.parse(this.urlOrCommand); @@ -286,10 +286,11 @@ class HelpItemValue { const emptyString: Promise = new Promise(resolve => setTimeout(() => resolve(''), 500)); this._url = await Promise.race([urlCommand, emptyString]); } - } else { - this._url = ''; } } + if (this._url === undefined) { + this._url = ''; + } resolve(this._url); }); } diff --git a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts index 708d50a707..42d9872689 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts @@ -12,16 +12,15 @@ import { MenuId, IMenuService, MenuItemAction, IMenu, MenuRegistry, registerActi import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar'; import { ILabelService } from 'vs/platform/label/common/label'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; +import { Schemas } from 'vs/base/common/network'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { RemoteConnectionState } from 'vs/workbench/browser/contextkeys'; import { isWeb } from 'vs/base/common/platform'; import { once } from 'vs/base/common/functional'; @@ -38,7 +37,7 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr private remoteAuthority = this.environmentService.configuration.remoteAuthority; private connectionState: 'initializing' | 'connected' | 'disconnected' | undefined = undefined; - private connectionStateContextKey = RemoteConnectionState.bindTo(this.contextKeyService); + private connectionStateContextKey = new RawContextKey<'' | 'initializing' | 'disconnected' | 'connected'>('remoteConnectionState', '').bindTo(this.contextKeyService); constructor( @IStatusbarService private readonly statusbarService: IStatusbarService, @@ -195,7 +194,7 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr // Remote Authority: show connection state else if (this.remoteAuthority) { - const hostLabel = this.labelService.getHostLabel(REMOTE_HOST_SCHEME, this.remoteAuthority) || this.remoteAuthority; + const hostLabel = this.labelService.getHostLabel(Schemas.vscodeRemote, this.remoteAuthority) || this.remoteAuthority; switch (this.connectionState) { case 'initializing': this.renderRemoteStatusIndicator(`$(sync~spin) ${nls.localize('host.open', "Opening Remote...")}`, nls.localize('host.open', "Opening Remote...")); diff --git a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts index 05608dbb13..9ad778fe69 100644 --- a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts @@ -14,36 +14,31 @@ import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { ILabelService } from 'vs/platform/label/common/label'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; +import { Schemas } from 'vs/base/common/network'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { DialogChannel } from 'vs/platform/dialogs/electron-browser/dialogIpc'; import { DownloadServiceChannel } from 'vs/platform/download/common/downloadIpc'; import { LoggerChannel } from 'vs/platform/log/common/logIpc'; import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import { IDiagnosticInfoOptions, IRemoteDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { RemoteFileDialogContext } from 'vs/workbench/browser/contextkeys'; import { IDownloadService } from 'vs/platform/download/common/download'; -import { OpenLocalFileFolderCommand, OpenLocalFileCommand, OpenLocalFolderCommand, SaveLocalFileCommand } from 'vs/workbench/services/dialogs/browser/simpleFileDialog'; -import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; +import { OpenLocalFileFolderCommand, OpenLocalFileCommand, OpenLocalFolderCommand, SaveLocalFileCommand, RemoteFileDialogContext } from 'vs/workbench/services/dialogs/browser/simpleFileDialog'; class RemoteChannelsContribution implements IWorkbenchContribution { constructor( @ILogService logService: ILogService, @IRemoteAgentService remoteAgentService: IRemoteAgentService, - @IDialogService dialogService: IDialogService, @IDownloadService downloadService: IDownloadService ) { const connection = remoteAgentService.getConnection(); if (connection) { - connection.registerChannel('dialog', new DialogChannel(dialogService)); connection.registerChannel('download', new DownloadServiceChannel(downloadService)); connection.registerChannel('logger', new LoggerChannel(logService)); } @@ -58,7 +53,7 @@ class RemoteAgentDiagnosticListener implements IWorkbenchContribution { ipcRenderer.on('vscode:getDiagnosticInfo', (event: unknown, request: { replyChannel: string, args: IDiagnosticInfoOptions }): void => { const connection = remoteAgentService.getConnection(); if (connection) { - const hostName = labelService.getHostLabel(REMOTE_HOST_SCHEME, connection.remoteAuthority); + const hostName = labelService.getHostLabel(Schemas.vscodeRemote, connection.remoteAuthority); remoteAgentService.getDiagnosticInfo(request.args) .then(info => { if (info) { diff --git a/src/vs/workbench/contrib/sash/browser/sash.ts b/src/vs/workbench/contrib/sash/browser/sash.ts index 390f5def22..ed1a3ba897 100644 --- a/src/vs/workbench/contrib/sash/browser/sash.ts +++ b/src/vs/workbench/contrib/sash/browser/sash.ts @@ -35,7 +35,7 @@ export class SashSizeController extends Disposable implements IWorkbenchContribu const size = clamp(this.configurationService.getValue(this.configurationName) ?? minSize, minSize, maxSize); // Update styles - this.stylesheet.innerHTML = ` + this.stylesheet.textContent = ` .monaco-sash.vertical { cursor: ew-resize; top: 0; width: ${size}px; height: 100%; } .monaco-sash.horizontal { cursor: ns-resize; left: 0; width: 100%; height: ${size}px; } .monaco-sash:not(.disabled).orthogonal-start::before, .monaco-sash:not(.disabled).orthogonal-end::after { content: ' '; height: ${size * 2}px; width: ${size * 2}px; z-index: 100; display: block; cursor: all-scroll; position: absolute; } diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index 2d6e8409dd..f2d45f5d94 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -41,7 +41,7 @@ import { MenuId, IMenuService, IMenu, MenuItemAction, MenuRegistry } from 'vs/pl import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IChange, IEditorModel, ScrollType, IEditorContribution, IDiffEditorModel } from 'vs/editor/common/editorCommon'; import { OverviewRulerLane, ITextModel, IModelDecorationOptions, MinimapPosition } from 'vs/editor/common/model'; -import { sortedDiff, firstIndex } from 'vs/base/common/arrays'; +import { sortedDiff } from 'vs/base/common/arrays'; import { IMarginData } from 'vs/editor/browser/controller/mouseTarget'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { ISplice } from 'vs/base/common/sequence'; @@ -769,7 +769,7 @@ export class DirtyDiffController extends Disposable implements IEditorContributi return; } - const index = firstIndex(model.changes, change => lineIntersectsChange(lineNumber, change)); + const index = model.changes.findIndex(change => lineIntersectsChange(lineNumber, change)); if (index < 0) { return; @@ -1301,7 +1301,7 @@ export class DirtyDiffWorkbenchController extends Disposable implements ext.IWor private setViewState(state: IViewState): void { this.viewState = state; - this.stylesheet.innerHTML = ` + this.stylesheet.textContent = ` .monaco-editor .dirty-diff-modified,.monaco-editor .dirty-diff-added{border-left-width:${state.width}px;} .monaco-editor .dirty-diff-modified, .monaco-editor .dirty-diff-added, .monaco-editor .dirty-diff-deleted { opacity: ${state.visibility === 'always' ? 1 : 0}; diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index 3303fa1b01..5bb68d6121 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -8,6 +8,16 @@ position: relative; } +.scm-overflow-widgets-container { + position: absolute; + top: 0; + left: 0; + width: 0; + height: 0; + overflow: visible; + z-index: 5000; +} + .scm-view .monaco-tl-contents > div { margin-right: 12px; overflow: hidden; @@ -39,6 +49,7 @@ flex: 1; overflow: hidden; text-overflow: ellipsis; + min-width: 50px; } .scm-view .scm-provider > .label > .name { diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts index 6d51a37e20..6b7c0e7b22 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoryRenderer.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/scm'; import { basename } from 'vs/base/common/resources'; import { IDisposable, Disposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle'; -import { append, $, addClass, toggleClass } from 'vs/base/browser/dom'; +import { append, $ } from 'vs/base/browser/dom'; import { ISCMRepository, ISCMViewService } from 'vs/workbench/contrib/scm/common/scm'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -48,7 +48,7 @@ export class RepositoryRenderer implements ICompressibleTreeRenderer toggleClass(provider, 'active', e)); + const visibilityDisposable = toolBar.onDidChangeDropdownVisibility(e => provider.classList.toggle('active', e)); const disposable = Disposable.None; const templateDisposable = combinedDisposable(visibilityDisposable, toolBar, badgeStyler); diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index e806bb90f1..c060b07a26 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -8,7 +8,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { basename, dirname, isEqual } from 'vs/base/common/resources'; import { IDisposable, Disposable, DisposableStore, combinedDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { append, $, addClass, toggleClass, removeClass, Dimension } from 'vs/base/browser/dom'; +import { append, $, Dimension } from 'vs/base/browser/dom'; import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list'; import { ISCMResourceGroup, ISCMResource, InputValidationType, ISCMRepository, ISCMInput, IInputValidation, ISCMViewService, ISCMViewVisibleRepositoryChangeEvent, ISCMService } from 'vs/workbench/contrib/scm/common/scm'; import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels'; @@ -22,7 +22,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { MenuItemAction, IMenuService } from 'vs/platform/actions/common/actions'; import { IAction, IActionViewItem, ActionRunner, Action, RadioGroup, Separator, SubmenuAction, IActionViewItemProvider } from 'vs/base/common/actions'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IThemeService, LIGHT, registerThemingParticipant, IFileIconTheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, IFileIconTheme } from 'vs/platform/theme/common/themeService'; import { isSCMResource, isSCMResourceGroup, connectPrimaryMenuToInlineActionBar, isSCMRepository, isSCMInput, collectContextMenuActions, StatusBarAction, StatusBarActionViewItem, getRepositoryVisibilityActions } from './util'; import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; import { WorkbenchCompressibleObjectTree, IOpenEvent } from 'vs/platform/list/browser/listService'; @@ -75,6 +75,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { RepositoryRenderer } from 'vs/workbench/contrib/scm/browser/scmRepositoryRenderer'; import { IPosition } from 'vs/editor/common/core/position'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; type TreeElement = ISCMRepository | ISCMInput | ISCMResourceGroup | IResourceNode | ISCMResource; @@ -136,17 +137,18 @@ class InputRenderer implements ICompressibleTreeRenderer void, @IInstantiationService private instantiationService: IInstantiationService, ) { } renderTemplate(container: HTMLElement): InputTemplate { // hack - addClass(container.parentElement!.parentElement!.querySelector('.monaco-tl-twistie')! as HTMLElement, 'force-no-twistie'); + (container.parentElement!.parentElement!.querySelector('.monaco-tl-twistie')! as HTMLElement).classList.add('force-no-twistie'); const disposables = new DisposableStore(); const inputElement = append(container, $('.scm-input')); - const inputWidget = this.instantiationService.createInstance(SCMInputWidget, inputElement); + const inputWidget = this.instantiationService.createInstance(SCMInputWidget, inputElement, this.overflowWidgetsDomNode); disposables.add(inputWidget); return { inputWidget, disposable: Disposable.None, templateDisposable: disposables }; @@ -266,7 +268,7 @@ class ResourceGroupRenderer implements ICompressibleTreeRenderer { const theme = this.themeService.getColorTheme(); - const icon = iconResource && (theme.type === LIGHT ? iconResource.decorations.icon : iconResource.decorations.iconDark); + const icon = iconResource && (theme.type === ColorScheme.LIGHT ? iconResource.decorations.icon : iconResource.decorations.iconDark); template.fileLabel.setFile(uri, { fileDecorations: { colors: false, badges: !icon }, @@ -462,8 +464,8 @@ class ResourceRenderer implements ICompressibleTreeRenderer toggleClass(this.placeholderTextContainer, 'hidden', textModel.getValueLength() > 0); + const updatePlaceholderVisibility = () => this.placeholderTextContainer.classList.toggle('hidden', textModel.getValueLength() > 0); this.repositoryDisposables.add(textModel.onDidChangeContent(() => { input.value = textModel.getValue(); updatePlaceholderVisibility(); @@ -1372,6 +1374,7 @@ class SCMInputWidget extends Disposable { constructor( container: HTMLElement, + overflowWidgetsDomNode: HTMLElement, @IContextKeyService contextKeyService: IContextKeyService, @IModelService private modelService: IModelService, @IModeService private modeService: IModeService, @@ -1401,7 +1404,8 @@ class SCMInputWidget extends Disposable { wrappingIndent: 'none', padding: { top: 3, bottom: 3 }, quickSuggestions: false, - scrollbar: { alwaysConsumeMouseWheel: false } + scrollbar: { alwaysConsumeMouseWheel: false }, + overflowWidgetsDomNode }; const codeEditorWidgetOptions: ICodeEditorWidgetOptions = { @@ -1425,11 +1429,11 @@ class SCMInputWidget extends Disposable { this._register(this.inputEditor.onDidFocusEditorText(() => { this.input?.repository.setSelected(true); // TODO@joao: remove - addClass(this.editorContainer, 'synthetic-focus'); + this.editorContainer.classList.add('synthetic-focus'); this.renderValidation(); })); this._register(this.inputEditor.onDidBlurEditorText(() => { - removeClass(this.editorContainer, 'synthetic-focus'); + this.editorContainer.classList.remove('synthetic-focus'); this.validationDisposable.dispose(); })); @@ -1457,7 +1461,7 @@ class SCMInputWidget extends Disposable { focus(): void { this.inputEditor.focus(); - addClass(this.editorContainer, 'synthetic-focus'); + this.editorContainer.classList.add('synthetic-focus'); } hasFocus(): boolean { @@ -1467,9 +1471,9 @@ class SCMInputWidget extends Disposable { private renderValidation(): void { this.validationDisposable.dispose(); - toggleClass(this.editorContainer, 'validation-info', this.validation?.type === InputValidationType.Information); - toggleClass(this.editorContainer, 'validation-warning', this.validation?.type === InputValidationType.Warning); - toggleClass(this.editorContainer, 'validation-error', this.validation?.type === InputValidationType.Error); + this.editorContainer.classList.toggle('validation-info', this.validation?.type === InputValidationType.Information); + this.editorContainer.classList.toggle('validation-warning', this.validation?.type === InputValidationType.Warning); + this.editorContainer.classList.toggle('validation-error', this.validation?.type === InputValidationType.Error); if (!this.validation || !this.inputEditor.hasTextFocus()) { return; @@ -1479,9 +1483,9 @@ class SCMInputWidget extends Disposable { getAnchor: () => this.editorContainer, render: container => { const element = append(container, $('.scm-editor-validation')); - toggleClass(element, 'validation-info', this.validation!.type === InputValidationType.Information); - toggleClass(element, 'validation-warning', this.validation!.type === InputValidationType.Warning); - toggleClass(element, 'validation-error', this.validation!.type === InputValidationType.Error); + element.classList.toggle('validation-info', this.validation!.type === InputValidationType.Information); + element.classList.toggle('validation-warning', this.validation!.type === InputValidationType.Warning); + element.classList.toggle('validation-error', this.validation!.type === InputValidationType.Error); element.style.width = `${this.editorContainer.clientWidth}px`; element.textContent = this.validation!.message; return Disposable.None; @@ -1591,21 +1595,23 @@ export class SCMViewPane extends ViewPane { // List this.listContainer = append(container, $('.scm-view.show-file-icons')); - const updateActionsVisibility = () => toggleClass(this.listContainer, 'show-actions', this.configurationService.getValue('scm.alwaysShowActions')); + const overflowWidgetsDomNode = $('.scm-overflow-widgets-container.monaco-editor'); + + const updateActionsVisibility = () => this.listContainer.classList.toggle('show-actions', this.configurationService.getValue('scm.alwaysShowActions')); this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.alwaysShowActions'))(updateActionsVisibility)); updateActionsVisibility(); const updateProviderCountVisibility = () => { const value = this.configurationService.getValue<'hidden' | 'auto' | 'visible'>('scm.providerCountBadge'); - toggleClass(this.listContainer, 'hide-provider-counts', value === 'hidden'); - toggleClass(this.listContainer, 'auto-provider-counts', value === 'auto'); + this.listContainer.classList.toggle('hide-provider-counts', value === 'hidden'); + this.listContainer.classList.toggle('auto-provider-counts', value === 'auto'); }; this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.providerCountBadge'))(updateProviderCountVisibility)); updateProviderCountVisibility(); this._register(this.scmViewService.onDidChangeVisibleRepositories(() => this.updateActions())); - this.inputRenderer = this.instantiationService.createInstance(InputRenderer, this.layoutCache, (input, height) => this.tree.updateElementHeight(input, height)); + this.inputRenderer = this.instantiationService.createInstance(InputRenderer, this.layoutCache, overflowWidgetsDomNode, (input, height) => this.tree.updateElementHeight(input, height)); const delegate = new ListDelegate(this.inputRenderer); const actionViewItemProvider = (action: IAction) => this.getActionViewItem(action); @@ -1642,7 +1648,6 @@ export class SCMViewPane extends ViewPane { filter, sorter, keyboardNavigationLabelProvider, - transformOptimization: false, overrideStyles: { listBackground: this.viewDescriptorService.getViewLocationById(this.id) === ViewContainerLocation.Sidebar ? SIDE_BAR_BACKGROUND : PANEL_BACKGROUND }, @@ -1655,6 +1660,8 @@ export class SCMViewPane extends ViewPane { this._register(this.tree.onDidScroll(this.inputRenderer.clearValidation, this.inputRenderer)); this._register(this.tree); + append(this.listContainer, overflowWidgetsDomNode); + let viewMode = this.configurationService.getValue<'tree' | 'list'>('scm.defaultViewMode') === 'list' ? ViewModelMode.List : ViewModelMode.Tree; const storageMode = this.storageService.get(`scm.viewMode`, StorageScope.WORKSPACE) as ViewModelMode; @@ -1665,8 +1672,8 @@ export class SCMViewPane extends ViewPane { this.viewModel = this.instantiationService.createInstance(ViewModel, this.tree, this.inputRenderer, viewMode, ViewModelSortKey.Path); this._register(this.viewModel); - addClass(this.listContainer, 'file-icon-themable-tree'); - addClass(this.listContainer, 'show-file-icons'); + this.listContainer.classList.add('file-icon-themable-tree'); + this.listContainer.classList.add('show-file-icons'); this.updateIndentStyles(this.themeService.getFileIconTheme()); this._register(this.themeService.onDidFileIconThemeChange(this.updateIndentStyles, this)); @@ -1682,10 +1689,10 @@ export class SCMViewPane extends ViewPane { } private updateIndentStyles(theme: IFileIconTheme): void { - toggleClass(this.listContainer, 'list-view-mode', this.viewModel.mode === ViewModelMode.List); - toggleClass(this.listContainer, 'tree-view-mode', this.viewModel.mode === ViewModelMode.Tree); - toggleClass(this.listContainer, 'align-icons-and-twisties', (this.viewModel.mode === ViewModelMode.List && theme.hasFileIcons) || (theme.hasFileIcons && !theme.hasFolderIcons)); - toggleClass(this.listContainer, 'hide-arrows', this.viewModel.mode === ViewModelMode.Tree && theme.hidesExplorerArrows === true); + this.listContainer.classList.toggle('list-view-mode', this.viewModel.mode === ViewModelMode.List); + this.listContainer.classList.toggle('tree-view-mode', this.viewModel.mode === ViewModelMode.Tree); + this.listContainer.classList.toggle('align-icons-and-twisties', (this.viewModel.mode === ViewModelMode.List && theme.hasFileIcons) || (theme.hasFileIcons && !theme.hasFolderIcons)); + this.listContainer.classList.toggle('hide-arrows', this.viewModel.mode === ViewModelMode.Tree && theme.hidesExplorerArrows === true); } private onDidChangeMode(): void { diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPaneContainer.ts b/src/vs/workbench/contrib/scm/browser/scmViewPaneContainer.ts index e141eabab6..4ae91a30dd 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPaneContainer.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPaneContainer.ts @@ -17,7 +17,6 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { addClass } from 'vs/base/browser/dom'; import { SCMViewPane } from 'vs/workbench/contrib/scm/browser/scmViewPane'; export class SCMViewPaneContainer extends ViewPaneContainer { @@ -39,7 +38,7 @@ export class SCMViewPaneContainer extends ViewPaneContainer { create(parent: HTMLElement): void { super.create(parent); - addClass(parent, 'scm-viewlet'); + parent.classList.add('scm-viewlet'); } getActionsContext(): unknown { diff --git a/src/vs/workbench/contrib/scm/browser/util.ts b/src/vs/workbench/contrib/scm/browser/util.ts index 11e2ef3999..c191c161f7 100644 --- a/src/vs/workbench/contrib/scm/browser/util.ts +++ b/src/vs/workbench/contrib/scm/browser/util.ts @@ -12,7 +12,7 @@ import { createAndFillInActionBarActions, createAndFillInContextMenuActions } fr import { equals } from 'vs/base/common/arrays'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; -import { renderCodiconsAsElement } from 'vs/base/browser/codicons'; +import { renderCodicons } from 'vs/base/browser/codicons'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { Command } from 'vs/editor/common/modes'; import { basename } from 'vs/base/common/resources'; @@ -105,7 +105,7 @@ export class StatusBarActionViewItem extends ActionViewItem { updateLabel(): void { if (this.options.label && this.label) { - reset(this.label, ...renderCodiconsAsElement(this.getAction().label)); + reset(this.label, ...renderCodicons(this.getAction().label)); } } } diff --git a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts index de50ffbd07..b351ac85e7 100644 --- a/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/anythingQuickAccess.ts @@ -661,7 +661,8 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider { progressReporter = p; - return new Promise(resolve => progressComplete = resolve); + return new Promise(resolve => progressComplete = resolve); }); const confirmation: IConfirmation = { @@ -1402,7 +1402,7 @@ export class SearchView extends ViewPane { private doSearch(query: ITextQuery, excludePatternText: string, includePatternText: string, triggeredOnType: boolean): Thenable { let progressComplete: () => void; this.progressService.withProgress({ location: this.getProgressLocation(), delay: triggeredOnType ? 300 : 0 }, _progress => { - return new Promise(resolve => progressComplete = resolve); + return new Promise(resolve => progressComplete = resolve); }); this.searchWidget.searchInput.clearMessage(); diff --git a/src/vs/workbench/contrib/search/common/searchModel.ts b/src/vs/workbench/contrib/search/common/searchModel.ts index b823dbf13f..f10ce7adc4 100644 --- a/src/vs/workbench/contrib/search/common/searchModel.ts +++ b/src/vs/workbench/contrib/search/common/searchModel.ts @@ -737,7 +737,7 @@ export class SearchResult extends Disposable { set query(query: ITextQuery | null) { // When updating the query we could change the roots, so keep a reference to them to clean up when we trigger `disposePastResults` const oldFolderMatches = this.folderMatches(); - new Promise(resolve => this.disposePastResults = resolve) + new Promise(resolve => this.disposePastResults = resolve) .then(() => oldFolderMatches.forEach(match => match.clear())) .then(() => oldFolderMatches.forEach(match => match.dispose())) .then(() => this._isDirty = false); diff --git a/src/vs/workbench/contrib/search/test/common/cacheState.test.ts b/src/vs/workbench/contrib/search/test/common/cacheState.test.ts index 5554aca1ac..d5bc922fda 100644 --- a/src/vs/workbench/contrib/search/test/common/cacheState.test.ts +++ b/src/vs/workbench/contrib/search/test/common/cacheState.test.ts @@ -207,7 +207,7 @@ suite('FileQueryCacheState', () => { } public awaitDisposal(n: number) { - return new Promise(resolve => { + return new Promise(resolve => { if (n === Object.keys(this.disposing).length) { resolve(); } else { diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts index 8f5f18bad7..edf623a8a3 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts @@ -160,10 +160,10 @@ export const openNewSearchEditor = args.triggerSearch === true || args.triggerSearch !== false && searchOnType && args.query ) { - editor.triggerSearch({ focusResults: args.focusResults !== false }); + editor.triggerSearch({ focusResults: args.focusResults }); } - if (args.focusResults === false) { editor.focusSearchInput(); } + if (!args.focusResults) { editor.focusSearchInput(); } }; export const createEditorFromSearchResult = diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts index 759cbb011c..ebe0f6cb6f 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts @@ -5,7 +5,6 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { Emitter, Event } from 'vs/base/common/event'; -import * as network from 'vs/base/common/network'; import { basename } from 'vs/base/common/path'; import { extname, isEqual, joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; @@ -24,7 +23,6 @@ import { Memento } from 'vs/workbench/common/memento'; import { SearchEditorFindMatchClass, SearchEditorScheme } from 'vs/workbench/contrib/searchEditor/browser/constants'; import { SearchEditorModel } from 'vs/workbench/contrib/searchEditor/browser/searchEditorModel'; import { defaultSearchConfig, extractSearchQueryFromModel, parseSavedSearchEditor, serializeSearchConfiguration } from 'vs/workbench/contrib/searchEditor/browser/searchEditorSerialization'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; @@ -82,7 +80,6 @@ export class SearchEditorInput extends EditorInput { private searchEditorModel: SearchEditorModel, @IModelService private readonly modelService: IModelService, @ITextFileService protected readonly textFileService: ITextFileService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IFileDialogService private readonly fileDialogService: IFileDialogService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, @@ -277,10 +274,7 @@ export class SearchEditorInput extends EditorInput { const searchFileName = (query.replace(/[^\w \-_]+/g, '_') || 'Search') + SEARCH_EDITOR_EXT; - const remoteAuthority = this.environmentService.configuration.remoteAuthority; - const schemeFilter = remoteAuthority ? network.Schemas.vscodeRemote : network.Schemas.file; - - return joinPath(this.fileDialogService.defaultFilePath(schemeFilter) || (await this.pathService.userHome()), searchFileName); + return joinPath(this.fileDialogService.defaultFilePath(this.pathService.defaultUriScheme) || (await this.pathService.userHome()), searchFileName); } } diff --git a/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts b/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts index 023e8f227f..c211070160 100644 --- a/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts +++ b/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts @@ -91,7 +91,7 @@ class InsertSnippetAction extends EditorAction { const clipboardService = accessor.get(IClipboardService); const quickInputService = accessor.get(IQuickInputService); - const snippet = await new Promise(async (resolve, reject) => { + const snippet = await new Promise(async (resolve, reject) => { const { lineNumber, column } = editor.getPosition(); let { snippet, name, langId } = Args.fromUser(arg); diff --git a/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts b/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts index b0df0407ec..3cd4b1b7a8 100644 --- a/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts +++ b/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts @@ -8,7 +8,7 @@ import { RawContextKey, IContextKeyService, ContextKeyExpr, IContextKey } from ' import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ISnippetsService } from './snippets.contribution'; import { getNonWhitespacePrefix } from './snippetsService'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable } from 'vs/base/common/lifecycle'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { Range } from 'vs/editor/common/core/range'; import { registerEditorContribution, EditorCommand, registerEditorCommand } from 'vs/editor/browser/editorExtensions'; @@ -53,8 +53,8 @@ export class TabCompletionController implements IEditorContribution { } dispose(): void { - dispose(this._configListener); - dispose(this._selectionListener); + this._configListener.dispose(); + this._selectionListener?.dispose(); } private _update(): void { @@ -62,7 +62,7 @@ export class TabCompletionController implements IEditorContribution { if (this._enabled !== enabled) { this._enabled = enabled; if (!this._enabled) { - dispose(this._selectionListener); + this._selectionListener?.dispose(); } else { this._selectionListener = this._editor.onDidChangeCursorSelection(e => this._updateSnippets()); if (this._editor.getModel()) { diff --git a/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts b/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts index 7b39bd0b1a..b1e0a5187a 100644 --- a/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts +++ b/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts @@ -19,12 +19,12 @@ import { Extensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common import * as themes from 'vs/workbench/common/theme'; import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { URI } from 'vs/base/common/uri'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import * as perf from 'vs/base/common/performance'; -import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { assertIsDefined } from 'vs/base/common/types'; import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron'; diff --git a/src/vs/workbench/contrib/tags/electron-browser/workspaceTagsService.ts b/src/vs/workbench/contrib/tags/electron-browser/workspaceTagsService.ts index 5705a680bd..d7bc664e83 100644 --- a/src/vs/workbench/contrib/tags/electron-browser/workspaceTagsService.ts +++ b/src/vs/workbench/contrib/tags/electron-browser/workspaceTagsService.ts @@ -6,7 +6,7 @@ import * as crypto from 'crypto'; import { IFileService, IResolveFileResult, IFileStat } from 'vs/platform/files/common/files'; import { IWorkspaceContextService, WorkbenchState, IWorkspace } from 'vs/platform/workspace/common/workspace'; -import { IWorkbenchEnvironmentService, IEnvironmentConfiguration } from 'vs/workbench/services/environment/common/environmentService'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { INotificationService, NeverShowAgainScope, INeverShowAgainOptions } from 'vs/platform/notification/common/notification'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; @@ -139,7 +139,7 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { async getTags(): Promise { if (!this._tags) { - this._tags = await this.resolveWorkspaceTags(this.environmentService.configuration, rootFiles => this.handleWorkspaceFiles(rootFiles)); + this._tags = await this.resolveWorkspaceTags(rootFiles => this.handleWorkspaceFiles(rootFiles)); } return this._tags; @@ -297,7 +297,7 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { "workspace.py.playwright" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } } */ - private resolveWorkspaceTags(configuration: IEnvironmentConfiguration, participant?: (rootFiles: string[]) => void): Promise { + private resolveWorkspaceTags(participant?: (rootFiles: string[]) => void): Promise { const tags: Tags = Object.create(null); const state = this.contextService.getWorkbenchState(); @@ -305,7 +305,7 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { tags['workspace.id'] = this.getTelemetryWorkspaceId(workspace, state); - const { filesToOpenOrCreate, filesToDiff } = configuration; + const { filesToOpenOrCreate, filesToDiff } = this.environmentService.configuration; tags['workbench.filesToOpenOrCreate'] = filesToOpenOrCreate && filesToOpenOrCreate.length || 0; tags['workbench.filesToDiff'] = filesToDiff && filesToDiff.length || 0; @@ -313,7 +313,7 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { tags['workspace.roots'] = isEmpty ? 0 : workspace.folders.length; tags['workspace.empty'] = isEmpty; - const folders = !isEmpty ? workspace.folders.map(folder => folder.uri) : this.productService.quality !== 'stable' && this.findFolders(configuration); + const folders = !isEmpty ? workspace.folders.map(folder => folder.uri) : this.productService.quality !== 'stable' && this.findFolders(); if (!folders || !folders.length || !this.fileService) { return Promise.resolve(tags); } @@ -524,12 +524,13 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { } } - private findFolders(configuration: IEnvironmentConfiguration): URI[] | undefined { - const folder = this.findFolder(configuration); + private findFolders(): URI[] | undefined { + const folder = this.findFolder(); return folder && [folder]; } - private findFolder({ filesToOpenOrCreate, filesToDiff }: IEnvironmentConfiguration): URI | undefined { + private findFolder(): URI | undefined { + const { filesToOpenOrCreate, filesToDiff } = this.environmentService.configuration; if (filesToOpenOrCreate && filesToOpenOrCreate.length) { return this.parentURI(filesToOpenOrCreate[0].fileUri); } else if (filesToDiff && filesToDiff.length) { diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index e417c993ce..30182d6e7a 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -2390,7 +2390,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } picker.dispose(); if (!selection) { - resolve(); + resolve(undefined); } resolve(selection); })); diff --git a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts index 9e6ff70dfd..94911a1bc8 100644 --- a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts +++ b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts @@ -38,7 +38,7 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut } } - private static runTasks(taskService: ITaskService, tasks: Array>) { + private static runTasks(taskService: ITaskService, tasks: Array>) { tasks.forEach(task => { if (task instanceof Promise) { task.then(promiseResult => { @@ -52,8 +52,8 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut }); } - private static findAutoTasks(taskService: ITaskService, workspaceTaskResult: Map): { tasks: Array>, taskNames: Array } { - const tasks = new Array>(); + private static findAutoTasks(taskService: ITaskService, workspaceTaskResult: Map): { tasks: Array>, taskNames: Array } { + const tasks = new Array>(); const taskNames = new Array(); if (workspaceTaskResult) { workspaceTaskResult.forEach(resultElement => { @@ -68,7 +68,7 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut if (resultElement.configurations) { forEach(resultElement.configurations.byIdentifier, (configedTask) => { if (configedTask.value.runOptions.runOn === RunOnOptions.folderOpen) { - tasks.push(new Promise(resolve => { + tasks.push(new Promise(resolve => { taskService.getTask(resultElement.workspaceFolder, configedTask.value._id, true).then(task => resolve(task)); })); if (configedTask.value._label) { diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 64faf4480e..94b6be1f41 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -1080,7 +1080,7 @@ export class TerminalTaskSystem implements ITaskSystem { } } // This must be normalized to the OS - shellLaunchConfig.cwd = isUNC(cwd) ? cwd : resources.toLocalResource(URI.from({ scheme: Schemas.file, path: cwd }), this.environmentService.configuration.remoteAuthority); + shellLaunchConfig.cwd = isUNC(cwd) ? cwd : resources.toLocalResource(URI.from({ scheme: Schemas.file, path: cwd }), this.environmentService.configuration.remoteAuthority, this.pathService.defaultUriScheme); } if (options.env) { shellLaunchConfig.env = options.env; @@ -1563,7 +1563,7 @@ export class TerminalTaskSystem implements ITaskSystem { } private async fileExists(path: string): Promise { - const uri: URI = resources.toLocalResource(URI.from({ scheme: Schemas.file, path: path }), this.environmentService.configuration.remoteAuthority); + const uri: URI = resources.toLocalResource(URI.from({ scheme: Schemas.file, path: path }), this.environmentService.configuration.remoteAuthority, this.pathService.defaultUriScheme); if (await this.fileService.exists(uri)) { return !((await this.fileService.resolve(uri)).isDirectory); } diff --git a/src/vs/workbench/contrib/tasks/common/problemCollectors.ts b/src/vs/workbench/contrib/tasks/common/problemCollectors.ts index 658a5e5b45..f8dc016269 100644 --- a/src/vs/workbench/contrib/tasks/common/problemCollectors.ts +++ b/src/vs/workbench/contrib/tasks/common/problemCollectors.ts @@ -115,7 +115,7 @@ export abstract class AbstractProblemCollector implements IDisposable { } } - protected abstract async processLineInternal(line: string): Promise; + protected abstract processLineInternal(line: string): Promise; public dispose() { this.modelListeners.dispose(); diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalExternalLinkProviderAdapter.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalExternalLinkProviderAdapter.ts index 40157f2e08..1ab0642214 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalExternalLinkProviderAdapter.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalExternalLinkProviderAdapter.ts @@ -65,7 +65,7 @@ export class TerminalExternalLinkProviderAdapter extends TerminalBaseLinkProvide }, startLine); const matchingText = lineContent.substr(link.startIndex, link.length) || ''; const activateLink = this._wrapLinkHandler((_, text) => link.activate(text)); - return this._instantiationService.createInstance(TerminalLink, bufferRange, matchingText, this._xterm.buffer.active.viewportY, activateLink, this._tooltipCallback, true, link.label); + return this._instantiationService.createInstance(TerminalLink, this._xterm, bufferRange, matchingText, this._xterm.buffer.active.viewportY, activateLink, this._tooltipCallback, true, link.label); }); } } diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLink.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLink.ts index 968041e3b8..f337ece4c0 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalLink.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLink.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { IViewportRange, IBufferRange, ILink, ILinkDecorations } from 'xterm'; +import type { IViewportRange, IBufferRange, ILink, ILinkDecorations, Terminal } from 'xterm'; import { DisposableStore } from 'vs/base/common/lifecycle'; import * as dom from 'vs/base/browser/dom'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -23,10 +23,11 @@ export class TerminalLink extends DisposableStore implements ILink { private _tooltipScheduler: RunOnceScheduler | undefined; private _hoverListeners: DisposableStore | undefined; - private readonly _onLeave = new Emitter(); - public get onLeave(): Event { return this._onLeave.event; } + private readonly _onInvalidated = new Emitter(); + public get onInvalidated(): Event { return this._onInvalidated.event; } constructor( + private readonly _xterm: Terminal, public readonly range: IBufferRange, public readonly text: string, private readonly _viewportY: number, @@ -57,17 +58,26 @@ export class TerminalLink extends DisposableStore implements ILink { hover(event: MouseEvent, text: string): void { // Listen for modifier before handing it off to the hover to handle so it gets disposed correctly - this.add(dom.addDisposableListener(document, 'keydown', e => { - if (this._isModifierDown(e)) { + this._hoverListeners = new DisposableStore(); + this._hoverListeners.add(dom.addDisposableListener(document, 'keydown', e => { + if (!e.repeat && this._isModifierDown(e)) { this._enableDecorations(); } })); - this.add(dom.addDisposableListener(document, 'keyup', e => { - if (!this._isModifierDown(e)) { + this._hoverListeners.add(dom.addDisposableListener(document, 'keyup', e => { + if (!e.repeat && !this._isModifierDown(e)) { this._disableDecorations(); } })); + // Listen for when the terminal renders on the same line as the link + this._hoverListeners.add(this._xterm.onRender(e => { + const viewportRangeY = this.range.start.y - this._viewportY; + if (viewportRangeY >= e.start && viewportRangeY <= e.end) { + this._onInvalidated.fire(); + } + })); + // Only show the tooltip and highlight for high confidence links (not word/search workspace // links). Feedback was that this makes using the terminal overly noisy. if (this._isHighConfidenceLink) { @@ -88,7 +98,6 @@ export class TerminalLink extends DisposableStore implements ILink { } const origin = { x: event.pageX, y: event.pageY }; - this._hoverListeners = new DisposableStore(); this._hoverListeners.add(dom.addDisposableListener(document, dom.EventType.MOUSE_MOVE, e => { // Update decorations if (this._isModifierDown(e)) { @@ -111,7 +120,6 @@ export class TerminalLink extends DisposableStore implements ILink { this._hoverListeners = undefined; this._tooltipScheduler?.dispose(); this._tooltipScheduler = undefined; - this._onLeave.fire(); } private _enableDecorations(): void { diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts index 1387da2b17..e43ab68b82 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkManager.ts @@ -14,7 +14,7 @@ import { ITextEditorSelection } from 'vs/platform/editor/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IFileService } from 'vs/platform/files/common/files'; import type { Terminal, IViewportRange, ILinkProvider } from 'xterm'; -import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; +import { Schemas } from 'vs/base/common/network'; import { posix, win32 } from 'vs/base/common/path'; import { ITerminalExternalLinkProvider, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { OperatingSystem, isMacintosh, OS } from 'vs/base/common/platform'; @@ -116,7 +116,7 @@ export class TerminalLinkManager extends DisposableStore { const widget = this._instantiationService.createInstance(TerminalHover, targetOptions, text, linkHandler); const attached = this._widgetManager.attachWidget(widget); if (attached) { - link?.onLeave(() => attached.dispose()); + link?.onInvalidated(() => attached.dispose()); } } } @@ -296,7 +296,7 @@ export class TerminalLinkManager extends DisposableStore { let uri: URI; if (this._processManager.remoteAuthority) { uri = URI.from({ - scheme: REMOTE_HOST_SCHEME, + scheme: Schemas.vscodeRemote, authority: this._processManager.remoteAuthority, path: linkUrl }); diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalProtocolLinkProvider.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalProtocolLinkProvider.ts index ad94205e1e..6615a9b7a5 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalProtocolLinkProvider.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalProtocolLinkProvider.ts @@ -52,7 +52,7 @@ export class TerminalProtocolLinkProvider extends TerminalBaseLinkProvider { ? (typeof link.url === 'string' ? URI.parse(link.url) : link.url) : undefined; const label = (uri?.scheme === 'file') ? OPEN_FILE_LABEL : undefined; - return this._instantiationService.createInstance(TerminalLink, range, link.url?.toString() || '', this._xterm.buffer.active.viewportY, this._activateCallback, this._tooltipCallback, true, label); + return this._instantiationService.createInstance(TerminalLink, this._xterm, range, link.url?.toString() || '', this._xterm.buffer.active.viewportY, this._activateCallback, this._tooltipCallback, true, label); }); } } diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalValidatedLocalLinkProvider.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalValidatedLocalLinkProvider.ts index 97e6c6bd93..0de235cb1e 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalValidatedLocalLinkProvider.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalValidatedLocalLinkProvider.ts @@ -20,14 +20,14 @@ const pathPrefix = '(\\.\\.?|\\~)'; const pathSeparatorClause = '\\/'; // '":; are allowed in paths but they are often separators so ignore them // Also disallow \\ to prevent a catastropic backtracking case #24798 -const excludedPathCharactersClause = '[^\\0\\s!$`&*()\\[\\]+\'":;\\\\]'; +const excludedPathCharactersClause = '[^\\0\\s!$`&*()\\[\\]\'":;\\\\]'; /** A regex that matches paths in the form /foo, ~/foo, ./foo, ../foo, foo/bar */ export const unixLocalLinkClause = '((' + pathPrefix + '|(' + excludedPathCharactersClause + ')+)?(' + pathSeparatorClause + '(' + excludedPathCharactersClause + ')+)+)'; export const winDrivePrefix = '(?:\\\\\\\\\\?\\\\)?[a-zA-Z]:'; const winPathPrefix = '(' + winDrivePrefix + '|\\.\\.?|\\~)'; const winPathSeparatorClause = '(\\\\|\\/)'; -const winExcludedPathCharactersClause = '[^\\0<>\\?\\|\\/\\s!$`&*()\\[\\]+\'":;]'; +const winExcludedPathCharactersClause = '[^\\0<>\\?\\|\\/\\s!$`&*()\\[\\]\'":;]'; /** A regex that matches paths in the form \\?\c:\foo c:\foo, ~\foo, .\foo, ..\foo, foo\bar */ export const winLocalLinkClause = '((' + winPathPrefix + '|(' + winExcludedPathCharactersClause + ')+)?(' + winPathSeparatorClause + '(' + winExcludedPathCharactersClause + ')+)+)'; @@ -144,7 +144,7 @@ export class TerminalValidatedLocalLinkProvider extends TerminalBaseLinkProvider this._activateFileCallback(event, text); } }); - r(this._instantiationService.createInstance(TerminalLink, bufferRange, link, this._xterm.buffer.active.viewportY, activateCallback, this._tooltipCallback, true, label)); + r(this._instantiationService.createInstance(TerminalLink, this._xterm, bufferRange, link, this._xterm.buffer.active.viewportY, activateCallback, this._tooltipCallback, true, label)); } else { r(undefined); } diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalWordLinkProvider.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalWordLinkProvider.ts index 08843bded1..e72662b182 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalWordLinkProvider.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalWordLinkProvider.ts @@ -53,7 +53,7 @@ export class TerminalWordLinkProvider extends TerminalBaseLinkProvider { // Add a link if this is a separator if (width !== 0 && wordSeparators.indexOf(chars) >= 0) { if (startX !== -1) { - result.push(new TerminalLink({ start: { x: startX + 1, y }, end: { x, y } }, text, this._xterm.buffer.active.viewportY, activateCallback, this._tooltipCallback, false, localize('searchWorkspace', 'Search workspace'), this._configurationService)); + result.push(this._createTerminalLink(startX, x, y, text, activateCallback)); text = ''; startX = -1; } @@ -70,12 +70,30 @@ export class TerminalWordLinkProvider extends TerminalBaseLinkProvider { // Add the final link if there is one if (startX !== -1) { - result.push(new TerminalLink({ start: { x: startX + 1, y }, end: { x: line.length, y } }, text, this._xterm.buffer.active.viewportY, activateCallback, this._tooltipCallback, false, localize('searchWorkspace', 'Search workspace'), this._configurationService)); + result.push(this._createTerminalLink(startX, line.length, y, text, activateCallback)); } return result; } + private _createTerminalLink(startX: number, endX: number, y: number, text: string, activateCallback: XtermLinkMatcherHandler): TerminalLink { + // Remove trailing colon if there is one so the link is more useful + if (text.length > 0 && text.charAt(text.length - 1) === ':') { + text = text.slice(0, -1); + endX--; + } + return this._instantiationService.createInstance(TerminalLink, + this._xterm, + { start: { x: startX + 1, y }, end: { x: endX, y } }, + text, + this._xterm.buffer.active.viewportY, + activateCallback, + this._tooltipCallback, + false, + localize('searchWorkspace', 'Search workspace') + ); + } + private async _activate(link: string) { const results = await this._searchService.fileSearch( this._fileQueryBuilder.file(this._workspaceContextService.getWorkspace().folders, { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 6f605e3ba2..5177f60892 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -652,7 +652,7 @@ export function registerTerminalActions() { const codeEditorService = accessor.get(ICodeEditorService); const instance = terminalService.getActiveOrCreateInstance(); - let editor = codeEditorService.getFocusedCodeEditor(); + let editor = codeEditorService.getActiveCodeEditor(); if (!editor || !editor.hasModel()) { return; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts index 9058c8520e..99fb4feae8 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts @@ -8,7 +8,7 @@ import * as platform from 'vs/base/common/platform'; import { EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { ITerminalConfiguration, ITerminalFont, IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, TERMINAL_CONFIG_SECTION, DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, MINIMUM_LETTER_SPACING, LinuxDistro, IShellLaunchConfig } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalConfiguration, ITerminalFont, IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, TERMINAL_CONFIG_SECTION, DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, MINIMUM_LETTER_SPACING, LinuxDistro, IShellLaunchConfig, MINIMUM_FONT_WEIGHT, MAXIMUM_FONT_WEIGHT, DEFAULT_FONT_WEIGHT, DEFAULT_BOLD_FONT_WEIGHT, FontWeight } from 'vs/workbench/contrib/terminal/common/terminal'; import Severity from 'vs/base/common/severity'; import { INotificationService, NeverShowAgainScope } from 'vs/platform/notification/common/notification'; import { IBrowserTerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminal'; @@ -67,7 +67,11 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { } private _updateConfig(): void { - this.config = this._configurationService.getValue(TERMINAL_CONFIG_SECTION); + const configValues = this._configurationService.getValue(TERMINAL_CONFIG_SECTION); + configValues.fontWeight = this._normalizeFontWeight(configValues.fontWeight, DEFAULT_FONT_WEIGHT); + configValues.fontWeightBold = this._normalizeFontWeight(configValues.fontWeightBold, DEFAULT_BOLD_FONT_WEIGHT); + + this.config = configValues; } public configFontIsMonospace(): boolean { @@ -157,7 +161,7 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { const editorConfig = this._configurationService.getValue('editor'); let fontFamily = this.config.fontFamily || editorConfig.fontFamily || EDITOR_FONT_DEFAULTS.fontFamily; - let fontSize = this._toInteger(this.config.fontSize, MINIMUM_FONT_SIZE, MAXIMUM_FONT_SIZE, EDITOR_FONT_DEFAULTS.fontSize); + let fontSize = this._clampInt(this.config.fontSize, MINIMUM_FONT_SIZE, MAXIMUM_FONT_SIZE, EDITOR_FONT_DEFAULTS.fontSize); // Work around bad font on Fedora/Ubuntu if (!this.config.fontFamily) { @@ -168,7 +172,7 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { fontFamily = '\'Ubuntu Mono\', monospace'; // Ubuntu mono is somehow smaller, so set fontSize a bit larger to get the same perceived size. - fontSize = this._toInteger(fontSize + 2, MINIMUM_FONT_SIZE, MAXIMUM_FONT_SIZE, EDITOR_FONT_DEFAULTS.fontSize); + fontSize = this._clampInt(fontSize + 2, MINIMUM_FONT_SIZE, MAXIMUM_FONT_SIZE, EDITOR_FONT_DEFAULTS.fontSize); } } @@ -271,7 +275,7 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { return !!isWorkspaceShellAllowed; } - private _toInteger(source: any, minimum: number, maximum: number, fallback: number): number { + private _clampInt(source: any, minimum: number, maximum: number, fallback: T): number | T { let r = parseInt(source, 10); if (isNaN(r)) { return fallback; @@ -340,4 +344,11 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { const extensions = await this._extensionManagementService.getInstalled(ExtensionType.User); return extensions.some(e => e.identifier.id === id); } + + private _normalizeFontWeight(input: any, defaultWeight: FontWeight): FontWeight { + if (input === 'normal' || input === 'bold') { + return input; + } + return this._clampInt(input, MINIMUM_FONT_WEIGHT, MAXIMUM_FONT_WEIGHT, defaultWeight); + } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index c9d820b20b..2659cc62ae 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -35,6 +35,7 @@ import { TerminalProcessManager } from 'vs/workbench/contrib/terminal/browser/te import type { Terminal as XTermTerminal, IBuffer, ITerminalAddon } from 'xterm'; import type { SearchAddon, ISearchOptions } from 'xterm-addon-search'; import type { Unicode11Addon } from 'xterm-addon-unicode11'; +import type { WebglAddon } from 'xterm-addon-webgl'; import { CommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/addons/commandTrackerAddon'; import { NavigationModeAddon } from 'vs/workbench/contrib/terminal/browser/addons/navigationModeAddon'; import { XTermCore } from 'vs/workbench/contrib/terminal/browser/xterm-private'; @@ -105,6 +106,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { private _widgetManager: TerminalWidgetManager = this._instantiationService.createInstance(TerminalWidgetManager); private _linkManager: TerminalLinkManager | undefined; private _environmentInfo: { widget: EnvironmentVariableInfoWidget, disposable: IDisposable } | undefined; + private _webglAddon: WebglAddon | undefined; private _commandTrackerAddon: CommandTrackerAddon | undefined; private _navigationModeAddon: INavigationMode & ITerminalAddon | undefined; @@ -496,9 +498,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._container.appendChild(this._wrapperElement); xterm.open(this._xtermElement); if (this._configHelper.config.rendererType === 'experimentalWebgl') { - this._terminalInstanceService.getXtermWebglConstructor().then(Addon => { - xterm.loadAddon(new Addon()); - }); + this._enableWebglRenderer(); } if (!xterm.element || !xterm.textarea) { @@ -767,7 +767,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { if (!this._xterm) { return; } - this._xterm.refresh(0, this._xterm.rows - 1); + this._webglAddon?.clearTextureAtlas(); + // TODO: Do canvas renderer too? } public focus(force?: boolean): void { @@ -1227,13 +1228,26 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._safeSetOption('macOptionClickForcesSelection', config.macOptionClickForcesSelection); this._safeSetOption('rightClickSelectsWord', config.rightClickBehavior === 'selectWord'); this._safeSetOption('wordSeparator', config.wordSeparators); - if (config.rendererType !== 'experimentalWebgl') { + if (config.rendererType === 'experimentalWebgl') { + this._enableWebglRenderer(); + } else { + this._webglAddon?.dispose(); + this._webglAddon = undefined; // Never set webgl as it's an addon not a rendererType this._safeSetOption('rendererType', config.rendererType === 'auto' ? 'canvas' : config.rendererType); } this._refreshEnvironmentVariableInfoWidgetState(this._processManager.environmentVariableInfo); } + private async _enableWebglRenderer(): Promise { + if (!this._xterm || this._webglAddon) { + return; + } + const Addon = await this._terminalInstanceService.getXtermWebglConstructor(); + this._webglAddon = new Addon(); + this._xterm.loadAddon(this._webglAddon); + } + private async _updateUnicodeVersion(): Promise { if (!this._xterm) { throw new Error('Cannot update unicode version before xterm has been initialized'); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts index ef20cca47a..e8194f7259 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts @@ -43,9 +43,9 @@ export class TerminalProcessExtHostProxy extends Disposable implements ITerminal private readonly _onRequestLatency = this._register(new Emitter()); public readonly onRequestLatency: Event = this._onRequestLatency.event; - private _pendingInitialCwdRequests: ((value?: string | Thenable) => void)[] = []; - private _pendingCwdRequests: ((value?: string | Thenable) => void)[] = []; - private _pendingLatencyRequests: ((value?: number | Thenable) => void)[] = []; + private _pendingInitialCwdRequests: ((value: string | PromiseLike) => void)[] = []; + private _pendingCwdRequests: ((value: string | PromiseLike) => void)[] = []; + private _pendingLatencyRequests: ((value: number | PromiseLike) => void)[] = []; constructor( public terminalId: number, diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 41c73553ba..7cc18eddfb 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -25,7 +25,6 @@ import { FindReplaceState } from 'vs/editor/contrib/find/findState'; import { escapeNonWindowsPath } from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; import { isWindows, isMacintosh, OperatingSystem, isWeb } from 'vs/base/common/platform'; import { basename } from 'vs/base/common/path'; -import { find } from 'vs/base/common/arrays'; import { timeout } from 'vs/base/common/async'; import { IViewsService, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -232,7 +231,9 @@ export class TerminalService implements ITerminalService { private _removeTab(tab: ITerminalTab): void { // Get the index of the tab and remove it from the list const index = this._terminalTabs.indexOf(tab); - const wasActiveTab = tab === this.getActiveTab(); + const activeTab = this.getActiveTab(); + const activeTabIndex = activeTab ? this._terminalTabs.indexOf(activeTab) : -1; + const wasActiveTab = tab === activeTab; if (index !== -1) { this._terminalTabs.splice(index, 1); } @@ -247,6 +248,9 @@ export class TerminalService implements ITerminalService { if (activeInstance) { activeInstance.focus(true); } + } else if (activeTabIndex >= this._terminalTabs.length) { + const newIndex = this._terminalTabs.length - 1; + this.setActiveTabByIndex(newIndex); } // Hide the panel if there are no more instances, provided that VS Code is not shutting @@ -454,7 +458,7 @@ export class TerminalService implements ITerminalService { } private _getTabForInstance(instance: ITerminalInstance): ITerminalTab | undefined { - return find(this._terminalTabs, tab => tab.terminalInstances.indexOf(instance) !== -1); + return this._terminalTabs.find(tab => tab.terminalInstances.indexOf(instance) !== -1); } public async showPanel(focus?: boolean): Promise { diff --git a/src/vs/workbench/contrib/terminal/browser/widgets/terminalHoverWidget.ts b/src/vs/workbench/contrib/terminal/browser/widgets/terminalHoverWidget.ts index 8856e7fbbc..6848d7bb8f 100644 --- a/src/vs/workbench/contrib/terminal/browser/widgets/terminalHoverWidget.ts +++ b/src/vs/workbench/contrib/terminal/browser/widgets/terminalHoverWidget.ts @@ -41,13 +41,16 @@ export class TerminalHover extends Disposable implements ITerminalWidget { attach(container: HTMLElement): void { const target = new CellHoverTarget(container, this._targetOptions); - this._hoverService.showHover({ + const hover = this._hoverService.showHover({ target, text: this._text, linkHandler: this._linkHandler, // .xterm-hover lets xterm know that the hover is part of a link additionalClasses: ['xterm-hover'] }); + if (hover) { + this._register(hover); + } } } diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 9a1fdfd43b..b24ae33aa3 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -70,7 +70,13 @@ export const DEFAULT_LETTER_SPACING = 0; export const MINIMUM_LETTER_SPACING = -5; export const DEFAULT_LINE_HEIGHT = 1; -export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900'; +export const MINIMUM_FONT_WEIGHT = 1; +export const MAXIMUM_FONT_WEIGHT = 1000; +export const DEFAULT_FONT_WEIGHT = 'normal'; +export const DEFAULT_BOLD_FONT_WEIGHT = 'bold'; +export const SUGGESTIONS_FONT_WEIGHT = ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900']; + +export type FontWeight = 'normal' | 'bold' | number; export interface ITerminalConfiguration { shell: { diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index 8996126c62..b5fc156464 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -6,7 +6,7 @@ import { IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry'; import { localize } from 'vs/nls'; import { EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions'; -import { DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle, DEFAULT_COMMANDS_TO_SKIP_SHELL } from 'vs/workbench/contrib/terminal/common/terminal'; +import { DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle, DEFAULT_COMMANDS_TO_SKIP_SHELL, SUGGESTIONS_FONT_WEIGHT, MINIMUM_FONT_WEIGHT, MAXIMUM_FONT_WEIGHT } from 'vs/workbench/contrib/terminal/common/terminal'; import { isMacintosh, isWindows, Platform } from 'vs/base/common/platform'; export const terminalConfiguration: IConfigurationNode = { @@ -136,15 +136,41 @@ export const terminalConfiguration: IConfigurationNode = { default: 1 }, 'terminal.integrated.fontWeight': { - type: 'string', - enum: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], - description: localize('terminal.integrated.fontWeight', "The font weight to use within the terminal for non-bold text."), + 'anyOf': [ + { + type: 'number', + minimum: MINIMUM_FONT_WEIGHT, + maximum: MAXIMUM_FONT_WEIGHT, + errorMessage: localize('terminal.integrated.fontWeightError', "Only \"normal\" and \"bold\" keywords or numbers between 1 and 1000 are allowed.") + }, + { + type: 'string', + pattern: '^(normal|bold|1000|[1-9][0-9]{0,2})$' + }, + { + enum: SUGGESTIONS_FONT_WEIGHT, + } + ], + description: localize('terminal.integrated.fontWeight', "The font weight to use within the terminal for non-bold text. Accepts \"normal\" and \"bold\" keywords or numbers between 1 and 1000."), default: 'normal' }, 'terminal.integrated.fontWeightBold': { - type: 'string', - enum: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], - description: localize('terminal.integrated.fontWeightBold', "The font weight to use within the terminal for bold text."), + 'anyOf': [ + { + type: 'number', + minimum: MINIMUM_FONT_WEIGHT, + maximum: MAXIMUM_FONT_WEIGHT, + errorMessage: localize('terminal.integrated.fontWeightError', "Only \"normal\" and \"bold\" keywords or numbers between 1 and 1000 are allowed.") + }, + { + type: 'string', + pattern: '^(normal|bold|1000|[1-9][0-9]{0,2})$' + }, + { + enum: SUGGESTIONS_FONT_WEIGHT, + } + ], + description: localize('terminal.integrated.fontWeightBold', "The font weight to use within the terminal for bold text. Accepts \"normal\" and \"bold\" keywords or numbers between 1 and 1000."), default: 'bold' }, 'terminal.integrated.cursorBlinking': { diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeContribution.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeContribution.ts index 645690a33f..a945c847d2 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeContribution.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalNativeContribution.ts @@ -46,11 +46,7 @@ export class TerminalNativeContribution extends Disposable implements IWorkbench } private _onOsResume(): void { - const activeTab = this._terminalService.getActiveTab(); - if (!activeTab) { - return; - } - activeTab.terminalInstances.forEach(instance => instance.forceRedraw()); + this._terminalService.terminalInstances.forEach(instance => instance.forceRedraw()); } private async _onOpenFileRequest(request: INativeOpenFileRequest): Promise { diff --git a/src/vs/workbench/contrib/terminal/node/terminalProcess.ts b/src/vs/workbench/contrib/terminal/node/terminalProcess.ts index 6637e73291..61d33fc0ba 100644 --- a/src/vs/workbench/contrib/terminal/node/terminalProcess.ts +++ b/src/vs/workbench/contrib/terminal/node/terminalProcess.ts @@ -36,6 +36,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess private _titleInterval: NodeJS.Timer | null = null; private _writeQueue: string[] = []; private _writeTimeout: NodeJS.Timeout | undefined; + private _delayedResizer: DelayedResizer | undefined; private readonly _initialCwd: string; private readonly _ptyOptions: pty.IPtyForkOptions | pty.IWindowsPtyForkOptions; @@ -80,6 +81,17 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess // This option will force conpty to not redraw the whole viewport on launch conptyInheritCursor: useConpty && !!_shellLaunchConfig.initialText }; + // Delay resizes to avoid conpty not respecting very early resize calls + if (platform.isWindows && useConpty && cols === 0 && rows === 0 && this._shellLaunchConfig.executable?.endsWith('Git\\bin\\bash.exe')) { + this._delayedResizer = new DelayedResizer(); + this._register(this._delayedResizer.onTrigger(dimensions => { + this._delayedResizer?.dispose(); + this._delayedResizer = undefined; + if (dimensions.cols && dimensions.rows) { + this.resize(dimensions.cols, dimensions.rows); + } + })); + } } public async start(): Promise { @@ -286,6 +298,14 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess if (this._ptyProcess) { cols = Math.max(cols, 1); rows = Math.max(rows, 1); + + // Delay resize if needed + if (this._delayedResizer) { + this._delayedResizer.cols = cols; + this._delayedResizer.rows = rows; + return; + } + this._logService.trace('IPty#resize', cols, rows); try { this._ptyProcess.resize(cols, rows); @@ -344,3 +364,32 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess return Promise.resolve(0); } } + +/** + * Tracks the latest resize event to be trigger at a later point. + */ +class DelayedResizer extends Disposable { + public rows: number | undefined; + public cols: number | undefined; + private _timeout: NodeJS.Timeout; + + private readonly _onTrigger = this._register(new Emitter<{ rows?: number, cols?: number }>()); + public get onTrigger(): Event<{ rows?: number, cols?: number }> { return this._onTrigger.event; } + + constructor() { + super(); + this._timeout = setTimeout(() => { + this._onTrigger.fire({ rows: this.rows, cols: this.cols }); + }, 1000); + this._register({ + dispose: () => { + clearTimeout(this._timeout); + } + }); + } + + dispose(): void { + super.dispose(); + clearTimeout(this._timeout); + } +} diff --git a/src/vs/workbench/contrib/terminal/test/browser/links/terminalValidatedLocalLinkProvider.test.ts b/src/vs/workbench/contrib/terminal/test/browser/links/terminalValidatedLocalLinkProvider.test.ts index 954aa1a0d1..96417b4651 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/links/terminalValidatedLocalLinkProvider.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/links/terminalValidatedLocalLinkProvider.test.ts @@ -19,7 +19,9 @@ const unixLinks = [ './foo', '../foo', '/foo/bar', - 'foo/bar' + '/foo/bar+more', + 'foo/bar', + 'foo/bar+more', ]; const windowsLinks = [ @@ -33,10 +35,12 @@ const windowsLinks = [ '~/foo', 'c:/foo/bar', 'c:\\foo\\bar', + 'c:\\foo\\bar+more', 'c:\\foo/bar\\baz', 'foo/bar', 'foo/bar', - 'foo\\bar' + 'foo\\bar', + 'foo\\bar+more', ]; interface LinkFormatInfo { 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 7352110c6a..ba6304967c 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 @@ -79,4 +79,13 @@ suite('Workbench - TerminalWordLinkProvider', () => { { range: [[5, 1], [7, 1]], text: 'bar' } ]); }); + + test('should remove trailing colon in the link results', async () => { + await configurationService.setUserConfiguration('terminal', { integrated: { wordSeparators: ' ' } }); + await assertLink('foo:5:6: bar:0:32:', [ + { range: [[1, 1], [7, 1]], text: 'foo:5:6' }, + { range: [[10, 1], [17, 1]], text: 'bar:0:32' } + ]); + }); + }); diff --git a/src/vs/workbench/contrib/terminal/test/common/terminalColorRegistry.test.ts b/src/vs/workbench/contrib/terminal/test/common/terminalColorRegistry.test.ts index 434fdb8c55..feaa833887 100644 --- a/src/vs/workbench/contrib/terminal/test/common/terminalColorRegistry.test.ts +++ b/src/vs/workbench/contrib/terminal/test/common/terminalColorRegistry.test.ts @@ -7,13 +7,14 @@ import * as assert from 'assert'; import { Extensions as ThemeingExtensions, IColorRegistry, ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { ansiColorIdentifiers, registerColors } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; -import { IColorTheme, ThemeType } from 'vs/platform/theme/common/themeService'; +import { IColorTheme } from 'vs/platform/theme/common/themeService'; import { Color } from 'vs/base/common/color'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; registerColors(); let themingRegistry = Registry.as(ThemeingExtensions.ColorContribution); -function getMockTheme(type: ThemeType): IColorTheme { +function getMockTheme(type: ColorScheme): IColorTheme { let theme = { selector: '', label: '', @@ -30,7 +31,7 @@ function getMockTheme(type: ThemeType): IColorTheme { suite('Workbench - TerminalColorRegistry', () => { test('hc colors', function () { - let theme = getMockTheme('hc'); + let theme = getMockTheme(ColorScheme.HIGH_CONTRAST); let colors = ansiColorIdentifiers.map(colorId => Color.Format.CSS.formatHexA(theme.getColor(colorId)!, true)); assert.deepEqual(colors, [ @@ -55,7 +56,7 @@ suite('Workbench - TerminalColorRegistry', () => { }); test('light colors', function () { - let theme = getMockTheme('light'); + let theme = getMockTheme(ColorScheme.LIGHT); let colors = ansiColorIdentifiers.map(colorId => Color.Format.CSS.formatHexA(theme.getColor(colorId)!, true)); assert.deepEqual(colors, [ @@ -80,7 +81,7 @@ suite('Workbench - TerminalColorRegistry', () => { }); test('dark colors', function () { - let theme = getMockTheme('dark'); + let theme = getMockTheme(ColorScheme.DARK); let colors = ansiColorIdentifiers.map(colorId => Color.Format.CSS.formatHexA(theme.getColor(colorId)!, true)); assert.deepEqual(colors, [ diff --git a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts index e20a6a4dd2..ed983208da 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts @@ -5,7 +5,6 @@ import { localize } from 'vs/nls'; import { Action } from 'vs/base/common/actions'; -import { firstIndex } from 'vs/base/common/arrays'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -17,7 +16,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IColorRegistry, Extensions as ColorRegistryExtensions } from 'vs/platform/theme/common/colorRegistry'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Color } from 'vs/base/common/color'; -import { LIGHT, DARK, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; import { colorThemeSchemaId } from 'vs/workbench/services/themes/common/colorThemeSchema'; import { onUnexpectedError } from 'vs/base/common/errors'; import { IQuickInputService, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; @@ -45,9 +44,9 @@ export class SelectColorThemeAction extends Action { const currentTheme = this.themeService.getColorTheme(); const picks: QuickPickInput[] = [ - ...toEntries(themes.filter(t => t.type === LIGHT), localize('themes.category.light', "light themes")), - ...toEntries(themes.filter(t => t.type === DARK), localize('themes.category.dark', "dark themes")), - ...toEntries(themes.filter(t => t.type === HIGH_CONTRAST), localize('themes.category.hc', "high contrast themes")), + ...toEntries(themes.filter(t => t.type === ColorScheme.LIGHT), localize('themes.category.light', "light themes")), + ...toEntries(themes.filter(t => t.type === ColorScheme.DARK), localize('themes.category.dark', "dark themes")), + ...toEntries(themes.filter(t => t.type === ColorScheme.HIGH_CONTRAST), localize('themes.category.hc', "high contrast themes")), // {{SQL CARBON EDIT}} // ...configurationEntries(this.extensionGalleryService, localize('installColorThemes', "Install Additional Color Themes...")) ]; @@ -74,7 +73,7 @@ export class SelectColorThemeAction extends Action { return new Promise((s, _) => { let isCompleted = false; - const autoFocusIndex = firstIndex(picks, p => isItem(p) && p.id === currentTheme.id); + const autoFocusIndex = picks.findIndex(p => isItem(p) && p.id === currentTheme.id); const quickpick = this.quickInputService.createQuickPick(); quickpick.items = picks; quickpick.placeholder = localize('themes.selectTheme', "Select Color Theme (Up/Down Keys to Preview)"); @@ -148,10 +147,10 @@ abstract class AbstractIconThemeAction extends Action { }, applyTheme ? 0 : 200); }; - return new Promise((s, _) => { + return new Promise((s, _) => { let isCompleted = false; - const autoFocusIndex = firstIndex(picks, p => isItem(p) && p.id === currentTheme.id); + const autoFocusIndex = picks.findIndex(p => isItem(p) && p.id === currentTheme.id); const quickpick = this.quickInputService.createQuickPick(); quickpick.items = picks; quickpick.placeholder = this.placeholderMessage; diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index 55d7f96b19..70aee5b13e 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -32,7 +32,7 @@ import { ITimelineService, TimelineChangeEvent, TimelineItem, TimelineOptions, T import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { SideBySideEditor, toResource } from 'vs/workbench/common/editor'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { IThemeService, LIGHT, ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IProgressService } from 'vs/platform/progress/common/progress'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -41,6 +41,7 @@ import { MenuEntryActionViewItem, createAndFillInContextMenuActions, SubmenuEntr import { MenuItemAction, IMenuService, MenuId, registerAction2, Action2, MenuRegistry, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; const ItemHeight = 22; @@ -1117,7 +1118,7 @@ class TimelineTreeRenderer implements ITreeRenderer { + return new Promise((c, e) => { const quickInputService = accessor.get(IQuickInputService); const commandService = accessor.get(ICommandService); const disposables = new DisposableStore(); diff --git a/src/vs/workbench/contrib/views/browser/treeView.ts b/src/vs/workbench/contrib/views/browser/treeView.ts index 1bf00e0897..4715641e04 100644 --- a/src/vs/workbench/contrib/views/browser/treeView.ts +++ b/src/vs/workbench/contrib/views/browser/treeView.ts @@ -24,7 +24,7 @@ import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels'; import { ActionBar } 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, ThemeIcon, IThemeService } from 'vs/platform/theme/common/themeService'; +import { FileThemeIcon, FolderThemeIcon, registerThemingParticipant, ThemeIcon, IThemeService } from 'vs/platform/theme/common/themeService'; import { FileKind } from 'vs/platform/files/common/files'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { localize } from 'vs/nls'; @@ -42,6 +42,7 @@ import { IHoverService, IHoverOptions, IHoverTarget } from 'vs/workbench/service import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { isMacintosh } from 'vs/base/common/platform'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; class Root implements ITreeItem { label = { label: 'root' }; @@ -113,6 +114,7 @@ export class TreeView extends Disposable implements ITreeView { @IKeybindingService private readonly keybindingService: IKeybindingService, @INotificationService private readonly notificationService: INotificationService, @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, + @IHoverService private readonly hoverService: IHoverService, @IContextKeyService contextKeyService: IContextKeyService ) { super(); @@ -434,6 +436,7 @@ export class TreeView extends Disposable implements ITreeView { } private onContextMenu(treeMenus: TreeMenus, treeEvent: ITreeContextMenuEvent, actionRunner: MultipleSelectionActionRunner): void { + this.hoverService.hideHover(); const node: ITreeItem | null = treeEvent.element; if (node === null) { return; @@ -754,7 +757,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer extends Disposable { @INotificationService notificationService: INotificationService, @ILogService private readonly _logService: ILogService, @ITelemetryService private readonly _telemetryService: ITelemetryService, - @IEnvironmentService private readonly _environmentService: IEnvironmentService, - @IWorkbenchEnvironmentService protected readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService ) { super(); @@ -173,8 +171,10 @@ export abstract class BaseWebview extends Disposable { if (this.element) { this.element.remove(); } - this._element = undefined; + + this._onDidDispose.fire(); + super.dispose(); } @@ -205,6 +205,9 @@ export abstract class BaseWebview extends Disposable { private readonly _onDidBlur = this._register(new Emitter()); public readonly onDidBlur = this._onDidBlur.event; + private readonly _onDidDispose = this._register(new Emitter()); + public readonly onDidDispose = this._onDidDispose.event; + public postMessage(data: any): void { this._send('message', data); } @@ -233,7 +236,7 @@ export abstract class BaseWebview extends Disposable { this._hasAlertedAboutMissingCsp = true; if (this.extension && this.extension.id) { - if (this._environmentService.isExtensionDevelopment) { + if (this.environmentService.isExtensionDevelopment) { this._onMissingCsp.fire(this.extension.id); } diff --git a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts index 27ae7d0997..ace68fa15d 100644 --- a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts +++ b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts @@ -59,12 +59,12 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewOv return !!this._webview.value?.isFocused; } - private readonly _onDispose = this._register(new Emitter()); - public onDispose = this._onDispose.event; + private readonly _onDidDispose = this._register(new Emitter()); + public onDidDispose = this._onDidDispose.event; dispose() { this.container.remove(); - this._onDispose.fire(); + this._onDidDispose.fire(); super.dispose(); } diff --git a/src/vs/workbench/contrib/webview/browser/pre/host.js b/src/vs/workbench/contrib/webview/browser/pre/host.js index 8a4d1111d6..1dd8a8af4c 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/host.js +++ b/src/vs/workbench/contrib/webview/browser/pre/host.js @@ -111,6 +111,7 @@ onMessage: hostMessaging.onMessage.bind(hostMessaging), ready: workerReady, fakeLoad: !onElectron, + onElectron: onElectron, rewriteCSP: onElectron ? (csp) => { return csp.replace(/vscode-resource:(?=(\s|;|$))/g, 'vscode-webview-resource:'); diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index ffef96f9d4..811126487b 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -13,6 +13,7 @@ * onIframeLoaded?: (iframe: HTMLIFrameElement) => void, * fakeLoad?: boolean, * rewriteCSP: (existingCSP: string, endpoint?: string) => string, + * onElectron?: boolean * }} WebviewHost */ @@ -286,8 +287,14 @@ // make sure we block the browser from dispatching it. Instead VS Code // handles these events and will dispatch a copy/paste back to the webview // if needed - if (isCopyPasteOrCut(e) || isUndoRedo(e)) { + if (isUndoRedo(e)) { e.preventDefault(); + } else if (isCopyPasteOrCut(e)) { + if (host.onElectron) { + e.preventDefault(); + } else { + return; // let the browser handle this + } } host.postMessage('did-keydown', { diff --git a/src/vs/workbench/contrib/webview/browser/themeing.ts b/src/vs/workbench/contrib/webview/browser/themeing.ts index 8edc3f5dab..f1b0c02601 100644 --- a/src/vs/workbench/contrib/webview/browser/themeing.ts +++ b/src/vs/workbench/contrib/webview/browser/themeing.ts @@ -8,9 +8,10 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import * as colorRegistry from 'vs/platform/theme/common/colorRegistry'; -import { DARK, IColorTheme, IThemeService, LIGHT } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { Emitter } from 'vs/base/common/event'; import { DEFAULT_FONT_FAMILY } from 'vs/workbench/browser/style'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; interface WebviewThemeData { readonly activeTheme: string; @@ -93,8 +94,8 @@ enum ApiThemeClassName { namespace ApiThemeClassName { export function fromTheme(theme: IColorTheme): ApiThemeClassName { switch (theme.type) { - case LIGHT: return ApiThemeClassName.light; - case DARK: return ApiThemeClassName.dark; + case ColorScheme.LIGHT: return ApiThemeClassName.light; + case ColorScheme.DARK: return ApiThemeClassName.dark; default: return ApiThemeClassName.highContrast; } } diff --git a/src/vs/workbench/contrib/webview/browser/webview.contribution.ts b/src/vs/workbench/contrib/webview/browser/webview.contribution.ts index fa4f1b2a68..858dc9af9e 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.contribution.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.contribution.ts @@ -3,67 +3,18 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { MultiCommand, RedoCommand, SelectAllCommand, ServicesAccessor, UndoCommand } from 'vs/editor/browser/editorExtensions'; +import { MultiCommand, RedoCommand, SelectAllCommand, UndoCommand } from 'vs/editor/browser/editorExtensions'; import { CopyAction, CutAction, PasteAction } from 'vs/editor/contrib/clipboard/clipboard'; -import { localize } from 'vs/nls'; -import { registerAction2 } from 'vs/platform/actions/common/actions'; -import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor'; -import { Extensions as EditorInputExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; -import { Webview, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; -import { WebviewEditorInputFactory } from 'vs/workbench/contrib/webview/browser/webviewEditorInputFactory'; -import { getActiveWebviewEditor, HideWebViewEditorFindCommand, ReloadWebviewAction, ShowWebViewEditorFindWidgetAction, WebViewEditorFindNextCommand, WebViewEditorFindPreviousCommand } from '../browser/webviewCommands'; -import { WebviewEditor } from './webviewEditor'; -import { WebviewInput } from './webviewEditorInput'; -import { IWebviewWorkbenchService, WebviewEditorService } from './webviewWorkbenchService'; - -(Registry.as(EditorExtensions.Editors)).registerEditor(EditorDescriptor.create( - WebviewEditor, - WebviewEditor.ID, - localize('webview.editor.label', "webview editor")), - [new SyncDescriptor(WebviewInput)]); - -Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory( - WebviewEditorInputFactory.ID, - WebviewEditorInputFactory); - -registerSingleton(IWebviewWorkbenchService, WebviewEditorService, true); - -registerAction2(ShowWebViewEditorFindWidgetAction); -registerAction2(HideWebViewEditorFindCommand); -registerAction2(WebViewEditorFindNextCommand); -registerAction2(WebViewEditorFindPreviousCommand); -registerAction2(ReloadWebviewAction); - - -function getInnerActiveWebview(accessor: ServicesAccessor): Webview | undefined { - const webview = getActiveWebviewEditor(accessor); - if (!webview) { - return undefined; - } - - // Make sure we are really focused on the webview - if (!['WEBVIEW', 'IFRAME'].includes(document.activeElement?.tagName ?? '')) { - return undefined; - } - - if ('getInnerWebview' in (webview as WebviewOverlay)) { - const innerWebview = (webview as WebviewOverlay).getInnerWebview(); - return innerWebview; - } - - return webview; -} +import { IWebviewService, Webview } from 'vs/workbench/contrib/webview/browser/webview'; const PRIORITY = 100; function overrideCommandForWebview(command: MultiCommand | undefined, f: (webview: Webview) => void) { command?.addImplementation(PRIORITY, accessor => { - const webview = getInnerActiveWebview(accessor); - if (webview && webview.isFocused) { + const webviewService = accessor.get(IWebviewService); + const webview = webviewService.activeWebview; + if (webview?.isFocused) { f(webview); return true; } diff --git a/src/vs/workbench/contrib/webview/browser/webview.ts b/src/vs/workbench/contrib/webview/browser/webview.ts index bf58344a03..c7d1901391 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.ts @@ -36,6 +36,8 @@ export interface WebviewIcons { export interface IWebviewService { readonly _serviceBrand: undefined; + readonly activeWebview: Webview | undefined; + createWebviewElement( id: string, options: WebviewOptions, @@ -100,6 +102,8 @@ export interface Webview extends IDisposable { readonly onDidFocus: Event; readonly onDidBlur: Event; + readonly onDidDispose: Event; + readonly onDidClickLink: Event; readonly onDidScroll: Event<{ scrollYPercentage: number }>; readonly onDidWheel: Event; @@ -142,8 +146,6 @@ export interface WebviewOverlay extends Webview { readonly container: HTMLElement; options: WebviewOptions; - readonly onDispose: Event; - claim(owner: any): void; release(owner: any): void; diff --git a/src/vs/workbench/contrib/webview/browser/webview.web.contribution.ts b/src/vs/workbench/contrib/webview/browser/webview.web.contribution.ts new file mode 100644 index 0000000000..e6f8a0ddff --- /dev/null +++ b/src/vs/workbench/contrib/webview/browser/webview.web.contribution.ts @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IWebviewService } from 'vs/workbench/contrib/webview/browser/webview'; +import { WebviewService } from './webviewService'; + +registerSingleton(IWebviewService, WebviewService, true); diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index 31030652d7..c70525d3cd 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -8,12 +8,10 @@ import { streamToBuffer } from 'vs/base/common/buffer'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { ITunnelService } from 'vs/platform/remote/common/tunnel'; import { IRequestService } from 'vs/platform/request/common/request'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -38,12 +36,11 @@ export class IFrameWebview extends BaseWebview implements Web @IFileService private readonly fileService: IFileService, @IRequestService private readonly requestService: IRequestService, @ITelemetryService telemetryService: ITelemetryService, - @IEnvironmentService environmentService: IEnvironmentService, - @IWorkbenchEnvironmentService private readonly _workbenchEnvironmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService, @ILogService logService: ILogService, ) { - super(id, options, contentOptions, extension, webviewThemeDataProvider, notificationService, logService, telemetryService, environmentService, _workbenchEnvironmentService); + super(id, options, contentOptions, extension, webviewThemeDataProvider, notificationService, logService, telemetryService, environmentService); this._portMappingManager = this._register(new WebviewPortMappingManager( () => this.extension?.location, @@ -83,7 +80,7 @@ export class IFrameWebview extends BaseWebview implements Web } private get externalEndpoint(): string { - const endpoint = this.workbenchEnvironmentService.webviewExternalEndpoint!.replace('{{uuid}}', this.id); + const endpoint = this.environmentService.webviewExternalEndpoint!.replace('{{uuid}}', this.id); if (endpoint[endpoint.length - 1] === '/') { return endpoint.slice(0, endpoint.length - 1); } @@ -142,17 +139,17 @@ export class IFrameWebview extends BaseWebview implements Web private async loadResource(requestPath: string, uri: URI) { try { - const remoteAuthority = this._workbenchEnvironmentService.configuration.remoteAuthority; + const remoteAuthority = this.environmentService.configuration.remoteAuthority; const remoteConnectionData = remoteAuthority ? this._remoteAuthorityResolverService.getConnectionData(remoteAuthority) : null; const extensionLocation = this.extension?.location; // If we are loading a file resource from a remote extension, rewrite the uri to go remote let rewriteUri: undefined | ((uri: URI) => URI); - if (extensionLocation?.scheme === REMOTE_HOST_SCHEME) { + if (extensionLocation?.scheme === Schemas.vscodeRemote) { rewriteUri = (uri) => { - if (uri.scheme === Schemas.file && extensionLocation?.scheme === REMOTE_HOST_SCHEME) { + if (uri.scheme === Schemas.file && extensionLocation?.scheme === Schemas.vscodeRemote) { return URI.from({ - scheme: REMOTE_HOST_SCHEME, + scheme: Schemas.vscodeRemote, authority: extensionLocation.authority, path: '/vscode-resource', query: JSON.stringify({ @@ -193,7 +190,7 @@ export class IFrameWebview extends BaseWebview implements Web } private async localLocalhost(origin: string) { - const authority = this._workbenchEnvironmentService.configuration.remoteAuthority; + const authority = this.environmentService.configuration.remoteAuthority; const resolveAuthority = authority ? await this._remoteAuthorityResolverService.resolveAuthority(authority) : undefined; const redirect = resolveAuthority ? await this._portMappingManager.getRedirect(resolveAuthority.authority, origin) : undefined; return this._send('did-load-localhost', { diff --git a/src/vs/workbench/contrib/webview/browser/webviewIconManager.ts b/src/vs/workbench/contrib/webview/browser/webviewIconManager.ts index 701fa98915..9fef9f4429 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewIconManager.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewIconManager.ts @@ -61,6 +61,6 @@ export class WebviewIconManager { } } } - this._styleElement.innerHTML = cssRules.join('\n'); + this._styleElement.textContent = cssRules.join('\n'); } } diff --git a/src/vs/workbench/contrib/webview/browser/webviewService.ts b/src/vs/workbench/contrib/webview/browser/webviewService.ts index ce4b148b42..b7ca02f24c 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewService.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewService.ts @@ -3,34 +3,39 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IWebviewService, WebviewContentOptions, WebviewOverlay, WebviewElement, WebviewIcons, WebviewOptions, WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview'; -import { IFrameWebview } from 'vs/workbench/contrib/webview/browser/webviewElement'; import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/browser/themeing'; +import { IWebviewService, Webview, WebviewContentOptions, WebviewElement, WebviewExtensionDescription, WebviewIcons, WebviewOptions, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; +import { IFrameWebview } from 'vs/workbench/contrib/webview/browser/webviewElement'; import { DynamicWebviewEditorOverlay } from './dynamicWebviewEditorOverlay'; import { WebviewIconManager } from './webviewIconManager'; export class WebviewService implements IWebviewService { declare readonly _serviceBrand: undefined; - private readonly _webviewThemeDataProvider: WebviewThemeDataProvider; + protected readonly _webviewThemeDataProvider: WebviewThemeDataProvider; + private readonly _iconManager: WebviewIconManager; constructor( - @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IInstantiationService protected readonly _instantiationService: IInstantiationService, ) { this._webviewThemeDataProvider = this._instantiationService.createInstance(WebviewThemeDataProvider); this._iconManager = this._instantiationService.createInstance(WebviewIconManager); } + private _activeWebview?: Webview; + public get activeWebview() { return this._activeWebview; } + createWebviewElement( id: string, options: WebviewOptions, contentOptions: WebviewContentOptions, extension: WebviewExtensionDescription | undefined, ): WebviewElement { - return this._instantiationService.createInstance(IFrameWebview, id, options, contentOptions, extension, this._webviewThemeDataProvider); + const webview = this._instantiationService.createInstance(IFrameWebview, id, options, contentOptions, extension, this._webviewThemeDataProvider); + this.addWebviewListeners(webview); + return webview; } createWebviewOverlay( @@ -39,12 +44,27 @@ export class WebviewService implements IWebviewService { contentOptions: WebviewContentOptions, extension: WebviewExtensionDescription | undefined, ): WebviewOverlay { - return this._instantiationService.createInstance(DynamicWebviewEditorOverlay, id, options, contentOptions, extension); + const webview = this._instantiationService.createInstance(DynamicWebviewEditorOverlay, id, options, contentOptions, extension); + this.addWebviewListeners(webview); + return webview; } setIcons(id: string, iconPath: WebviewIcons | undefined): void { this._iconManager.setIcons(id, iconPath); } -} -registerSingleton(IWebviewService, WebviewService, true); + protected addWebviewListeners(webview: Webview) { + webview.onDidFocus(() => { + this._activeWebview = webview; + }); + + const onBlur = () => { + if (this._activeWebview === webview) { + this._activeWebview = undefined; + } + }; + + webview.onDidBlur(onBlur); + webview.onDidDispose(onBlur); + } +} diff --git a/src/vs/workbench/contrib/webview/electron-browser/iframeWebviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/iframeWebviewElement.ts index 93d7c4a7d3..2631d5ff4d 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/iframeWebviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/iframeWebviewElement.ts @@ -6,7 +6,6 @@ import { ThrottledDelayer } from 'vs/base/common/async'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; @@ -42,15 +41,14 @@ export class ElectronIframeWebview extends IFrameWebview { @IFileService fileService: IFileService, @IRequestService requestService: IRequestService, @ITelemetryService telemetryService: ITelemetryService, - @IEnvironmentService environmentService: IEnvironmentService, - @IWorkbenchEnvironmentService _workbenchEnvironmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IRemoteAuthorityResolverService _remoteAuthorityResolverService: IRemoteAuthorityResolverService, @ILogService logService: ILogService, @IInstantiationService instantiationService: IInstantiationService, @INotificationService noficationService: INotificationService, ) { super(id, options, contentOptions, extension, webviewThemeDataProvider, - noficationService, tunnelService, fileService, requestService, telemetryService, environmentService, _workbenchEnvironmentService, _remoteAuthorityResolverService, logService); + noficationService, tunnelService, fileService, requestService, telemetryService, environmentService, _remoteAuthorityResolverService, logService); this._resourceRequestManager = this._register(instantiationService.createInstance(WebviewResourceRequestManager, id, extension, this.content.options)); } diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index a2b38e09a7..2762f38630 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -14,7 +14,6 @@ import { isMacintosh } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { createChannelSender } from 'vs/base/parts/ipc/common/ipc'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; import { ILogService } from 'vs/platform/log/common/log'; @@ -121,13 +120,12 @@ export class ElectronWebviewBasedWebview extends BaseWebview impleme @ILogService private readonly _myLogService: ILogService, @IInstantiationService instantiationService: IInstantiationService, @ITelemetryService telemetryService: ITelemetryService, - @IEnvironmentService environmentService: IEnvironmentService, - @IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IConfigurationService configurationService: IConfigurationService, @IMainProcessService mainProcessService: IMainProcessService, @INotificationService noficationService: INotificationService, ) { - super(id, options, contentOptions, extension, _webviewThemeDataProvider, noficationService, _myLogService, telemetryService, environmentService, workbenchEnvironmentService); + super(id, options, contentOptions, extension, _webviewThemeDataProvider, noficationService, _myLogService, telemetryService, environmentService); /* __GDPR__ "webview.createWebview" : { diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts index 05514a391a..266f7eb1db 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts @@ -6,24 +6,19 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { DynamicWebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay'; -import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/browser/themeing'; -import { IWebviewService, WebviewContentOptions, WebviewElement, WebviewExtensionDescription, WebviewIcons, WebviewOptions, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; -import { WebviewIconManager } from 'vs/workbench/contrib/webview/browser/webviewIconManager'; +import { WebviewContentOptions, WebviewElement, WebviewExtensionDescription, WebviewOptions, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; +import { WebviewService } from 'vs/workbench/contrib/webview/browser/webviewService'; import { ElectronIframeWebview } from 'vs/workbench/contrib/webview/electron-browser/iframeWebviewElement'; import { ElectronWebviewBasedWebview } from 'vs/workbench/contrib/webview/electron-browser/webviewElement'; -export class ElectronWebviewService implements IWebviewService { +export class ElectronWebviewService extends WebviewService { declare readonly _serviceBrand: undefined; - private readonly _webviewThemeDataProvider: WebviewThemeDataProvider; - private readonly _iconManager: WebviewIconManager; - constructor( - @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IInstantiationService instantiationService: IInstantiationService, @IConfigurationService private readonly _configService: IConfigurationService, ) { - this._webviewThemeDataProvider = this._instantiationService.createInstance(WebviewThemeDataProvider); - this._iconManager = this._instantiationService.createInstance(WebviewIconManager); + super(instantiationService); } createWebviewElement( @@ -33,7 +28,9 @@ export class ElectronWebviewService implements IWebviewService { extension: WebviewExtensionDescription | undefined, ): WebviewElement { const useIframes = this._configService.getValue('webview.experimental.useIframes'); - return this._instantiationService.createInstance(useIframes ? ElectronIframeWebview : ElectronWebviewBasedWebview, id, options, contentOptions, extension, this._webviewThemeDataProvider); + const webview = this._instantiationService.createInstance(useIframes ? ElectronIframeWebview : ElectronWebviewBasedWebview, id, options, contentOptions, extension, this._webviewThemeDataProvider); + this.addWebviewListeners(webview); + return webview; } createWebviewOverlay( @@ -42,10 +39,8 @@ export class ElectronWebviewService implements IWebviewService { contentOptions: WebviewContentOptions, extension: WebviewExtensionDescription | undefined, ): WebviewOverlay { - return this._instantiationService.createInstance(DynamicWebviewEditorOverlay, id, options, contentOptions, extension); - } - - setIcons(id: string, iconPath: WebviewIcons | undefined): void { - this._iconManager.setIcons(id, iconPath); + const webview = this._instantiationService.createInstance(DynamicWebviewEditorOverlay, id, options, contentOptions, extension); + this.addWebviewListeners(webview); + return webview; } } diff --git a/src/vs/workbench/contrib/webview/browser/webviewCommands.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewCommands.ts similarity index 96% rename from src/vs/workbench/contrib/webview/browser/webviewCommands.ts rename to src/vs/workbench/contrib/webviewPanel/browser/webviewCommands.ts index a4dcddd8da..77966cfd4c 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewCommands.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewCommands.ts @@ -10,8 +10,8 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, Webview, webviewDeveloperCategory } from 'vs/workbench/contrib/webview/browser/webview'; -import { WebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewEditor'; -import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; +import { WebviewEditor } from 'vs/workbench/contrib/webviewPanel/browser/webviewEditor'; +import { WebviewInput } from 'vs/workbench/contrib/webviewPanel/browser/webviewEditorInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; const webviewActiveContextKeyExpr = ContextKeyExpr.and(ContextKeyExpr.equals('activeEditor', WebviewEditor.ID), ContextKeyExpr.not('editorFocus') /* https://github.com/Microsoft/vscode/issues/58668 */)!; diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts similarity index 98% rename from src/vs/workbench/contrib/webview/browser/webviewEditor.ts rename to src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts index 0b2844e0c9..0be1845079 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts @@ -15,11 +15,11 @@ import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { IEditorDropService } from 'vs/workbench/services/editor/browser/editorDropService'; import { EditorInput, EditorOptions, IEditorOpenContext } from 'vs/workbench/common/editor'; import { WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; -import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; +import { WebviewInput } from 'vs/workbench/contrib/webviewPanel/browser/webviewEditorInput'; export class WebviewEditor extends EditorPane { diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInput.ts similarity index 100% rename from src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts rename to src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInput.ts diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputFactory.ts similarity index 100% rename from src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts rename to src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputFactory.ts diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts new file mode 100644 index 0000000000..1600ce80da --- /dev/null +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { registerAction2 } from 'vs/platform/actions/common/actions'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor'; +import { Extensions as EditorInputExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; +import { HideWebViewEditorFindCommand, ReloadWebviewAction, ShowWebViewEditorFindWidgetAction, WebViewEditorFindNextCommand, WebViewEditorFindPreviousCommand } from './webviewCommands'; +import { WebviewEditor } from './webviewEditor'; +import { WebviewInput } from './webviewEditorInput'; +import { WebviewEditorInputFactory } from './webviewEditorInputFactory'; +import { IWebviewWorkbenchService, WebviewEditorService } from './webviewWorkbenchService'; + +(Registry.as(EditorExtensions.Editors)).registerEditor(EditorDescriptor.create( + WebviewEditor, + WebviewEditor.ID, + localize('webview.editor.label', "webview editor")), + [new SyncDescriptor(WebviewInput)]); + +Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory( + WebviewEditorInputFactory.ID, + WebviewEditorInputFactory); + +registerSingleton(IWebviewWorkbenchService, WebviewEditorService, true); + + +registerAction2(ShowWebViewEditorFindWidgetAction); +registerAction2(HideWebViewEditorFindCommand); +registerAction2(WebViewEditorFindNextCommand); +registerAction2(WebViewEditorFindPreviousCommand); +registerAction2(ReloadWebviewAction); diff --git a/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService.ts similarity index 98% rename from src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts rename to src/vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService.ts index 0d9c3f3f0f..ebcd224e24 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService.ts @@ -13,7 +13,6 @@ import { Lazy } from 'vs/base/common/lazy'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { isEqual } from 'vs/base/common/resources'; import { EditorActivation } from 'vs/platform/editor/common/editor'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { GroupIdentifier } from 'vs/workbench/common/editor'; import { IWebviewService, WebviewContentOptions, WebviewExtensionDescription, WebviewIcons, WebviewOptions, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; @@ -312,5 +311,3 @@ export class WebviewEditorService implements IWebviewWorkbenchService { }, options, extension); } } - -registerSingleton(IWebviewWorkbenchService, WebviewEditorService, true); diff --git a/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts b/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts index 14ef590b38..79a277fb11 100644 --- a/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts +++ b/src/vs/workbench/contrib/webviewView/browser/webviewViewPane.ts @@ -21,14 +21,17 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { Memento, MementoObject } from 'vs/workbench/common/memento'; -import { IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; import { IWebviewService, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; -import { IWebviewViewService } from 'vs/workbench/contrib/webviewView/browser/webviewViewService'; +import { IWebviewViewService, WebviewView } from 'vs/workbench/contrib/webviewView/browser/webviewViewService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; declare const ResizeObserver: any; -const webviewStateKey = 'webviewState'; +const storageKeys = { + webviewState: 'webviewState', + title: 'title' +} as const; export class WebviewViewPane extends ViewPane { @@ -38,6 +41,9 @@ export class WebviewViewPane extends ViewPane { private _container?: HTMLElement; private _resizeObserver?: any; + private readonly defaultTitle: string; + private setTitle: string | undefined; + private readonly memento: Memento; private readonly viewState: MementoObject; @@ -57,12 +63,19 @@ export class WebviewViewPane extends ViewPane { @IProgressService private readonly progressService: IProgressService, @IWebviewService private readonly webviewService: IWebviewService, @IWebviewViewService private readonly webviewViewService: IWebviewViewService, + @IViewsService private readonly viewService: IViewsService, ) { super({ ...options, titleMenuId: MenuId.ViewTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + this.defaultTitle = this.title; this.memento = new Memento(`webviewView.${this.id}`, storageService); this.viewState = this.memento.getMemento(StorageScope.WORKSPACE); + const storedTitle = this.viewState[storageKeys.title]; + if (typeof storedTitle === 'string') { + this.updateTitle(storedTitle); + } + this._register(this.onDidChangeBodyVisibility(() => this.updateTreeVisibility())); this.updateTreeVisibility(); } @@ -107,8 +120,9 @@ export class WebviewViewPane extends ViewPane { public saveState() { if (this._webview) { - this.viewState[webviewStateKey] = this._webview.state; + this.viewState[storageKeys.webviewState] = this._webview.state; } + this.viewState[storageKeys.title] = this.setTitle; this.memento.saveMemento(); super.saveState(); @@ -141,7 +155,7 @@ export class WebviewViewPane extends ViewPane { const webviewId = `webviewView-${this.id.replace(/[^a-z0-9]/gi, '-')}`.toLowerCase(); const webview = this.webviewService.createWebviewOverlay(webviewId, {}, {}, undefined); - webview.state = this.viewState['webviewState']; + webview.state = this.viewState[storageKeys.webviewState]; this._webview = webview; this._register(toDisposable(() => { @@ -149,7 +163,7 @@ export class WebviewViewPane extends ViewPane { })); this._register(webview.onDidUpdateState(() => { - this.viewState[webviewStateKey] = webview.state; + this.viewState[storageKeys.webviewState] = webview.state; })); const source = this._register(new CancellationTokenSource()); @@ -158,17 +172,32 @@ export class WebviewViewPane extends ViewPane { await this.extensionService.activateByEvent(`onView:${this.id}`); let self = this; - await this.webviewViewService.resolve(this.id, { + const webviewView: WebviewView = { webview, onDidChangeVisibility: this.onDidChangeBodyVisibility, onDispose: this.onDispose, - get title() { return self.title; }, - set title(value: string) { self.updateTitle(value); } - }, source.token); + + get title(): string | undefined { return self.setTitle; }, + set title(value: string | undefined) { self.updateTitle(value); }, + + get description(): string | undefined { return self.titleDescription; }, + set description(value: string | undefined) { self.updateTitleDescription(value); }, + + show: (preserveFocus) => { + this.viewService.openView(this.id, !preserveFocus); + } + }; + + await this.webviewViewService.resolve(this.id, webviewView, source.token); }); } } + protected updateTitle(value: string | undefined) { + this.setTitle = value; + super.updateTitle(typeof value === 'string' ? value : this.defaultTitle); + } + private async withProgress(task: () => Promise): Promise { return this.progressService.withProgress({ location: this.id, delay: 500 }, task); } diff --git a/src/vs/workbench/contrib/webviewView/browser/webviewViewService.ts b/src/vs/workbench/contrib/webviewView/browser/webviewViewService.ts index 3a3acdc481..fdc7ef6787 100644 --- a/src/vs/workbench/contrib/webviewView/browser/webviewViewService.ts +++ b/src/vs/workbench/contrib/webviewView/browser/webviewViewService.ts @@ -13,11 +13,14 @@ export const IWebviewViewService = createDecorator('webview export interface WebviewView { title?: string; + description?: string; readonly webview: WebviewOverlay; readonly onDidChangeVisibility: Event; readonly onDispose: Event; + + show(preserveFocus: boolean): void; } export interface IWebviewViewResolver { diff --git a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts index cecddfc29b..50209a0143 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts @@ -79,7 +79,7 @@ export class WelcomePageContribution implements IWorkbenchContribution { .then(folder => { const files = folder.children ? folder.children.map(child => child.name) : []; - const file = arrays.find(files.sort(), file => strings.startsWith(file.toLowerCase(), 'readme')); + const file = files.sort().find(file => strings.startsWith(file.toLowerCase(), 'readme')); if (file) { return joinPath(folderUri, file); } @@ -331,7 +331,7 @@ class WelcomePage extends Disposable { const prodName = container.querySelector('.welcomePage2 .title .caption') as HTMLElement; if (prodName) { - prodName.innerHTML = this.productService.nameLong; + prodName.textContent = this.productService.nameLong; } recentlyOpened.then(({ workspaces }) => { diff --git a/src/vs/workbench/electron-browser/desktop.main.ts b/src/vs/workbench/electron-browser/desktop.main.ts index e2f73bbc60..08cc029291 100644 --- a/src/vs/workbench/electron-browser/desktop.main.ts +++ b/src/vs/workbench/electron-browser/desktop.main.ts @@ -8,7 +8,7 @@ import * as gracefulFs from 'graceful-fs'; import { zoomLevelToZoomFactor } from 'vs/platform/windows/common/windows'; import { importEntries, mark } from 'vs/base/common/performance'; import { Workbench } from 'vs/workbench/browser/workbench'; -import { NativeWindow } from 'vs/workbench/electron-browser/window'; +import { NativeWindow } from 'vs/workbench/electron-sandbox/window'; import { setZoomLevel, setZoomFactor, setFullscreen } from 'vs/base/browser/browser'; import { domContentLoaded, addDisposableListener, EventType, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -16,8 +16,8 @@ import { URI } from 'vs/base/common/uri'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWorkbenchConfiguration } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { INativeWindowConfiguration } from 'vs/platform/windows/node/window'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { ILogService } from 'vs/platform/log/common/log'; import { NativeStorageService } from 'vs/platform/storage/node/storageService'; @@ -38,7 +38,7 @@ import { FileService } from 'vs/platform/files/common/fileService'; import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider'; import { RemoteFileSystemProvider } from 'vs/workbench/services/remote/common/remoteAgentFileSystemChannel'; -import { ConfigurationCache } from 'vs/workbench/services/configuration/node/configurationCache'; +import { ConfigurationCache } from 'vs/workbench/services/configuration/electron-browser/configurationCache'; import { SignService } from 'vs/platform/sign/node/signService'; import { ISignService } from 'vs/platform/sign/common/sign'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; @@ -52,9 +52,9 @@ import { IElectronService, ElectronService } from 'vs/platform/electron/electron class DesktopMain extends Disposable { - private readonly environmentService = new NativeWorkbenchEnvironmentService(this.configuration, this.configuration.execPath); + private readonly environmentService = new NativeWorkbenchEnvironmentService(this.configuration); - constructor(private configuration: INativeWindowConfiguration) { + constructor(private configuration: INativeWorkbenchConfiguration) { super(); this.init(); @@ -206,7 +206,7 @@ class DesktopMain extends Disposable { fileService.registerProvider(Schemas.file, diskFileSystemProvider); // User Data Provider - fileService.registerProvider(Schemas.userData, new FileUserDataProvider(this.environmentService.appSettingsHome, this.environmentService.backupHome, diskFileSystemProvider, this.environmentService, logService)); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(this.environmentService.appSettingsHome, this.environmentService.configuration.backupPath ? URI.file(this.environmentService.configuration.backupPath) : undefined, diskFileSystemProvider, this.environmentService, logService)); const connection = remoteAgentService.getConnection(); if (connection) { @@ -259,8 +259,8 @@ class DesktopMain extends Disposable { // Fallback to empty workspace if we have no payload yet. if (!workspaceInitializationPayload) { let id: string; - if (this.environmentService.configuration.backupWorkspaceResource) { - id = basename(this.environmentService.configuration.backupWorkspaceResource); // we know the backupPath must be a unique path so we leverage its name as workspace ID + if (this.environmentService.backupWorkspaceHome) { + id = basename(this.environmentService.backupWorkspaceHome); // we know the backupPath must be a unique path so we leverage its name as workspace ID } else if (this.environmentService.isExtensionDevelopment) { id = 'ext-dev'; // extension development window never stores backups and is a singleton } else { @@ -319,7 +319,7 @@ class DesktopMain extends Disposable { } -export function main(configuration: INativeWindowConfiguration): Promise { +export function main(configuration: INativeWorkbenchConfiguration): Promise { const workbench = new DesktopMain(configuration); return workbench.open(); diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index 67c26c3014..ea31243549 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -9,7 +9,7 @@ import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; -import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; +import { isLinux, isMacintosh } from 'vs/base/common/platform'; import { ToggleDevToolsAction, ConfigureRuntimeArgumentsAction } from 'vs/workbench/electron-sandbox/actions/developerActions'; import { ZoomResetAction, ZoomOutAction, ZoomInAction, CloseCurrentWindowAction, SwitchWindow, QuickSwitchWindow, ReloadWindowWithExtensionsDisabledAction, NewWindowTabHandler, ShowPreviousWindowTabHandler, ShowNextWindowTabHandler, MoveWindowTabToNewWindowHandler, MergeWindowTabsHandlerHandler, ToggleWindowTabsBarHandler } from 'vs/workbench/electron-sandbox/actions/windowActions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -17,7 +17,7 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IsMacContext } from 'vs/platform/contextkey/common/contextkeys'; -import { NoEditorsVisibleContext, SingleEditorGroupsContext } from 'vs/workbench/common/editor'; +import { EditorsVisibleContext, SingleEditorGroupsContext } from 'vs/workbench/common/editor'; import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import product from 'vs/platform/product/common/product'; @@ -48,7 +48,7 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten KeybindingsRegistry.registerCommandAndKeybindingRule({ id: CloseCurrentWindowAction.ID, // close the window when the last editor is closed by reusing the same keybinding weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(NoEditorsVisibleContext, SingleEditorGroupsContext), + when: ContextKeyExpr.and(EditorsVisibleContext.toNegated(), SingleEditorGroupsContext), primary: KeyMod.CtrlCmd | KeyCode.KEY_W, handler: accessor => { const electronService = accessor.get(IElectronService); @@ -268,13 +268,6 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten 'default': false, 'description': nls.localize('closeWhenEmpty', "Controls whether closing the last editor should also close the window. This setting only applies for windows that do not show folders.") }, - 'window.autoDetectHighContrast': { - 'type': 'boolean', - 'default': true, - 'description': nls.localize('autoDetectHighContrast', "If enabled, will automatically change to high contrast theme if the OS is using a high contrast theme, and to dark theme when switching away from a high contrast theme."), - 'scope': ConfigurationScope.APPLICATION, - 'included': isWindows || isMacintosh - }, 'window.doubleClickIconToClose': { 'type': 'boolean', 'default': false, diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts index 0ff3f44ebe..d58509f7a1 100644 --- a/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/desktop.main.ts @@ -6,11 +6,13 @@ import { zoomLevelToZoomFactor } from 'vs/platform/windows/common/windows'; import { importEntries, mark } from 'vs/base/common/performance'; import { Workbench } from 'vs/workbench/browser/workbench'; +import { NativeWindow } from 'vs/workbench/electron-sandbox/window'; import { setZoomLevel, setZoomFactor, setFullscreen } from 'vs/base/browser/browser'; import { domContentLoaded, addDisposableListener, EventType, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; import { URI } from 'vs/base/common/uri'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { ILogService } from 'vs/platform/log/common/log'; import { Schemas } from 'vs/base/common/network'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -29,17 +31,14 @@ import { IProductService } from 'vs/platform/product/common/productService'; import product from 'vs/platform/product/common/product'; import { IResourceIdentityService } from 'vs/platform/resource/common/resourceIdentityService'; import { IElectronService, ElectronService } from 'vs/platform/electron/electron-sandbox/electron'; -import { SimpleConfigurationService, simpleFileSystemProvider, SimpleLogService, SimpleRemoteAgentService, SimpleRemoteAuthorityResolverService, SimpleResourceIdentityService, SimpleSignService, SimpleStorageService, SimpleWorkspaceService } from 'vs/workbench/electron-sandbox/sandbox.simpleservices'; -import { BrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; +import { SimpleConfigurationService, simpleFileSystemProvider, SimpleLogService, SimpleRemoteAgentService, SimpleRemoteAuthorityResolverService, SimpleResourceIdentityService, SimpleSignService, SimpleStorageService, SimpleWorkbenchEnvironmentService, SimpleWorkspaceService } from 'vs/workbench/electron-sandbox/sandbox.simpleservices'; +import { INativeWorkbenchConfiguration } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; class DesktopMain extends Disposable { - private readonly environmentService = new BrowserWorkbenchEnvironmentService({ - logsPath: URI.file('logs-path'), - workspaceId: '' - }); + private readonly environmentService = new SimpleWorkbenchEnvironmentService(this.configuration); - constructor(private configuration: any /*INativeWindowConfiguration*/) { + constructor(private configuration: INativeWorkbenchConfiguration) { super(); this.init(); @@ -47,14 +46,43 @@ class DesktopMain extends Disposable { private init(): void { + // Massage configuration file URIs + this.reviveUris(); + // Setup perf - importEntries(this.configuration.perfEntries); + importEntries(this.environmentService.configuration.perfEntries); // Browser config const zoomLevel = this.configuration.zoomLevel || 0; setZoomFactor(zoomLevelToZoomFactor(zoomLevel)); setZoomLevel(zoomLevel, true /* isTrusted */); - setFullscreen(!!this.configuration.fullscreen); + setFullscreen(!!this.environmentService.configuration.fullscreen); + } + + private reviveUris() { + if (this.environmentService.configuration.folderUri) { + this.environmentService.configuration.folderUri = URI.revive(this.environmentService.configuration.folderUri); + } + + if (this.environmentService.configuration.workspace) { + this.environmentService.configuration.workspace = reviveWorkspaceIdentifier(this.environmentService.configuration.workspace); + } + + const filesToWait = this.environmentService.configuration.filesToWait; + const filesToWaitPaths = filesToWait?.paths; + [filesToWaitPaths, this.environmentService.configuration.filesToOpenOrCreate, this.environmentService.configuration.filesToDiff].forEach(paths => { + if (Array.isArray(paths)) { + paths.forEach(path => { + if (path.fileUri) { + path.fileUri = URI.revive(path.fileUri); + } + }); + } + }); + + if (filesToWait) { + filesToWait.waitMarkerFileUri = URI.revive(filesToWait.waitMarkerFileUri); + } } async open(): Promise { @@ -70,7 +98,10 @@ class DesktopMain extends Disposable { this.registerListeners(workbench, services.storageService); // Startup - workbench.startup(); + const instantiationService = workbench.startup(); + + // Window + this._register(instantiationService.createInstance(NativeWindow)); // Logging services.logService.trace('workbench configuration', JSON.stringify(this.environmentService.configuration)); @@ -150,7 +181,7 @@ class DesktopMain extends Disposable { fileService.registerProvider(Schemas.file, simpleFileSystemProvider); // User Data Provider - fileService.registerProvider(Schemas.userData, new FileUserDataProvider(URI.file('user-home'), this.environmentService.backupHome, simpleFileSystemProvider, this.environmentService, logService)); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(URI.file('user-home'), undefined, simpleFileSystemProvider, this.environmentService, logService)); const connection = remoteAgentService.getConnection(); if (connection) { @@ -194,7 +225,7 @@ class DesktopMain extends Disposable { } } -export function main(configuration: any /*INativeWindowConfiguration*/): Promise { +export function main(configuration: INativeWorkbenchConfiguration): Promise { const workbench = new DesktopMain(configuration); return workbench.open(); diff --git a/src/vs/workbench/electron-sandbox/sandbox.simpleservices.ts b/src/vs/workbench/electron-sandbox/sandbox.simpleservices.ts index d18e5a74f5..c1f3daa06a 100644 --- a/src/vs/workbench/electron-sandbox/sandbox.simpleservices.ts +++ b/src/vs/workbench/electron-sandbox/sandbox.simpleservices.ts @@ -35,25 +35,19 @@ import { IKeyboardMapper } from 'vs/workbench/services/keybinding/common/keyboar import { ChordKeybinding, ResolvedKeybinding, SimpleKeybinding } from 'vs/base/common/keyCodes'; import { ScanCodeBinding } from 'vs/base/common/scanCode'; import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; -import { isWindows, OperatingSystem, OS } from 'vs/base/common/platform'; -import { IPathService } from 'vs/workbench/services/path/common/pathService'; -import { posix, win32 } from 'vs/base/common/path'; -import { IConfirmation, IConfirmationResult, IDialogOptions, IDialogService, IShowResult } from 'vs/platform/dialogs/common/dialogs'; -import Severity from 'vs/base/common/severity'; +import { isWindows, OS } from 'vs/base/common/platform'; import { IWebviewService, WebviewContentOptions, WebviewElement, WebviewExtensionDescription, WebviewIcons, WebviewOptions, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { AbstractTextFileService } from 'vs/workbench/services/textfile/browser/textFileService'; import { EnablementState, ExtensionRecommendationReason, IExtensionManagementServer, IExtensionManagementServerService, IExtensionRecommendation } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { LanguageId, TokenizationRegistry } from 'vs/editor/common/modes'; import { IGrammar, ITextMateService } from 'vs/workbench/services/textMate/common/textMateService'; -import { AccessibilitySupport, IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { ITunnelProvider, ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IManualSyncTask, IResourcePreview, ISyncResourceHandle, ISyncTask, IUserDataAutoSyncService, IUserDataSyncService, IUserDataSyncStore, IUserDataSyncStoreManagementService, SyncResource, SyncStatus, UserDataSyncStoreType } from 'vs/platform/userDataSync/common/userDataSync'; import { IUserDataSyncAccount, IUserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount'; import { AbstractTimerService, IStartupMetrics, ITimerService, Writeable } from 'vs/workbench/services/timer/browser/timerService'; -import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; -import { ISingleFolderWorkspaceIdentifier, IWorkspaceFolderCreationData, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { ITaskProvider, ITaskService, ITaskSummary, ProblemMatcherRunOptions, Task, TaskFilter, TaskTerminateResponse, WorkspaceFolderTaskResult } from 'vs/workbench/contrib/tasks/common/taskService'; import { Action } from 'vs/base/common/actions'; import { LinkedMap } from 'vs/base/common/map'; @@ -67,6 +61,97 @@ import { Color, RGBA } from 'vs/base/common/color'; import { joinPath } from 'vs/base/common/resources'; import { VSBuffer } from 'vs/base/common/buffer'; import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IIntegrityService, IntegrityTestResult } from 'vs/workbench/services/integrity/common/integrity'; +import { INativeWorkbenchConfiguration, INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; +import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; +import { IExtensionHostDebugParams } from 'vs/platform/environment/common/environment'; +import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api'; +import { Schemas } from 'vs/base/common/network'; + + +//#region Environment + +export class SimpleWorkbenchEnvironmentService implements INativeWorkbenchEnvironmentService { + + declare readonly _serviceBrand: undefined; + + constructor( + readonly configuration: INativeWorkbenchConfiguration + ) { } + + get userRoamingDataHome(): URI { return URI.file('/sandbox-user-data-dir').with({ scheme: Schemas.userData }); } + get settingsResource(): URI { return joinPath(this.userRoamingDataHome, 'settings.json'); } + get argvResource(): URI { return joinPath(this.userRoamingDataHome, 'argv.json'); } + get snippetsHome(): URI { return joinPath(this.userRoamingDataHome, 'snippets'); } + get globalStorageHome(): URI { return URI.joinPath(this.userRoamingDataHome, 'globalStorage'); } + get workspaceStorageHome(): URI { return URI.joinPath(this.userRoamingDataHome, 'workspaceStorage'); } + get keybindingsResource(): URI { return joinPath(this.userRoamingDataHome, 'keybindings.json'); } + get logFile(): URI { return joinPath(this.userRoamingDataHome, 'window.log'); } + get untitledWorkspacesHome(): URI { return joinPath(this.userRoamingDataHome, 'Workspaces'); } + get serviceMachineIdResource(): URI { return joinPath(this.userRoamingDataHome, 'machineid'); } + get userDataSyncLogResource(): URI { return joinPath(this.userRoamingDataHome, 'syncLog'); } + get userDataSyncHome(): URI { return joinPath(this.userRoamingDataHome, 'syncHome'); } + get backupWorkspaceHome(): URI { return joinPath(this.userRoamingDataHome, 'Backups', 'workspace'); } + + options?: IWorkbenchConstructionOptions | undefined; + logExtensionHostCommunication?: boolean | undefined; + extensionEnabledProposedApi?: string[] | undefined; + webviewExternalEndpoint: string = undefined!; + webviewResourceRoot: string = undefined!; + webviewCspSource: string = undefined!; + skipReleaseNotes: boolean = undefined!; + keyboardLayoutResource: URI = undefined!; + sync: 'on' | 'off' | undefined; + enableSyncByDefault: boolean = false; + debugExtensionHost: IExtensionHostDebugParams = undefined!; + isExtensionDevelopment: boolean = false; + disableExtensions: boolean | string[] = []; + extensionDevelopmentLocationURI?: URI[] | undefined; + extensionTestsLocationURI?: URI | undefined; + logsPath: string = undefined!; + logLevel?: string | undefined; + + args: NativeParsedArgs = Object.create(null); + + execPath: string = undefined!; + cliPath: string = undefined!; + appRoot: string = undefined!; + userHome: URI = undefined!; + appSettingsHome: URI = undefined!; + userDataPath: string = undefined!; + machineSettingsResource: URI = undefined!; + backupHome: string = undefined!; + backupWorkspacesPath: string = undefined!; + + log?: string | undefined; + extHostLogsPath: URI = undefined!; + + installSourcePath: string = undefined!; + + mainIPCHandle: string = undefined!; + sharedIPCHandle: string = undefined!; + + extensionsPath?: string | undefined; + extensionsDownloadPath: string = undefined!; + builtinExtensionsPath: string = undefined!; + + driverHandle?: string | undefined; + driverVerbose = false; + + crashReporterDirectory?: string | undefined; + crashReporterId?: string | undefined; + + nodeCachedDataDir?: string | undefined; + + disableUpdates = false; + sandbox = true; + verbose = false; + isBuilt = false; + disableTelemetry = false; +} + +//#endregion + //#region Workspace @@ -509,45 +594,13 @@ registerSingleton(IKeymapService, SimpleKeymapService); //#endregion -//#region Path - -class SimplePathService implements IPathService { - - declare readonly _serviceBrand: undefined; - - readonly resolvedUserHome = URI.file('user-home'); - readonly path = Promise.resolve(OS === OperatingSystem.Windows ? win32 : posix); - - async fileURI(path: string): Promise { return URI.file(path); } - async userHome(options?: { preferLocal: boolean; }): Promise { return this.resolvedUserHome; } -} - -registerSingleton(IPathService, SimplePathService); - -//#endregion - - -//#region Dialog - -class SimpleDialogService implements IDialogService { - - declare readonly _serviceBrand: undefined; - - async confirm(confirmation: IConfirmation): Promise { return { confirmed: false }; } - async show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): Promise { return { choice: 1 }; } - async about(): Promise { } -} - -registerSingleton(IDialogService, SimpleDialogService); - -//#endregion - - //#region Webview class SimpleWebviewService implements IWebviewService { declare readonly _serviceBrand: undefined; + readonly activeWebview = undefined; + createWebviewElement(id: string, options: WebviewOptions, contentOptions: WebviewContentOptions, extension: WebviewExtensionDescription | undefined): WebviewElement { throw new Error('Method not implemented.'); } createWebviewOverlay(id: string, options: WebviewOptions, contentOptions: WebviewContentOptions, extension: WebviewExtensionDescription | undefined): WebviewOverlay { throw new Error('Method not implemented.'); } setIcons(id: string, value: WebviewIcons | undefined): void { } @@ -606,25 +659,6 @@ registerSingleton(ITextMateService, SimpleTextMateService); //#endregion -//#region Accessibility - -class SimpleAccessibilityService implements IAccessibilityService { - - declare readonly _serviceBrand: undefined; - - onDidChangeScreenReaderOptimized = Event.None; - - isScreenReaderOptimized(): boolean { return false; } - async alwaysUnderlineAccessKeys(): Promise { return false; } - setAccessibilitySupport(accessibilitySupport: AccessibilitySupport): void { } - getAccessibilitySupport(): AccessibilitySupport { return AccessibilitySupport.Unknown; } -} - -registerSingleton(IAccessibilityService, SimpleAccessibilityService); - -//#endregion - - //#region Tunnel class SimpleTunnelService implements ITunnelService { @@ -758,27 +792,6 @@ registerSingleton(ITimerService, SimpleTimerService); //#endregion -//#region Workspace Editing - -class SimpleWorkspaceEditingService implements IWorkspaceEditingService { - - declare readonly _serviceBrand: undefined; - - async addFolders(folders: IWorkspaceFolderCreationData[], donotNotifyError?: boolean): Promise { } - async removeFolders(folders: URI[], donotNotifyError?: boolean): Promise { } - async updateFolders(index: number, deleteCount?: number, foldersToAdd?: IWorkspaceFolderCreationData[], donotNotifyError?: boolean): Promise { } - async enterWorkspace(path: URI): Promise { } - async createAndEnterWorkspace(folders: IWorkspaceFolderCreationData[], path?: URI): Promise { } - async saveAndEnterWorkspace(path: URI): Promise { } - async copyWorkspaceSettings(toWorkspace: IWorkspaceIdentifier): Promise { } - async pickNewWorkspacePath(): Promise { return undefined!; } -} - -registerSingleton(IWorkspaceEditingService, SimpleWorkspaceEditingService); - -//#endregion - - //#region Task class SimpleTaskService implements ITaskService { @@ -905,3 +918,19 @@ class SimpleOutputChannelModelService extends AsbtractOutputChannelModelService registerSingleton(IOutputChannelModelService, SimpleOutputChannelModelService); //#endregion + + +//#region Integrity + +class SimpleIntegrityService implements IIntegrityService { + + declare readonly _serviceBrand: undefined; + + async isPure(): Promise { + return { isPure: true, proof: [] }; + } +} + +registerSingleton(IIntegrityService, SimpleIntegrityService); + +//#endregion diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-sandbox/window.ts similarity index 98% rename from src/vs/workbench/electron-browser/window.ts rename to src/vs/workbench/electron-sandbox/window.ts index e14bcb4325..e56d8666c0 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -35,6 +35,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { coalesce } from 'vs/base/common/arrays'; @@ -59,7 +60,6 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { Event } from 'vs/base/common/event'; -import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { clearAllFontInfos } from 'vs/editor/browser/config/configuration'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { IAddressProvider, IAddress } from 'vs/platform/remote/common/remoteAgentConnection'; @@ -198,17 +198,6 @@ export class NativeWindow extends Disposable { setFullscreen(false); }); - // High Contrast Events - ipcRenderer.on('vscode:enterHighContrast', async () => { - await this.lifecycleService.when(LifecyclePhase.Ready); - this.themeService.setOSHighContrast(true); - }); - - ipcRenderer.on('vscode:leaveHighContrast', async () => { - await this.lifecycleService.when(LifecyclePhase.Ready); - this.themeService.setOSHighContrast(false); - }); - // accessibility support changed event ipcRenderer.on('vscode:accessibilitySupportChanged', (event: unknown, accessibilitySupportEnabled: boolean) => { this.accessibilityService.setAccessibilitySupport(accessibilitySupportEnabled ? AccessibilitySupport.Enabled : AccessibilitySupport.Disabled); diff --git a/src/vs/workbench/services/accessibility/electron-browser/accessibilityService.ts b/src/vs/workbench/services/accessibility/electron-sandbox/accessibilityService.ts similarity index 93% rename from src/vs/workbench/services/accessibility/electron-browser/accessibilityService.ts rename to src/vs/workbench/services/accessibility/electron-sandbox/accessibilityService.ts index dcb8fd696c..f1f0b452f8 100644 --- a/src/vs/workbench/services/accessibility/electron-browser/accessibilityService.ts +++ b/src/vs/workbench/services/accessibility/electron-sandbox/accessibilityService.ts @@ -6,6 +6,7 @@ import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { isWindows, isLinux } from 'vs/base/common/platform'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -15,8 +16,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; interface AccessibilityMetrics { enabled: boolean; @@ -74,8 +73,8 @@ registerSingleton(IAccessibilityService, NativeAccessibilityService, true); class LinuxAccessibilityContribution implements IWorkbenchContribution { constructor( @IJSONEditingService jsonEditingService: IJSONEditingService, - @IAccessibilityService accessibilityService: AccessibilityService, - @IEnvironmentService environmentService: IEnvironmentService + @IAccessibilityService accessibilityService: IAccessibilityService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService ) { const forceRendererAccessibility = () => { if (accessibilityService.isScreenReaderOptimized()) { diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index f6c986e573..ef88666c0f 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -329,7 +329,7 @@ export class AuthenticationService extends Disposable implements IAuthentication // Activate has already been called for the authentication provider, but it cannot block on registering itself // since this is sync and returns a disposable. So, wait for registration event to fire that indicates the // provider is now in the map. - await new Promise((resolve, _) => { + await new Promise((resolve, _) => { this.onDidRegisterAuthenticationProvider(e => { if (e.id === providerId) { provider = this._authenticationProviders.get(providerId); @@ -444,7 +444,12 @@ export class AuthenticationService extends Disposable implements IAuthentication const didRegister: Promise = new Promise((resolve, _) => { this.onDidRegisterAuthenticationProvider(e => { if (e.id === providerId) { - resolve(this._authenticationProviders.get(providerId)); + provider = this._authenticationProviders.get(providerId); + if (provider) { + resolve(provider); + } else { + throw new Error(`No authentication provider '${providerId}' is currently registered.`); + } } }); }); diff --git a/src/vs/workbench/services/backup/common/backupFileService.ts b/src/vs/workbench/services/backup/common/backupFileService.ts index b6e5e69dc9..d276623099 100644 --- a/src/vs/workbench/services/backup/common/backupFileService.ts +++ b/src/vs/workbench/services/backup/common/backupFileService.ts @@ -128,7 +128,7 @@ export class BackupFileService implements IBackupFileService { } private initialize(): BackupFileServiceImpl | InMemoryBackupFileService { - const backupWorkspaceResource = this.environmentService.configuration.backupWorkspaceResource; + const backupWorkspaceResource = this.environmentService.backupWorkspaceHome; if (backupWorkspaceResource) { return new BackupFileServiceImpl(backupWorkspaceResource, this.hashPath, this.fileService, this.logService); } @@ -140,7 +140,7 @@ export class BackupFileService implements IBackupFileService { // Re-init implementation (unless we are running in-memory) if (this.impl instanceof BackupFileServiceImpl) { - const backupWorkspaceResource = this.environmentService.configuration.backupWorkspaceResource; + const backupWorkspaceResource = this.environmentService.backupWorkspaceHome; if (backupWorkspaceResource) { this.impl.initialize(backupWorkspaceResource); } else { diff --git a/src/vs/workbench/services/backup/electron-browser/backup.ts b/src/vs/workbench/services/backup/electron-browser/backup.ts deleted file mode 100644 index c0d4525486..0000000000 --- a/src/vs/workbench/services/backup/electron-browser/backup.ts +++ /dev/null @@ -1,12 +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 { URI } from 'vs/base/common/uri'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; -import { joinPath, relativePath } from 'vs/base/common/resources'; - -export function toBackupWorkspaceResource(backupWorkspacePath: string, environmentService: INativeEnvironmentService): URI { - return joinPath(environmentService.userRoamingDataHome, relativePath(URI.file(environmentService.userDataPath), URI.file(backupWorkspacePath))!); -} diff --git a/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts b/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts index cda6964a8d..3f94b17152 100644 --- a/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts +++ b/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts @@ -24,13 +24,11 @@ import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environ import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; import { IFileService } from 'vs/platform/files/common/files'; import { hashPath, BackupFileService } from 'vs/workbench/services/backup/node/backupFileService'; -import { BACKUPS } from 'vs/platform/environment/common/environment'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; import { VSBuffer } from 'vs/base/common/buffer'; -import { TestWindowConfiguration } from 'vs/workbench/test/electron-browser/workbenchTestServices'; +import { TestWorkbenchConfiguration } from 'vs/workbench/test/electron-browser/workbenchTestServices'; const userdataDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'backupfileservice'); -const appSettingsHome = path.join(userdataDir, 'User'); const backupHome = path.join(userdataDir, 'Backups'); const workspacesJsonPath = path.join(backupHome, 'workspaces.json'); @@ -46,10 +44,10 @@ const fooBackupPath = path.join(workspaceBackupPath, 'file', hashPath(fooFile)); const barBackupPath = path.join(workspaceBackupPath, 'file', hashPath(barFile)); const untitledBackupPath = path.join(workspaceBackupPath, 'untitled', hashPath(untitledFile)); -class TestBackupEnvironmentService extends NativeWorkbenchEnvironmentService { +class TestWorkbenchEnvironmentService extends NativeWorkbenchEnvironmentService { constructor(backupPath: string) { - super({ ...TestWindowConfiguration, backupPath, 'user-data-dir': userdataDir }, TestWindowConfiguration.execPath); + super({ ...TestWorkbenchConfiguration, backupPath, 'user-data-dir': userdataDir }); } } @@ -62,12 +60,12 @@ export class NodeTestBackupFileService extends BackupFileService { discardedBackups: URI[]; constructor(workspaceBackupPath: string) { - const environmentService = new TestBackupEnvironmentService(workspaceBackupPath); + const environmentService = new TestWorkbenchEnvironmentService(workspaceBackupPath); const logService = new NullLogService(); const fileService = new FileService(logService); const diskFileSystemProvider = new DiskFileSystemProvider(logService); fileService.registerProvider(Schemas.file, diskFileSystemProvider); - fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, diskFileSystemProvider, environmentService, logService)); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, URI.file(workspaceBackupPath), diskFileSystemProvider, environmentService, logService)); super(environmentService, fileService, logService); @@ -159,7 +157,7 @@ suite('BackupFileService', () => { const backupResource = fooFile; const workspaceHash = hashPath(workspaceResource); const filePathHash = hashPath(backupResource); - const expectedPath = URI.file(path.join(appSettingsHome, BACKUPS, workspaceHash, Schemas.file, filePathHash)).with({ scheme: Schemas.userData }).toString(); + const expectedPath = URI.file(path.join(backupHome, workspaceHash, Schemas.file, filePathHash)).with({ scheme: Schemas.userData }).toString(); assert.equal(service.toBackupResource(backupResource).toString(), expectedPath); }); @@ -168,7 +166,7 @@ suite('BackupFileService', () => { const backupResource = URI.from({ scheme: Schemas.untitled, path: 'Untitled-1' }); const workspaceHash = hashPath(workspaceResource); const filePathHash = hashPath(backupResource); - const expectedPath = URI.file(path.join(appSettingsHome, BACKUPS, workspaceHash, Schemas.untitled, filePathHash)).with({ scheme: Schemas.userData }).toString(); + const expectedPath = URI.file(path.join(backupHome, workspaceHash, Schemas.untitled, filePathHash)).with({ scheme: Schemas.userData }).toString(); assert.equal(service.toBackupResource(backupResource).toString(), expectedPath); }); }); diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 60512ea8a0..f6860f2b5e 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -403,7 +403,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic if (!this.localUserConfiguration.hasTasksLoaded) { // Reload local user configuration again to load user tasks - runWhenIdle(() => this.reloadLocalUserConfiguration().then(configurationModel => this.onLocalUserConfigurationChanged(configurationModel)), 5000); + runWhenIdle(() => this.reloadLocalUserConfiguration(), 5000); } }); } @@ -436,16 +436,27 @@ export class WorkspaceService extends Disposable implements IConfigurationServic .then(([local, remote]) => ({ local, remote })); } - private reloadUserConfiguration(key?: string): Promise<{ local: ConfigurationModel, remote: ConfigurationModel }> { - return Promise.all([this.reloadLocalUserConfiguration(), this.reloadRemoeUserConfiguration()]).then(([local, remote]) => ({ local, remote })); + private reloadUserConfiguration(): Promise<{ local: ConfigurationModel, remote: ConfigurationModel }> { + return Promise.all([this.reloadLocalUserConfiguration(true), this.reloadRemoteUserConfiguration(true)]).then(([local, remote]) => ({ local, remote })); } - private reloadLocalUserConfiguration(key?: string): Promise { - return this.localUserConfiguration.reload(); + async reloadLocalUserConfiguration(donotTrigger?: boolean): Promise { + const model = await this.localUserConfiguration.reload(); + if (!donotTrigger) { + this.onLocalUserConfigurationChanged(model); + } + return model; } - private reloadRemoeUserConfiguration(key?: string): Promise { - return this.remoteUserConfiguration ? this.remoteUserConfiguration.reload() : Promise.resolve(new ConfigurationModel()); + private async reloadRemoteUserConfiguration(donotTrigger?: boolean): Promise { + if (this.remoteUserConfiguration) { + const model = await this.remoteUserConfiguration.reload(); + if (!donotTrigger) { + this.onRemoteUserConfigurationChanged(model); + } + return model; + } + return new ConfigurationModel(); } private reloadWorkspaceConfiguration(key?: string): Promise { @@ -667,9 +678,9 @@ export class WorkspaceService extends Disposable implements IConfigurationServic .then(() => { switch (editableConfigurationTarget) { case EditableConfigurationTarget.USER_LOCAL: - return this.reloadLocalUserConfiguration().then(local => this.onLocalUserConfigurationChanged(local)); + return this.reloadLocalUserConfiguration().then(() => undefined); case EditableConfigurationTarget.USER_REMOTE: - return this.reloadRemoeUserConfiguration().then(remote => this.onRemoteUserConfigurationChanged(remote)); + return this.reloadRemoteUserConfiguration().then(() => undefined); case EditableConfigurationTarget.WORKSPACE: return this.reloadWorkspaceConfiguration(); case EditableConfigurationTarget.WORKSPACE_FOLDER: diff --git a/src/vs/workbench/services/configuration/common/configurationEditingService.ts b/src/vs/workbench/services/configuration/common/configurationEditingService.ts index 176af8147a..eeb6e46d58 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditingService.ts @@ -7,7 +7,6 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import * as resources from 'vs/base/common/resources'; import * as json from 'vs/base/common/json'; -import * as strings from 'vs/base/common/strings'; import { setProperty } from 'vs/base/common/jsonEdit'; import { Queue } from 'vs/base/common/async'; import { Edit } from 'vs/base/common/jsonFormatter'; @@ -426,7 +425,7 @@ export class ConfigurationEditingService { // Without jsonPath, the entire configuration file is being replaced, so we just use JSON.stringify if (!jsonPath.length) { - const content = JSON.stringify(value, null, insertSpaces ? strings.repeat(' ', tabSize) : '\t'); + const content = JSON.stringify(value, null, insertSpaces ? ' '.repeat(tabSize) : '\t'); return [{ content, length: model.getValue().length, diff --git a/src/vs/workbench/services/configuration/common/jsonEditingService.ts b/src/vs/workbench/services/configuration/common/jsonEditingService.ts index 146bf36028..2154fbdf3f 100644 --- a/src/vs/workbench/services/configuration/common/jsonEditingService.ts +++ b/src/vs/workbench/services/configuration/common/jsonEditingService.ts @@ -6,7 +6,6 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import * as json from 'vs/base/common/json'; -import * as strings from 'vs/base/common/strings'; import { setProperty } from 'vs/base/common/jsonEdit'; import { Queue } from 'vs/base/common/async'; import { Edit } from 'vs/base/common/jsonFormatter'; @@ -79,7 +78,7 @@ export class JSONEditingService implements IJSONEditingService { // With empty path the entire file is being replaced, so we just use JSON.stringify if (!path.length) { - const content = JSON.stringify(value, null, insertSpaces ? strings.repeat(' ', tabSize) : '\t'); + const content = JSON.stringify(value, null, insertSpaces ? ' '.repeat(tabSize) : '\t'); return [{ content, length: content.length, diff --git a/src/vs/workbench/services/configuration/node/configurationCache.ts b/src/vs/workbench/services/configuration/electron-browser/configurationCache.ts similarity index 90% rename from src/vs/workbench/services/configuration/node/configurationCache.ts rename to src/vs/workbench/services/configuration/electron-browser/configurationCache.ts index a8ad232ceb..5df97c9691 100644 --- a/src/vs/workbench/services/configuration/node/configurationCache.ts +++ b/src/vs/workbench/services/configuration/electron-browser/configurationCache.ts @@ -5,14 +5,14 @@ import * as pfs from 'vs/base/node/pfs'; import { join } from 'vs/base/common/path'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { IConfigurationCache, ConfigurationKey } from 'vs/workbench/services/configuration/common/configuration'; export class ConfigurationCache implements IConfigurationCache { private readonly cachedConfigurations: Map = new Map(); - constructor(private readonly environmentService: INativeEnvironmentService) { + constructor(private readonly environmentService: INativeWorkbenchEnvironmentService) { } read(key: ConfigurationKey): Promise { @@ -47,7 +47,7 @@ class CachedConfiguration { constructor( { type, key }: ConfigurationKey, - environmentService: INativeEnvironmentService + environmentService: INativeWorkbenchEnvironmentService ) { this.cachedConfigurationFolderPath = join(environmentService.userDataPath, 'CachedConfigurations', type, key); this.cachedConfigurationFilePath = join(this.cachedConfigurationFolderPath, type === 'workspaces' ? 'workspace.json' : 'configuration.json'); diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts index d9bd665578..fea8446e19 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts @@ -13,7 +13,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; -import { TestWindowConfiguration, TestTextFileService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; +import { TestWorkbenchConfiguration, TestTextFileService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import * as uuid from 'vs/base/common/uuid'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; @@ -37,15 +37,15 @@ import { NullLogService } from 'vs/platform/log/common/log'; import { Schemas } from 'vs/base/common/network'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; import { IFileService } from 'vs/platform/files/common/files'; -import { ConfigurationCache } from 'vs/workbench/services/configuration/node/configurationCache'; +import { ConfigurationCache } from 'vs/workbench/services/configuration/electron-browser/configurationCache'; import { KeybindingsEditingService, IKeybindingEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing'; import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; -class TestEnvironmentService extends NativeWorkbenchEnvironmentService { +class TestWorkbenchEnvironmentService extends NativeWorkbenchEnvironmentService { constructor(private _appSettingsHome: URI) { - super(TestWindowConfiguration, TestWindowConfiguration.execPath); + super(TestWorkbenchConfiguration); } get appSettingsHome() { return this._appSettingsHome; } @@ -104,13 +104,13 @@ suite('ConfigurationEditingService', () => { clearServices(); instantiationService = workbenchInstantiationService(); - const environmentService = new TestEnvironmentService(URI.file(workspaceDir)); + const environmentService = new TestWorkbenchEnvironmentService(URI.file(workspaceDir)); instantiationService.stub(IEnvironmentService, environmentService); const remoteAgentService = instantiationService.createInstance(RemoteAgentService); const fileService = new FileService(new NullLogService()); const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); fileService.registerProvider(Schemas.file, diskFileSystemProvider); - fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, diskFileSystemProvider, environmentService, new NullLogService())); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, undefined, diskFileSystemProvider, environmentService, new NullLogService())); instantiationService.stub(IFileService, fileService); instantiationService.stub(IRemoteAgentService, remoteAgentService); const workspaceService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService); diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts index 8dfd84b173..78af48301c 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts @@ -21,7 +21,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace'; import { ConfigurationTarget, IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { workbenchInstantiationService, RemoteFileSystemProvider } from 'vs/workbench/test/browser/workbenchTestServices'; -import { TestWindowConfiguration, TestTextFileService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; +import { TestWorkbenchConfiguration, TestTextFileService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; @@ -38,7 +38,7 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA import { FileService } from 'vs/platform/files/common/fileService'; import { NullLogService } from 'vs/platform/log/common/log'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; -import { ConfigurationCache } from 'vs/workbench/services/configuration/node/configurationCache'; +import { ConfigurationCache } from 'vs/workbench/services/configuration/electron-browser/configurationCache'; import { ConfigurationCache as BrowserConfigurationCache } from 'vs/workbench/services/configuration/browser/configurationCache'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { IConfigurationCache } from 'vs/workbench/services/configuration/common/configuration'; @@ -53,10 +53,10 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import product from 'vs/platform/product/common/product'; import { BrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; -class TestEnvironmentService extends NativeWorkbenchEnvironmentService { +class TestWorkbenchEnvironmentService extends NativeWorkbenchEnvironmentService { constructor(private _appSettingsHome: URI) { - super(TestWindowConfiguration, TestWindowConfiguration.execPath); + super(TestWorkbenchConfiguration); } get appSettingsHome() { return this._appSettingsHome; } @@ -109,11 +109,11 @@ suite.skip('WorkspaceContextService - Folder', () => { // {{SQL CARBON EDIT}} sk .then(({ parentDir, folderDir }) => { parentResource = parentDir; workspaceResource = folderDir; - const environmentService = new TestEnvironmentService(URI.file(parentDir)); + const environmentService = new TestWorkbenchEnvironmentService(URI.file(parentDir)); const fileService = new FileService(new NullLogService()); const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); fileService.registerProvider(Schemas.file, diskFileSystemProvider); - fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, new DiskFileSystemProvider(new NullLogService()), environmentService, new NullLogService())); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, undefined, new DiskFileSystemProvider(new NullLogService()), environmentService, new NullLogService())); workspaceContextService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, new RemoteAgentService(environmentService, { _serviceBrand: undefined, ...product }, new RemoteAuthorityResolverService(), new SignService(undefined), new NullLogService())); return (workspaceContextService).initialize(convertToWorkspacePayload(URI.file(folderDir))); }); @@ -173,13 +173,13 @@ suite.skip('WorkspaceContextService - Workspace', () => { // {{SQL CARBON EDIT}} parentResource = parentDir; instantiationService = workbenchInstantiationService(); - const environmentService = new TestEnvironmentService(URI.file(parentDir)); + const environmentService = new TestWorkbenchEnvironmentService(URI.file(parentDir)); const remoteAgentService = instantiationService.createInstance(RemoteAgentService); instantiationService.stub(IRemoteAgentService, remoteAgentService); const fileService = new FileService(new NullLogService()); const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); fileService.registerProvider(Schemas.file, diskFileSystemProvider); - fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, diskFileSystemProvider, environmentService, new NullLogService())); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, undefined, diskFileSystemProvider, environmentService, new NullLogService())); const workspaceService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, workspaceService); @@ -233,13 +233,13 @@ suite.skip('WorkspaceContextService - Workspace Editing', () => { // {{SQL CARBO parentResource = parentDir; instantiationService = workbenchInstantiationService(); - const environmentService = new TestEnvironmentService(URI.file(parentDir)); + const environmentService = new TestWorkbenchEnvironmentService(URI.file(parentDir)); const remoteAgentService = instantiationService.createInstance(RemoteAgentService); instantiationService.stub(IRemoteAgentService, remoteAgentService); const fileService = new FileService(new NullLogService()); const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); fileService.registerProvider(Schemas.file, diskFileSystemProvider); - fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, diskFileSystemProvider, environmentService, new NullLogService())); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, undefined, diskFileSystemProvider, environmentService, new NullLogService())); const workspaceService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, workspaceService); @@ -494,13 +494,13 @@ suite.skip('WorkspaceService - Initialization', () => { // {{SQL CARBON EDIT}} s globalSettingsFile = path.join(parentDir, 'settings.json'); const instantiationService = workbenchInstantiationService(); - const environmentService = new TestEnvironmentService(URI.file(parentDir)); + const environmentService = new TestWorkbenchEnvironmentService(URI.file(parentDir)); const remoteAgentService = instantiationService.createInstance(RemoteAgentService); instantiationService.stub(IRemoteAgentService, remoteAgentService); const fileService = new FileService(new NullLogService()); const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); fileService.registerProvider(Schemas.file, diskFileSystemProvider); - fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, diskFileSystemProvider, environmentService, new NullLogService())); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, undefined, diskFileSystemProvider, environmentService, new NullLogService())); const workspaceService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, workspaceService); instantiationService.stub(IConfigurationService, workspaceService); @@ -771,13 +771,13 @@ suite.skip('WorkspaceConfigurationService - Folder', () => { // {{SQL CARBON EDI globalTasksFile = path.join(parentDir, 'tasks.json'); const instantiationService = workbenchInstantiationService(); - const environmentService = new TestEnvironmentService(URI.file(parentDir)); + const environmentService = new TestWorkbenchEnvironmentService(URI.file(parentDir)); const remoteAgentService = instantiationService.createInstance(RemoteAgentService); instantiationService.stub(IRemoteAgentService, remoteAgentService); fileService = new FileService(new NullLogService()); const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); fileService.registerProvider(Schemas.file, diskFileSystemProvider); - fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, diskFileSystemProvider, environmentService, new NullLogService())); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, undefined, diskFileSystemProvider, environmentService, new NullLogService())); workspaceService = disposableStore.add(new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService)); instantiationService.stub(IWorkspaceContextService, workspaceService); instantiationService.stub(IConfigurationService, workspaceService); @@ -1191,14 +1191,14 @@ suite.skip('WorkspaceConfigurationService - Folder', () => { // {{SQL CARBON EDI test('change event when there are global tasks', () => { fs.writeFileSync(globalTasksFile, '{ "version": "1.0.0", "tasks": [{ "taskName": "myTask" }'); - return new Promise((c) => testObject.onDidChangeConfiguration(() => c())); + return new Promise((c) => testObject.onDidChangeConfiguration(() => c())); }); test('creating workspace settings', async () => { fs.writeFileSync(globalSettingsFile, '{ "configurationService.folder.testSetting": "userValue" }'); await testObject.reloadConfiguration(); const workspaceSettingsResource = URI.file(path.join(workspaceDir, '.vscode', 'settings.json')); - await new Promise(async (c) => { + await new Promise(async (c) => { const disposable = testObject.onDidChangeConfiguration(e => { assert.ok(e.affectsConfiguration('configurationService.folder.testSetting')); assert.equal(testObject.getValue('configurationService.folder.testSetting'), 'workspaceValue'); @@ -1217,7 +1217,7 @@ suite.skip('WorkspaceConfigurationService - Folder', () => { // {{SQL CARBON EDI const workspaceSettingsResource = URI.file(path.join(workspaceDir, '.vscode', 'settings.json')); await fileService.writeFile(workspaceSettingsResource, VSBuffer.fromString('{ "configurationService.folder.testSetting": "workspaceValue" }')); await testObject.reloadConfiguration(); - await new Promise(async (c) => { + await new Promise(async (c) => { const disposable = testObject.onDidChangeConfiguration(e => { assert.ok(e.affectsConfiguration('configurationService.folder.testSetting')); assert.equal(testObject.getValue('configurationService.folder.testSetting'), 'userValue'); @@ -1280,13 +1280,13 @@ suite.skip('WorkspaceConfigurationService-Multiroot', () => { // {{SQL CARBON ED globalSettingsFile = path.join(parentDir, 'settings.json'); const instantiationService = workbenchInstantiationService(); - const environmentService = new TestEnvironmentService(URI.file(parentDir)); + const environmentService = new TestWorkbenchEnvironmentService(URI.file(parentDir)); const remoteAgentService = instantiationService.createInstance(RemoteAgentService); instantiationService.stub(IRemoteAgentService, remoteAgentService); const fileService = new FileService(new NullLogService()); const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); fileService.registerProvider(Schemas.file, diskFileSystemProvider); - fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, diskFileSystemProvider, environmentService, new NullLogService())); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, undefined, diskFileSystemProvider, environmentService, new NullLogService())); const workspaceService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, workspaceService); @@ -1822,7 +1822,7 @@ suite.skip('WorkspaceConfigurationService-Multiroot', () => { // {{SQL CARBON ED await workspaceService.removeFolders([uri]); fs.writeFileSync(path.join(uri.fsPath, '.vscode', 'settings.json'), '{ "configurationService.workspace.testResourceSetting": "workspaceFolderValue" }'); - return new Promise((c, e) => { + return new Promise((c, e) => { testObject.onDidChangeConfiguration(() => { try { assert.equal(testObject.getValue('configurationService.workspace.testResourceSetting', { resource: uri }), 'workspaceFolderValue'); @@ -1883,12 +1883,12 @@ suite('WorkspaceConfigurationService - Remote Folder', () => { remoteSettingsResource = URI.file(remoteSettingsFile).with({ scheme: Schemas.vscodeRemote, authority: remoteAuthority }); instantiationService = workbenchInstantiationService(); - const environmentService = new TestEnvironmentService(URI.file(parentDir)); + const environmentService = new TestWorkbenchEnvironmentService(URI.file(parentDir)); const remoteEnvironmentPromise = new Promise>(c => resolveRemoteEnvironment = () => c({ settingsPath: remoteSettingsResource })); const remoteAgentService = instantiationService.stub(IRemoteAgentService, >{ getEnvironment: () => remoteEnvironmentPromise }); const fileService = new FileService(new NullLogService()); fileService.registerProvider(Schemas.file, diskFileSystemProvider); - fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, diskFileSystemProvider, environmentService, new NullLogService())); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, undefined, diskFileSystemProvider, environmentService, new NullLogService())); const configurationCache: IConfigurationCache = { read: () => Promise.resolve(''), write: () => Promise.resolve(), remove: () => Promise.resolve() }; testObject = new WorkspaceService({ configurationCache, remoteAuthority }, environmentService, fileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, testObject); @@ -1948,7 +1948,7 @@ suite('WorkspaceConfigurationService - Remote Folder', () => { fs.writeFileSync(remoteSettingsFile, '{ "configurationService.remote.machineSetting": "remoteValue" }'); registerRemoteFileSystemProvider(); await initialize(); - const promise = new Promise((c, e) => { + const promise = new Promise((c, e) => { testObject.onDidChangeConfiguration(event => { try { assert.equal(event.source, ConfigurationTarget.USER); @@ -1968,7 +1968,7 @@ suite('WorkspaceConfigurationService - Remote Folder', () => { fs.writeFileSync(remoteSettingsFile, '{ "configurationService.remote.machineSetting": "remoteValue" }'); registerRemoteFileSystemProviderOnActivation(); await initialize(); - const promise = new Promise((c, e) => { + const promise = new Promise((c, e) => { testObject.onDidChangeConfiguration(event => { try { assert.equal(event.source, ConfigurationTarget.USER); @@ -1989,7 +1989,7 @@ suite('WorkspaceConfigurationService - Remote Folder', () => { resolveRemoteEnvironment(); await initialize(); assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'isSet'); - const promise = new Promise((c, e) => { + const promise = new Promise((c, e) => { testObject.onDidChangeConfiguration(event => { try { assert.equal(event.source, ConfigurationTarget.USER); diff --git a/src/vs/workbench/services/configurationResolver/electron-browser/configurationResolverService.ts b/src/vs/workbench/services/configurationResolver/electron-sandbox/configurationResolverService.ts similarity index 94% rename from src/vs/workbench/services/configurationResolver/electron-browser/configurationResolverService.ts rename to src/vs/workbench/services/configurationResolver/electron-sandbox/configurationResolverService.ts index 24c50fc23b..7c743db49a 100644 --- a/src/vs/workbench/services/configurationResolver/electron-browser/configurationResolverService.ts +++ b/src/vs/workbench/services/configurationResolver/electron-sandbox/configurationResolverService.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -13,7 +14,7 @@ import { IConfigurationResolverService } from 'vs/workbench/services/configurati import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IProcessEnvironment } from 'vs/base/common/platform'; import { BaseConfigurationResolverService } from 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; -import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; +import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; export class ConfigurationResolverService extends BaseConfigurationResolverService { 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 94da9fc5d2..e7f271466c 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 @@ -13,7 +13,7 @@ import { IConfigurationResolverService } from 'vs/workbench/services/configurati import { BaseConfigurationResolverService } from 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; 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 { TestWorkbenchConfiguration } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { Disposable } from 'vs/base/common/lifecycle'; import { IQuickInputService, IQuickPickItem, QuickPickInput, IPickOptions, Omit, IInputOptions, IQuickInputButton, IQuickPick, IInputBox, IQuickNavigateConfiguration } from 'vs/platform/quickinput/common/quickInput'; @@ -691,6 +691,6 @@ class MockInputsConfigurationService extends TestConfigurationService { class MockWorkbenchEnvironmentService extends NativeWorkbenchEnvironmentService { constructor(public userEnv: platform.IProcessEnvironment) { - super({ ...TestWindowConfiguration, userEnv }, TestWindowConfiguration.execPath); + super({ ...TestWorkbenchConfiguration, userEnv }); } } diff --git a/src/vs/workbench/services/credentials/browser/credentialsService.ts b/src/vs/workbench/services/credentials/browser/credentialsService.ts index e34d833312..7c761abcc7 100644 --- a/src/vs/workbench/services/credentials/browser/credentialsService.ts +++ b/src/vs/workbench/services/credentials/browser/credentialsService.ts @@ -6,7 +6,6 @@ import { ICredentialsProvider, ICredentialsService } from 'vs/platform/credentials/common/credentials'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { find } from 'vs/base/common/arrays'; export class BrowserCredentialsService implements ICredentialsService { @@ -80,7 +79,7 @@ class InMemoryCredentialsProvider implements ICredentialsProvider { } private doFindPassword(service: string, account?: string): ICredential | undefined { - return find(this.credentials, credential => + return this.credentials.find(credential => credential.service === service && (typeof account !== 'string' || credential.account === account)); } diff --git a/src/vs/workbench/services/decorations/test/browser/decorationsService.test.ts b/src/vs/workbench/services/decorations/test/browser/decorationsService.test.ts index 33402d630b..6a665f0fbd 100644 --- a/src/vs/workbench/services/decorations/test/browser/decorationsService.test.ts +++ b/src/vs/workbench/services/decorations/test/browser/decorationsService.test.ts @@ -94,7 +94,7 @@ suite('DecorationsService', function () { // un-register -> ensure good event let didSeeEvent = false; - let p = new Promise(resolve => { + let p = new Promise(resolve => { service.onDidChangeDecorations(e => { assert.equal(e.affectsResource(uri), true); assert.deepEqual(service.getDecoration(uri, false), undefined); @@ -275,7 +275,7 @@ suite('DecorationsService', function () { data = service.getDecoration(uri2, true)!; assert.ok(data.tooltip); // emphazied items... - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { let l = service.onDidChangeDecorations(e => { l.dispose(); try { diff --git a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts index a20f657ee3..b165569365 100644 --- a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts @@ -10,12 +10,10 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/ import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { URI } from 'vs/base/common/uri'; -import { Schemas } from 'vs/base/common/network'; import * as resources from 'vs/base/common/resources'; import { IInstantiationService, } from 'vs/platform/instantiation/common/instantiation'; import { SimpleFileDialog } from 'vs/workbench/services/dialogs/browser/simpleFileDialog'; import { WORKSPACE_EXTENSION, isUntitledWorkspace, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; -import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IFileService } from 'vs/platform/files/common/files'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -25,6 +23,7 @@ import { coalesce } from 'vs/base/common/arrays'; import { trim } from 'vs/base/common/strings'; import { IModeService } from 'vs/editor/common/services/modeService'; import { ILabelService } from 'vs/platform/label/common/label'; +import { IPathService } from 'vs/workbench/services/path/common/pathService'; export abstract class AbstractFileDialogService implements IFileDialogService { @@ -42,7 +41,8 @@ export abstract class AbstractFileDialogService implements IFileDialogService { @IDialogService private readonly dialogService: IDialogService, @IModeService private readonly modeService: IModeService, @IWorkspacesService private readonly workspacesService: IWorkspacesService, - @ILabelService private readonly labelService: ILabelService + @ILabelService private readonly labelService: ILabelService, + @IPathService private readonly pathService: IPathService ) { } defaultFilePath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined { @@ -230,7 +230,7 @@ export abstract class AbstractFileDialogService implements IFileDialogService { } protected getSchemeFilterForWindow(defaultUriScheme?: string): string { - return !this.environmentService.configuration.remoteAuthority ? (!defaultUriScheme || defaultUriScheme === Schemas.file ? Schemas.file : defaultUriScheme) : REMOTE_HOST_SCHEME; + return defaultUriScheme ?? this.pathService.defaultUriScheme; } protected getFileSystemSchema(options: { availableFileSystems?: readonly string[], defaultUri?: URI }): string { diff --git a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts index 8726f6f820..4c0af0a077 100644 --- a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts +++ b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts @@ -11,7 +11,6 @@ import { IQuickInputService, IQuickPickItem, IQuickPick } from 'vs/platform/quic import { URI } from 'vs/base/common/uri'; import { isWindows, OperatingSystem } from 'vs/base/common/platform'; import { ISaveDialogOptions, IOpenDialogOptions, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { ILabelService } from 'vs/platform/label/common/label'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -21,12 +20,11 @@ import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { Schemas } from 'vs/base/common/network'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, IContextKey, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { equalsIgnoreCase, format, startsWithIgnoreCase } from 'vs/base/common/strings'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { isValidBasename } from 'vs/base/common/extpath'; -import { RemoteFileDialogContext } from 'vs/workbench/browser/contextkeys'; import { Emitter } from 'vs/base/common/event'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { createCancelablePromise, CancelablePromise } from 'vs/base/common/async'; @@ -98,6 +96,8 @@ enum UpdateResult { InvalidPath } +export const RemoteFileDialogContext = new RawContextKey('remoteFileDialogVisible', false); + export class SimpleFileDialog { private options!: IOpenDialogOptions; private currentFolder!: URI; @@ -108,7 +108,7 @@ export class SimpleFileDialog { private remoteAuthority: string | undefined; private requiresTrailing: boolean = false; private trailing: string | undefined; - protected scheme: string = REMOTE_HOST_SCHEME; + protected scheme: string; private contextKey: IContextKey; private userEnteredPathSegment: string = ''; private autoCompletePathSegment: string = ''; @@ -141,6 +141,7 @@ export class SimpleFileDialog { ) { this.remoteAuthority = this.environmentService.configuration.remoteAuthority; this.contextKey = RemoteFileDialogContext.bindTo(contextKeyService); + this.scheme = this.pathService.defaultUriScheme; } set busy(busy: boolean) { @@ -211,7 +212,7 @@ export class SimpleFileDialog { path = path.replace(/\\/g, '/'); } const uri: URI = this.scheme === Schemas.file ? URI.file(path) : URI.from({ scheme: this.scheme, path }); - return resources.toLocalResource(uri, uri.scheme === Schemas.file ? undefined : this.remoteAuthority); + return resources.toLocalResource(uri, uri.scheme === Schemas.file ? undefined : this.remoteAuthority, this.pathService.defaultUriScheme); } private getScheme(available: readonly string[] | undefined, defaultUri: URI | undefined): string { diff --git a/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts b/src/vs/workbench/services/dialogs/electron-sandbox/dialogService.ts similarity index 93% rename from src/vs/workbench/services/dialogs/electron-browser/dialogService.ts rename to src/vs/workbench/services/dialogs/electron-sandbox/dialogService.ts index 1d5958cdf7..9d4bf1cd8e 100644 --- a/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts +++ b/src/vs/workbench/services/dialogs/electron-sandbox/dialogService.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as os from 'os'; import Severity from 'vs/base/common/severity'; import { isLinux, isWindows } from 'vs/base/common/platform'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; @@ -12,8 +11,6 @@ import { IDialogService, IConfirmation, IConfirmationResult, IDialogOptions, ISh import { DialogService as HTMLDialogService } from 'vs/workbench/services/dialogs/browser/dialogService'; import { ILogService } from 'vs/platform/log/common/log'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; -import { DialogChannel } from 'vs/platform/dialogs/electron-browser/dialogIpc'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -23,6 +20,7 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron'; import { MessageBoxOptions } from 'vs/base/parts/sandbox/common/electronTypes'; import { fromNow } from 'vs/base/common/date'; +import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; interface IMassagedMessageBoxOptions { @@ -51,14 +49,13 @@ export class DialogService implements IDialogService { @ILogService logService: ILogService, @ILayoutService layoutService: ILayoutService, @IThemeService themeService: IThemeService, - @ISharedProcessService sharedProcessService: ISharedProcessService, @IKeybindingService keybindingService: IKeybindingService, @IProductService productService: IProductService, @IClipboardService clipboardService: IClipboardService, @IElectronService electronService: IElectronService ) { this.customImpl = new HTMLDialogService(logService, layoutService, themeService, keybindingService, productService, clipboardService); - this.nativeImpl = new NativeDialogService(logService, sharedProcessService, electronService, productService, clipboardService); + this.nativeImpl = new NativeDialogService(logService, electronService, productService, clipboardService); } private get useCustomDialog(): boolean { @@ -92,12 +89,10 @@ class NativeDialogService implements IDialogService { constructor( @ILogService private readonly logService: ILogService, - @ISharedProcessService sharedProcessService: ISharedProcessService, @IElectronService private readonly electronService: IElectronService, @IProductService private readonly productService: IProductService, @IClipboardService private readonly clipboardService: IClipboardService ) { - sharedProcessService.registerChannel('dialog', new DialogChannel(this)); } async confirm(confirmation: IConfirmation): Promise { @@ -217,6 +212,7 @@ class NativeDialogService implements IDialogService { } const isSnap = process.platform === 'linux' && process.env.SNAP && process.env.SNAP_REVISION; + const os = await this.electronService.getOS(); const detailString = (useAgo: boolean): string => { return nls.localize('aboutDetail', @@ -228,7 +224,7 @@ class NativeDialogService implements IDialogService { process.versions['chrome'], process.versions['node'], process.versions['v8'], - `${os.type()} ${os.arch()} ${os.release()}${isSnap ? ' snap' : ''}`, + `${os.type} ${os.arch} ${os.release}${isSnap ? ' snap' : ''}`, this.productService.vscodeVersion ); }; diff --git a/src/vs/workbench/services/dialogs/electron-sandbox/fileDialogService.ts b/src/vs/workbench/services/dialogs/electron-sandbox/fileDialogService.ts index a4ef3bdd04..0b1782c554 100644 --- a/src/vs/workbench/services/dialogs/electron-sandbox/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/electron-sandbox/fileDialogService.ts @@ -21,6 +21,7 @@ import { Schemas } from 'vs/base/common/network'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { ILabelService } from 'vs/platform/label/common/label'; +import { IPathService } from 'vs/workbench/services/path/common/pathService'; export class FileDialogService extends AbstractFileDialogService implements IFileDialogService { @@ -39,9 +40,11 @@ export class FileDialogService extends AbstractFileDialogService implements IFil @IDialogService dialogService: IDialogService, @IModeService modeService: IModeService, @IWorkspacesService workspacesService: IWorkspacesService, - @ILabelService labelService: ILabelService + @ILabelService labelService: ILabelService, + @IPathService pathService: IPathService ) { - super(hostService, contextService, historyService, environmentService, instantiationService, configurationService, fileService, openerService, dialogService, modeService, workspacesService, labelService); + super(hostService, contextService, historyService, environmentService, instantiationService, + configurationService, fileService, openerService, dialogService, modeService, workspacesService, labelService, pathService); } private toNativeOpenDialogOptions(options: IPickAndOpenOptions): INativeOpenDialogOptions { diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index f3f2a1a01a..e5617ad09e 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -436,20 +436,27 @@ export class EditorService extends Disposable implements EditorServiceImpl { return this.getEditors(EditorsOrder.SEQUENTIAL).map(({ editor }) => editor); } - getEditors(order: EditorsOrder.MOST_RECENTLY_ACTIVE): ReadonlyArray; - getEditors(order: EditorsOrder.SEQUENTIAL, options?: { excludeSticky?: boolean }): ReadonlyArray; getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): ReadonlyArray { - if (order === EditorsOrder.MOST_RECENTLY_ACTIVE) { - return this.editorsObserver.editors; + switch (order) { + + // MRU + case EditorsOrder.MOST_RECENTLY_ACTIVE: + if (options?.excludeSticky) { + return this.editorsObserver.editors.filter(({ groupId, editor }) => !this.editorGroupService.getGroup(groupId)?.isSticky(editor)); + } + + return this.editorsObserver.editors; + + // Sequential + case EditorsOrder.SEQUENTIAL: + const editors: IEditorIdentifier[] = []; + + this.editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE).forEach(group => { + editors.push(...group.getEditors(EditorsOrder.SEQUENTIAL, options).map(editor => ({ editor, groupId: group.id }))); + }); + + return editors; } - - const editors: IEditorIdentifier[] = []; - - this.editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE).forEach(group => { - editors.push(...group.getEditors(EditorsOrder.SEQUENTIAL, options).map(editor => ({ editor, groupId: group.id }))); - }); - - return editors; } get activeEditor(): IEditorInput | undefined { @@ -1323,15 +1330,7 @@ export class DelegatingEditorService implements IEditorService { get editors(): ReadonlyArray { return this.editorService.editors; } get count(): number { return this.editorService.count; } - getEditors(order: EditorsOrder.MOST_RECENTLY_ACTIVE): ReadonlyArray; - getEditors(order: EditorsOrder.SEQUENTIAL, options?: { excludeSticky?: boolean }): ReadonlyArray; - getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): ReadonlyArray { - if (order === EditorsOrder.MOST_RECENTLY_ACTIVE) { - return this.editorService.getEditors(order); - } - - return this.editorService.getEditors(order, options); - } + getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): ReadonlyArray { return this.editorService.getEditors(order, options); } openEditors(editors: IEditorInputWithOptions[], group?: OpenInEditorGroup): Promise; openEditors(editors: IResourceEditorInputType[], group?: OpenInEditorGroup): Promise; diff --git a/src/vs/workbench/services/editor/common/editorOpenWith.ts b/src/vs/workbench/services/editor/common/editorOpenWith.ts index 7282621db2..d28d786d72 100644 --- a/src/vs/workbench/services/editor/common/editorOpenWith.ts +++ b/src/vs/workbench/services/editor/common/editorOpenWith.ts @@ -62,13 +62,13 @@ export async function openEditorWith( // Prompt const resourceExt = extname(resource); - const items: (IQuickPickItem & { handler: IOpenEditorOverrideHandler })[] = allEditorOverrides.map((override) => { + const items: (IQuickPickItem & { handler: IOpenEditorOverrideHandler })[] = allEditorOverrides.map(([handler, entry]) => { 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, + handler: handler, + id: entry.id, + label: entry.label, + description: entry.active ? nls.localize('promptOpenWith.currentlyActive', 'Currently Active') : undefined, + detail: entry.detail, buttons: resourceExt ? [{ iconClass: 'codicon-settings-gear', tooltip: nls.localize('promptOpenWith.setDefaultTooltip', "Set as default editor for '{0}' files", resourceExt) diff --git a/src/vs/workbench/services/editor/common/editorService.ts b/src/vs/workbench/services/editor/common/editorService.ts index 10f2716a8f..81dabc1062 100644 --- a/src/vs/workbench/services/editor/common/editorService.ts +++ b/src/vs/workbench/services/editor/common/editorService.ts @@ -175,9 +175,9 @@ export interface IEditorService { * identifier. * * @param order the order of the editors to use + * @param options wether to exclude sticky editors or not */ - getEditors(order: EditorsOrder.MOST_RECENTLY_ACTIVE): ReadonlyArray; - getEditors(order: EditorsOrder.SEQUENTIAL, options?: { excludeSticky?: boolean }): ReadonlyArray; + getEditors(order: EditorsOrder, options?: { excludeSticky?: boolean }): ReadonlyArray; /** * Open an editor in an editor group. diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index 122041fa93..336b47b7c4 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -163,6 +163,11 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite assert.equal(input, sequentialEditorsExcludingSticky[0].editor); assert.equal(otherInput, sequentialEditorsExcludingSticky[1].editor); + const mruEditorsExcludingSticky = service.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE, { excludeSticky: true }); + assert.equal(mruEditorsExcludingSticky.length, 2); + assert.equal(input, sequentialEditorsExcludingSticky[0].editor); + assert.equal(otherInput, sequentialEditorsExcludingSticky[1].editor); + activeEditorChangeListener.dispose(); visibleEditorChangeListener.dispose(); didCloseEditorListener.dispose(); diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index 74979ddfe2..24e7f61245 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -7,21 +7,21 @@ import { Schemas } from 'vs/base/common/network'; import { joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; -import { BACKUPS, IExtensionHostDebugParams } from 'vs/platform/environment/common/environment'; -import { IPath } from 'vs/platform/windows/common/windows'; -import { IWorkbenchEnvironmentService, IEnvironmentConfiguration } from 'vs/workbench/services/environment/common/environmentService'; -import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api'; +import { IExtensionHostDebugParams } from 'vs/platform/environment/common/environment'; +import { IPath, IWindowConfiguration } from 'vs/platform/windows/common/windows'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IWorkbenchConstructionOptions as IWorkbenchOptions } from 'vs/workbench/workbench.web.api'; import product from 'vs/platform/product/common/product'; import { memoize } from 'vs/base/common/decorators'; import { onUnexpectedError } from 'vs/base/common/errors'; import { parseLineAndColumnAware } from 'vs/base/common/extpath'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; -export class BrowserEnvironmentConfiguration implements IEnvironmentConfiguration { +class BrowserWorkbenchConfiguration implements IWindowConfiguration { constructor( - private readonly options: IBrowserWorkbenchEnvironmentConstructionOptions, - private readonly payload: Map | undefined, - private readonly backupHome: URI + private readonly options: IBrowserWorkbenchOptions, + private readonly payload: Map | undefined ) { } @memoize @@ -30,9 +30,6 @@ export class BrowserEnvironmentConfiguration implements IEnvironmentConfiguratio @memoize get remoteAuthority(): string | undefined { return this.options.remoteAuthority; } - @memoize - get backupWorkspaceResource(): URI { return joinPath(this.backupHome, this.options.workspaceId); } - @memoize get filesToOpenOrCreate(): IPath[] | undefined { if (this.payload) { @@ -74,12 +71,17 @@ export class BrowserEnvironmentConfiguration implements IEnvironmentConfiguratio return undefined; } - get highContrast() { - return false; // could investigate to detect high contrast theme automatically + // TODO@martin TODO@ben this currently does not support high-contrast theme preference (no browser support yet) + get colorScheme() { + if (window.matchMedia(`(prefers-color-scheme: dark)`).matches) { + return ColorScheme.DARK; + } + + return ColorScheme.LIGHT; } } -interface IBrowserWorkbenchEnvironmentConstructionOptions extends IWorkbenchConstructionOptions { +interface IBrowserWorkbenchOptions extends IWorkbenchOptions { workspaceId: string; logsPath: URI; } @@ -96,10 +98,10 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment declare readonly _serviceBrand: undefined; - private _configuration: IEnvironmentConfiguration | undefined = undefined; - get configuration(): IEnvironmentConfiguration { + private _configuration: IWindowConfiguration | undefined = undefined; + get configuration(): IWindowConfiguration { if (!this._configuration) { - this._configuration = new BrowserEnvironmentConfiguration(this.options, this.payload, this.backupHome); + this._configuration = new BrowserWorkbenchConfiguration(this.options, this.payload); } return this._configuration; @@ -158,7 +160,7 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment get keyboardLayoutResource(): URI { return joinPath(this.userRoamingDataHome, 'keyboardLayout.json'); } @memoize - get backupHome(): URI { return joinPath(this.userRoamingDataHome, BACKUPS); } + get backupWorkspaceHome(): URI { return joinPath(this.userRoamingDataHome, 'Backups', this.options.workspaceId); } @memoize get untitledWorkspacesHome(): URI { return joinPath(this.userRoamingDataHome, 'Workspaces'); } @@ -237,7 +239,7 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment private payload: Map | undefined; - constructor(readonly options: IBrowserWorkbenchEnvironmentConstructionOptions) { + constructor(readonly options: IBrowserWorkbenchOptions) { if (options.workspaceProvider && Array.isArray(options.workspaceProvider.payload)) { try { this.payload = new Map(options.workspaceProvider.payload); diff --git a/src/vs/workbench/services/environment/common/environmentService.ts b/src/vs/workbench/services/environment/common/environmentService.ts index a698b448db..0e56d86403 100644 --- a/src/vs/workbench/services/environment/common/environmentService.ts +++ b/src/vs/workbench/services/environment/common/environmentService.ts @@ -6,28 +6,42 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api'; +import type { IWorkbenchConstructionOptions as IWorkbenchOptions } from 'vs/workbench/workbench.web.api'; import { URI } from 'vs/base/common/uri'; export const IWorkbenchEnvironmentService = createDecorator('environmentService'); -export interface IEnvironmentConfiguration extends IWindowConfiguration { - backupWorkspaceResource?: URI; -} - +/** + * A workbench specific environment service that is only present in workbench + * layer. + */ export interface IWorkbenchEnvironmentService extends IEnvironmentService { + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // NOTE: KEEP THIS INTERFACE AS SMALL AS POSSIBLE. AS SUCH: + // - PUT NON-WEB PROPERTIES INTO NATIVE WB ENV SERVICE + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + readonly _serviceBrand: undefined; - readonly configuration: IEnvironmentConfiguration; + readonly configuration: IWindowConfiguration; - readonly options?: IWorkbenchConstructionOptions; + readonly options?: IWorkbenchOptions; readonly logFile: URI; + readonly backupWorkspaceHome?: URI; + + readonly logExtensionHostCommunication?: boolean; + readonly extensionEnabledProposedApi?: string[]; readonly webviewExternalEndpoint: string; readonly webviewResourceRoot: string; readonly webviewCspSource: string; readonly skipReleaseNotes: boolean; + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // NOTE: KEEP THIS INTERFACE AS SMALL AS POSSIBLE. AS SUCH: + // - PUT NON-WEB PROPERTIES INTO NATIVE WB ENV SERVICE + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! } diff --git a/src/vs/workbench/services/environment/electron-browser/environmentService.ts b/src/vs/workbench/services/environment/electron-browser/environmentService.ts index de380a0785..2c86ca64d4 100644 --- a/src/vs/workbench/services/environment/electron-browser/environmentService.ts +++ b/src/vs/workbench/services/environment/electron-browser/environmentService.ts @@ -3,30 +3,14 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { EnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; -import { IWorkbenchEnvironmentService, IEnvironmentConfiguration } from 'vs/workbench/services/environment/common/environmentService'; +import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { INativeWorkbenchConfiguration, INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { memoize } from 'vs/base/common/decorators'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; -import { toBackupWorkspaceResource } from 'vs/workbench/services/backup/electron-browser/backup'; -import { join } from 'vs/base/common/path'; +import { dirname, join } from 'vs/base/common/path'; import product from 'vs/platform/product/common/product'; -import { INativeWindowConfiguration } from 'vs/platform/windows/node/window'; - -export interface INativeWorkbenchEnvironmentService extends IWorkbenchEnvironmentService, INativeEnvironmentService { - - readonly configuration: INativeEnvironmentConfiguration; - - readonly crashReporterDirectory?: string; - readonly crashReporterId?: string; - - readonly cliPath: string; - - readonly log?: string; - readonly extHostLogsPath: URI; -} - -export interface INativeEnvironmentConfiguration extends IEnvironmentConfiguration, INativeWindowConfiguration { } +import { isLinux, isWindows } from 'vs/base/common/platform'; export class NativeWorkbenchEnvironmentService extends EnvironmentService implements INativeWorkbenchEnvironmentService { @@ -48,6 +32,9 @@ export class NativeWorkbenchEnvironmentService extends EnvironmentService implem @memoize get userRoamingDataHome(): URI { return this.appSettingsHome.with({ scheme: Schemas.userData }); } + // Do not memoize as `backupPath` can change in configuration + get backupWorkspaceHome(): URI | undefined { return this.configuration.backupPath ? URI.file(this.configuration.backupPath).with({ scheme: this.userRoamingDataHome.scheme }) : undefined; } + @memoize get logFile(): URI { return URI.file(join(this.logsPath, `renderer${this.configuration.windowId}.log`)); } @@ -57,12 +44,57 @@ export class NativeWorkbenchEnvironmentService extends EnvironmentService implem @memoize get skipReleaseNotes(): boolean { return !!this.args['skip-release-notes']; } - constructor( - readonly configuration: INativeEnvironmentConfiguration, - execPath: string - ) { - super(configuration, execPath); + @memoize + get logExtensionHostCommunication(): boolean { return !!this.args.logExtensionHostCommunication; } - this.configuration.backupWorkspaceResource = this.configuration.backupPath ? toBackupWorkspaceResource(this.configuration.backupPath, this) : undefined; + get extensionEnabledProposedApi(): string[] | undefined { + if (Array.isArray(this.args['enable-proposed-api'])) { + return this.args['enable-proposed-api']; + } + + if ('enable-proposed-api' in this.args) { + return []; + } + + return undefined; + } + + @memoize + get cliPath(): string { return this.doGetCLIPath(); } + + readonly execPath = this.configuration.execPath; + + constructor( + readonly configuration: INativeWorkbenchConfiguration + ) { + super(configuration); + } + + private doGetCLIPath(): string { + + // Windows + if (isWindows) { + if (this.isBuilt) { + return join(dirname(this.execPath), 'bin', `${product.applicationName}.cmd`); + } + + return join(this.appRoot, 'scripts', 'code-cli.bat'); + } + + // Linux + if (isLinux) { + if (this.isBuilt) { + return join(dirname(this.execPath), 'bin', `${product.applicationName}`); + } + + return join(this.appRoot, 'scripts', 'code-cli.sh'); + } + + // macOS + if (this.isBuilt) { + return join(this.appRoot, 'bin', 'code'); + } + + return join(this.appRoot, 'scripts', 'code-cli.sh'); } } diff --git a/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts b/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts new file mode 100644 index 0000000000..7345f146cb --- /dev/null +++ b/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWindowConfiguration, IWindowConfiguration } from 'vs/platform/windows/common/windows'; +import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; +import { URI } from 'vs/base/common/uri'; + +export interface INativeWorkbenchConfiguration extends IWindowConfiguration, INativeWindowConfiguration { } + +/** + * A subclass of the `IWorkbenchEnvironmentService` to be used only in native + * environments (Windows, Linux, macOS) but not e.g. web. + */ +export interface INativeWorkbenchEnvironmentService extends IWorkbenchEnvironmentService, INativeEnvironmentService { + + readonly configuration: INativeWorkbenchConfiguration; + + readonly crashReporterDirectory?: string; + readonly crashReporterId?: string; + + readonly execPath: string; + readonly cliPath: string; + + readonly log?: string; + readonly extHostLogsPath: URI; +} diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts index ae0042321e..3cf4471230 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts @@ -6,7 +6,7 @@ import { localize } from 'vs/nls'; import { IExtensionManagementServer, IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; +import { Schemas } from 'vs/base/common/network'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ILabelService } from 'vs/platform/label/common/label'; @@ -41,10 +41,9 @@ export class ExtensionManagementServerService implements IExtensionManagementSer this.remoteExtensionManagementServer = { id: 'remote', extensionManagementService, - get label() { return labelService.getHostLabel(REMOTE_HOST_SCHEME, remoteAgentConnection!.remoteAuthority) || localize('remote', "Remote"); } + get label() { return labelService.getHostLabel(Schemas.vscodeRemote, remoteAgentConnection!.remoteAuthority) || localize('remote', "Remote"); } }; - } - if (isWeb) { + } else if (isWeb) { const extensionManagementService = instantiationService.createInstance(WebExtensionManagementService); this.webExtensionManagementServer = { id: 'web', @@ -55,7 +54,7 @@ export class ExtensionManagementServerService implements IExtensionManagementSer } getExtensionManagementServer(extension: IExtension): IExtensionManagementServer { - if (extension.location.scheme === REMOTE_HOST_SCHEME) { + if (extension.location.scheme === Schemas.vscodeRemote) { return this.remoteExtensionManagementServer!; } if (this.webExtensionManagementServer) { diff --git a/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts index 26c5caafce..331d6f3a1d 100644 --- a/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/common/webExtensionsScannerService.ts @@ -19,7 +19,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IGalleryExtension, INSTALL_ERROR_NOT_SUPPORTED } from 'vs/platform/extensionManagement/common/extensionManagement'; import { groupByExtension, areSameExtensions, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IStaticExtension } from 'vs/workbench/workbench.web.api'; +import type { IStaticExtension } from 'vs/workbench/workbench.web.api'; import { Disposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; import { localizeManifest } from 'vs/platform/extensionManagement/common/extensionNls'; diff --git a/src/vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService.ts b/src/vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService.ts index cfe21ad67b..028a80002f 100644 --- a/src/vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService.ts @@ -8,7 +8,6 @@ import { Schemas } from 'vs/base/common/network'; import { IExtensionManagementServer, IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -47,7 +46,7 @@ export class ExtensionManagementServerService implements IExtensionManagementSer this.remoteExtensionManagementServer = { id: 'remote', extensionManagementService, - get label() { return labelService.getHostLabel(REMOTE_HOST_SCHEME, remoteAgentConnection!.remoteAuthority) || localize('remote', "Remote"); } + get label() { return labelService.getHostLabel(Schemas.vscodeRemote, remoteAgentConnection!.remoteAuthority) || localize('remote', "Remote"); } }; } } @@ -56,7 +55,7 @@ export class ExtensionManagementServerService implements IExtensionManagementSer if (extension.location.scheme === Schemas.file) { return this.localExtensionManagementServer; } - if (this.remoteExtensionManagementServer && extension.location.scheme === REMOTE_HOST_SCHEME) { + if (this.remoteExtensionManagementServer && extension.location.scheme === Schemas.vscodeRemote) { return this.remoteExtensionManagementServer; } throw new Error(`Invalid Extension ${extension.location}`); diff --git a/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts b/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts index e683258da6..1d31d6b128 100644 --- a/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts +++ b/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts @@ -18,8 +18,6 @@ import { areSameExtensions } from 'vs/platform/extensionManagement/common/extens import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; -import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; -import { assign } from 'vs/base/common/objects'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { productService } from 'vs/workbench/test/browser/workbenchTestServices'; import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService'; @@ -594,7 +592,7 @@ function anExtensionManagementServerService(localExtensionManagementServer: IExt if (extension.location.scheme === Schemas.file) { return localExtensionManagementServer; } - if (extension.location.scheme === REMOTE_HOST_SCHEME) { + if (extension.location.scheme === Schemas.vscodeRemote) { return remoteExtensionManagementServer; } return webExtensionManagementServer; @@ -608,11 +606,12 @@ function aLocalExtension(id: string, contributes?: IExtensionContributions, type function aLocalExtension2(id: string, manifest: any = {}, properties: any = {}): ILocalExtension { const [publisher, name] = id.split('.'); - properties = assign({ + manifest = { name, publisher, ...manifest }; + properties = { identifier: { id }, galleryIdentifier: { id, uuid: undefined }, - type: ExtensionType.User - }, properties); - manifest = assign({ name, publisher }, manifest); + type: ExtensionType.User, + ...properties + }; return Object.create({ manifest, ...properties }); } diff --git a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts index 4f1958030c..32b3b1c0df 100644 --- a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts +++ b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts @@ -28,6 +28,7 @@ import { localize } from 'vs/nls'; import { generateUuid } from 'vs/base/common/uuid'; import { canceled, onUnexpectedError } from 'vs/base/common/errors'; import { WEB_WORKER_IFRAME } from 'vs/workbench/services/extensions/common/webWorkerIframe'; +import { Barrier } from 'vs/base/common/async'; export interface IWebWorkerExtensionHostInitData { readonly autoStart: boolean; @@ -82,7 +83,7 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost return this._protocolPromise; } - private _startInsideIframe(): Promise { + private async _startInsideIframe(): Promise { const emitter = this._register(new Emitter()); const iframe = document.createElement('iframe'); @@ -111,6 +112,9 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost const iframeContent = `data:text/html;charset=utf-8,${encodeURIComponent(html)}`; iframe.setAttribute('src', iframeContent); + const barrier = new Barrier(); + let port!: MessagePort; + this._register(dom.addDisposableListener(window, 'message', (event) => { if (event.source !== iframe.contentWindow) { return; @@ -129,38 +133,68 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost return; } const { data } = event.data; + if (barrier.isOpen() || !(data instanceof MessagePort)) { + console.warn('UNEXPECTED message', event); + this._onDidExit.fire([81, 'UNEXPECTED message']); + return; + } + port = data; + barrier.open(); + })); + + document.body.appendChild(iframe); + this._register(toDisposable(() => iframe.remove())); + + // await MessagePort and use it to directly communicate + // with the worker extension host + await barrier.wait(); + + port.onmessage = (event) => { + const { data } = event; if (!(data instanceof ArrayBuffer)) { console.warn('UNKNOWN data received', data); this._onDidExit.fire([77, 'UNKNOWN data received']); return; } emitter.fire(VSBuffer.wrap(new Uint8Array(data, 0, data.byteLength))); - })); + }; const protocol: IMessagePassingProtocol = { onMessage: emitter.event, send: vsbuf => { const data = vsbuf.buffer.buffer.slice(vsbuf.buffer.byteOffset, vsbuf.buffer.byteOffset + vsbuf.buffer.byteLength); - iframe.contentWindow!.postMessage({ - vscodeWebWorkerExtHostId, - data: data - }, '*', [data]); + port.postMessage(data, [data]); } }; - document.body.appendChild(iframe); - this._register(toDisposable(() => iframe.remove())); - return this._performHandshake(protocol); } - private _startOutsideIframe(): Promise { + private async _startOutsideIframe(): Promise { const emitter = new Emitter(); const url = getWorkerBootstrapUrl(require.toUrl('../worker/extensionHostWorkerMain.js'), 'WorkerExtensionHost'); const worker = new Worker(url, { name: 'WorkerExtensionHost' }); + const barrier = new Barrier(); + let port!: MessagePort; + worker.onmessage = (event) => { + const { data } = event; + if (barrier.isOpen() || !(data instanceof MessagePort)) { + console.warn('UNEXPECTED message', event); + this._onDidExit.fire([81, 'UNEXPECTED message']); + return; + } + port = data; + barrier.open(); + }; + + // await MessagePort and use it to directly communicate + // with the worker extension host + await barrier.wait(); + + port.onmessage = (event) => { const { data } = event; if (!(data instanceof ArrayBuffer)) { console.warn('UNKNOWN data received', data); @@ -184,7 +218,7 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost onMessage: emitter.event, send: vsbuf => { const data = vsbuf.buffer.buffer.slice(vsbuf.buffer.byteOffset, vsbuf.buffer.byteOffset + vsbuf.buffer.byteLength); - worker.postMessage(data, [data]); + port.postMessage(data, [data]); } }; diff --git a/src/vs/workbench/services/extensions/common/webWorkerIframe.ts b/src/vs/workbench/services/extensions/common/webWorkerIframe.ts index c8ab17673d..e11f25ab28 100644 --- a/src/vs/workbench/services/extensions/common/webWorkerIframe.ts +++ b/src/vs/workbench/services/extensions/common/webWorkerIframe.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ export const WEB_WORKER_IFRAME = { - sha: 'sha256-rSINb5Ths99Zj4Ml59jEdHS4WbO+H5Iw+oyRmyi2MLw=', + sha: 'sha256-r24mDVsMuFEo8ChaY9ppVJKbY3CUM4I12Aw/yscWZbg=', js: ` (function() { const workerSrc = document.getElementById('vscode-worker-src').getAttribute('data-value'); @@ -13,8 +13,8 @@ export const WEB_WORKER_IFRAME = { worker.onmessage = (event) => { const { data } = event; - if (!(data instanceof ArrayBuffer)) { - console.warn('Unknown data received', data); + if (!(data instanceof MessagePort)) { + console.warn('Unknown data received', event); window.parent.postMessage({ vscodeWebWorkerExtHostId, error: { @@ -42,16 +42,6 @@ export const WEB_WORKER_IFRAME = { } }, '*'); }; - - window.addEventListener('message', function(event) { - if (event.source !== window.parent) { - return; - } - if (event.data.vscodeWebWorkerExtHostId !== vscodeWebWorkerExtHostId) { - return; - } - worker.postMessage(event.data.data, [event.data.data]); - }, false); })(); ` }; diff --git a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts index 4f84ced89b..ac2e7de061 100644 --- a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts +++ b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts @@ -14,8 +14,8 @@ import * as platform from 'vs/base/common/platform'; import { originalFSPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import * as pfs from 'vs/base/node/pfs'; -import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { BUILTIN_MANIFEST_CACHE_FILE, MANIFEST_CACHE_FOLDER, USER_MANIFEST_CACHE_FILE, ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IProductService } from 'vs/platform/product/common/productService'; diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index afebe3a389..7bdb498e5d 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -32,7 +32,6 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { Logger } from 'vs/workbench/services/extensions/common/extensionPoints'; import { flatten } from 'vs/base/common/arrays'; import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron'; -import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { getRemoteName } from 'vs/platform/remote/common/remoteHosts'; @@ -61,7 +60,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten constructor( @IInstantiationService instantiationService: IInstantiationService, @INotificationService notificationService: INotificationService, - @IWorkbenchEnvironmentService protected readonly _environmentService: INativeWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService protected readonly _environmentService: IWorkbenchEnvironmentService, @ITelemetryService telemetryService: ITelemetryService, @IWorkbenchExtensionEnablementService extensionEnablementService: IWorkbenchExtensionEnablementService, @IFileService fileService: IFileService, diff --git a/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts index b33019a825..d28cfe1e25 100644 --- a/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/localProcessExtensionHost.ts @@ -22,6 +22,7 @@ import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import { PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net'; import { generateRandomPipeName, NodeSocket } from 'vs/base/parts/ipc/node/ipc.net'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { ILabelService } from 'vs/platform/label/common/label'; import { ILifecycleService, WillShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; @@ -43,7 +44,6 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { joinPath } from 'vs/base/common/resources'; import { Registry } from 'vs/platform/registry/common/platform'; import { IOutputChannelRegistry, Extensions } from 'vs/workbench/services/output/common/output'; -import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { isUUID } from 'vs/base/common/uuid'; import { join } from 'vs/base/common/path'; @@ -172,7 +172,7 @@ export class LocalProcessExtensionHost implements IExtensionHost { } const opts = { - env: env, + env, // We only detach the extension host on windows. Linux and Mac orphan by default // and detach under Linux and Mac create another process group. // We detach because we have noticed that when the renderer exits, its child processes diff --git a/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts b/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts index 800117f268..ced38672d0 100644 --- a/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts +++ b/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts @@ -33,7 +33,7 @@ self.close = () => console.trace(`'close' has been blocked`); const nativePostMessage = postMessage.bind(self); self.postMessage = () => console.trace(`'postMessage' has been blocked`); -const nativeAddEventLister = addEventListener.bind(self); +// const nativeAddEventLister = addEventListener.bind(self); self.addEventLister = () => console.trace(`'addEventListener' has been blocked`); (self)['AMDLoader'] = undefined; @@ -79,11 +79,14 @@ class ExtensionWorker { constructor() { - let emitter = new Emitter(); + const channel = new MessageChannel(); + const emitter = new Emitter(); let terminating = false; + // send over port2, keep port1 + nativePostMessage(channel.port2, [channel.port2]); - nativeAddEventLister('message', event => { + channel.port1.onmessage = event => { const { data } = event; if (!(data instanceof ArrayBuffer)) { console.warn('UNKNOWN data received', data); @@ -100,14 +103,14 @@ class ExtensionWorker { // emit non-terminate messages to the outside emitter.fire(msg); - }); + }; this.protocol = { onMessage: emitter.event, send: vsbuf => { if (!terminating) { const data = vsbuf.buffer.buffer.slice(vsbuf.buffer.byteOffset, vsbuf.buffer.byteOffset + vsbuf.buffer.byteLength); - nativePostMessage(data, [data]); + channel.port1.postMessage(data, [data]); } } }; diff --git a/src/vs/workbench/services/extensions/worker/extensionHostWorkerMain.ts b/src/vs/workbench/services/extensions/worker/extensionHostWorkerMain.ts index 66cc5c3f1a..e26f446a45 100644 --- a/src/vs/workbench/services/extensions/worker/extensionHostWorkerMain.ts +++ b/src/vs/workbench/services/extensions/worker/extensionHostWorkerMain.ts @@ -14,7 +14,8 @@ require.config({ baseUrl: monacoBaseUrl, - catchError: true + catchError: true, + createTrustedScriptURL: (value: string) => value }); require(['vs/workbench/services/extensions/worker/extensionHostWorker'], () => { }, err => console.error(err)); diff --git a/src/vs/workbench/services/history/browser/history.ts b/src/vs/workbench/services/history/browser/history.ts index 4171170118..57005a0d20 100644 --- a/src/vs/workbench/services/history/browser/history.ts +++ b/src/vs/workbench/services/history/browser/history.ts @@ -35,6 +35,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { extUri } from 'vs/base/common/resources'; import { IdleValue } from 'vs/base/common/async'; import { ResourceGlobMatcher } from 'vs/workbench/common/resources'; +import { IPathService } from 'vs/workbench/services/path/common/pathService'; /** * Stores the selection & view state of an editor and allows to compare it to other selection states. @@ -117,7 +118,8 @@ export class HistoryService extends Disposable implements IHistoryService { @IWorkspacesService private readonly workspacesService: IWorkspacesService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, - @IContextKeyService private readonly contextKeyService: IContextKeyService + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IPathService private readonly pathService: IPathService ) { super(); @@ -514,7 +516,7 @@ export class HistoryService extends Disposable implements IHistoryService { private preferResourceEditorInput(input: IEditorInput): IEditorInput | IResourceEditorInput { const resource = input.resource; - if (resource && (resource.scheme === Schemas.file || resource.scheme === Schemas.vscodeRemote || resource.scheme === Schemas.userData)) { + if (resource && (resource.scheme === Schemas.file || resource.scheme === Schemas.vscodeRemote || resource.scheme === Schemas.userData || resource.scheme === this.pathService.defaultUriScheme)) { // for now, only prefer well known schemes that we control to prevent // issues such as https://github.com/microsoft/vscode/issues/85204 return { resource }; diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index 94679b42ba..576731524b 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -84,6 +84,8 @@ export class BrowserHostService extends Disposable implements IHostService { } } + //#region Focus + @memoize get onDidChangeFocus(): Event { const focusTracker = this._register(trackFocus(window)); @@ -107,6 +109,11 @@ export class BrowserHostService extends Disposable implements IHostService { window.focus(); } + //#endregion + + + //#region Window + openWindow(options?: IOpenEmptyWindowOptions): Promise; openWindow(toOpen: IWindowOpenable[], options?: IOpenWindowOptions): Promise; openWindow(arg1?: IOpenEmptyWindowOptions | IWindowOpenable[], arg2?: IOpenWindowOptions): Promise { @@ -320,6 +327,10 @@ export class BrowserHostService extends Disposable implements IHostService { } } + //#endregion + + //#region Lifecycle + async restart(): Promise { this.reload(); } @@ -327,6 +338,8 @@ export class BrowserHostService extends Disposable implements IHostService { async reload(): Promise { window.location.reload(); } + + //#endregion } registerSingleton(IHostService, BrowserHostService, true); diff --git a/src/vs/workbench/services/host/browser/host.ts b/src/vs/workbench/services/host/browser/host.ts index 92fa826ccf..e801aeb2cb 100644 --- a/src/vs/workbench/services/host/browser/host.ts +++ b/src/vs/workbench/services/host/browser/host.ts @@ -13,6 +13,7 @@ export interface IHostService { readonly _serviceBrand: undefined; + //#region Focus /** @@ -64,7 +65,6 @@ export interface IHostService { //#endregion - //#region Lifecycle /** diff --git a/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts b/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts index ae9960811e..7e89859a10 100644 --- a/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts +++ b/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts @@ -24,6 +24,8 @@ export class NativeHostService extends Disposable implements IHostService { super(); } + //#region Focus + get onDidChangeFocus(): Event { return this._onDidChangeFocus; } private _onDidChangeFocus: Event = Event.latch(Event.any( Event.map(Event.filter(this.electronService.onWindowFocus, id => id === this.electronService.windowId), () => this.hasFocus), @@ -44,6 +46,11 @@ export class NativeHostService extends Disposable implements IHostService { return activeWindowId === this.electronService.windowId; } + //#endregion + + + //#region Window + openWindow(options?: IOpenEmptyWindowOptions): Promise; openWindow(toOpen: IWindowOpenable[], options?: IOpenWindowOptions): Promise; openWindow(arg1?: IOpenEmptyWindowOptions | IWindowOpenable[], arg2?: IOpenWindowOptions): Promise { @@ -82,6 +89,11 @@ export class NativeHostService extends Disposable implements IHostService { return this.electronService.toggleFullScreen(); } + //#endregion + + + //#region Lifecycle + focus(options?: { force: boolean }): Promise { return this.electronService.focusWindow(options); } @@ -93,6 +105,8 @@ export class NativeHostService extends Disposable implements IHostService { reload(): Promise { return this.electronService.reload(); } + + //#endregion } registerSingleton(IHostService, NativeHostService, true); diff --git a/src/vs/workbench/services/hover/browser/hover.ts b/src/vs/workbench/services/hover/browser/hover.ts index 3bc7b6adcd..0b07c717cf 100644 --- a/src/vs/workbench/services/hover/browser/hover.ts +++ b/src/vs/workbench/services/hover/browser/hover.ts @@ -29,7 +29,12 @@ export interface IHoverService { * }); * ``` */ - showHover(options: IHoverOptions, focus?: boolean): void; + showHover(options: IHoverOptions, focus?: boolean): IDisposable | undefined; + + /** + * Hides the hover if it was visible. + */ + hideHover(): void; } export interface IHoverOptions { diff --git a/src/vs/workbench/services/hover/browser/hoverService.ts b/src/vs/workbench/services/hover/browser/hoverService.ts index 3852bb0ba1..c976132294 100644 --- a/src/vs/workbench/services/hover/browser/hoverService.ts +++ b/src/vs/workbench/services/hover/browser/hoverService.ts @@ -25,9 +25,9 @@ export class HoverService implements IHoverService { ) { } - showHover(options: IHoverOptions, focus?: boolean): void { + showHover(options: IHoverOptions, focus?: boolean): IDisposable | undefined { if (this._currentHoverOptions === options) { - return; + return undefined; } this._currentHoverOptions = options; @@ -43,6 +43,16 @@ export class HoverService implements IHoverService { observer.observe(firstTargetElement); hover.onDispose(() => observer.disconnect()); } + + return hover; + } + + hideHover(): void { + if (!this._currentHoverOptions) { + return; + } + this._currentHoverOptions = undefined; + this._contextViewService.hideContextView(); } private _intersectionChange(entries: IntersectionObserverEntry[], hover: IDisposable): void { diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts index ea88c99e84..264d589b9c 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts @@ -47,7 +47,7 @@ import { FileUserDataProvider } from 'vs/workbench/services/userData/common/file import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -import { TestWindowConfiguration, TestTextFileService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; +import { TestWorkbenchConfiguration, TestTextFileService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import { ILabelService } from 'vs/platform/label/common/label'; import { LabelService } from 'vs/workbench/services/label/common/labelService'; import { IFilesConfigurationService, FilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; @@ -61,10 +61,10 @@ import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService'; -class TestEnvironmentService extends NativeWorkbenchEnvironmentService { +class TestWorkbenchEnvironmentService extends NativeWorkbenchEnvironmentService { constructor(private _appSettingsHome: URI) { - super(TestWindowConfiguration, TestWindowConfiguration.execPath); + super(TestWorkbenchConfiguration); } get appSettingsHome() { return this._appSettingsHome; } @@ -90,7 +90,7 @@ suite('KeybindingsEditing', () => { instantiationService = new TestInstantiationService(); - const environmentService = new TestEnvironmentService(URI.file(testDir)); + const environmentService = new TestWorkbenchEnvironmentService(URI.file(testDir)); const configService = new TestConfigurationService(); configService.setUserConfiguration('files', { 'eol': '\n' }); @@ -117,7 +117,7 @@ suite('KeybindingsEditing', () => { const fileService = new FileService(new NullLogService()); const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); fileService.registerProvider(Schemas.file, diskFileSystemProvider); - fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, diskFileSystemProvider, environmentService, new NullLogService())); + fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, undefined, diskFileSystemProvider, environmentService, new NullLogService())); instantiationService.stub(IFileService, fileService); instantiationService.stub(IUriIdentityService, new UriIdentityService(fileService)); instantiationService.stub(IWorkingCopyService, new TestWorkingCopyService()); diff --git a/src/vs/workbench/services/output/electron-browser/outputChannelModelService.ts b/src/vs/workbench/services/output/electron-browser/outputChannelModelService.ts index 7b73f5ef44..1b1ec95244 100644 --- a/src/vs/workbench/services/output/electron-browser/outputChannelModelService.ts +++ b/src/vs/workbench/services/output/electron-browser/outputChannelModelService.ts @@ -21,7 +21,6 @@ import { toLocalISOString } from 'vs/base/common/date'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Emitter, Event } from 'vs/base/common/event'; -import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron'; class OutputChannelBackedByFile extends AbstractFileOutputChannelModel implements IOutputChannelModel { @@ -204,7 +203,7 @@ export class OutputChannelModelService extends AsbtractOutputChannelModelService constructor( @IInstantiationService instantiationService: IInstantiationService, - @IWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IFileService private readonly fileService: IFileService, @IElectronService private readonly electronService: IElectronService ) { diff --git a/src/vs/workbench/services/path/browser/pathService.ts b/src/vs/workbench/services/path/browser/pathService.ts index 5cf965240e..b2889e6956 100644 --- a/src/vs/workbench/services/path/browser/pathService.ts +++ b/src/vs/workbench/services/path/browser/pathService.ts @@ -9,15 +9,32 @@ import { IPathService, AbstractPathService } from 'vs/workbench/services/path/co import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; export class BrowserPathService extends AbstractPathService { + readonly defaultUriScheme = defaultUriScheme(this.environmentService, this.contextService); + constructor( @IRemoteAgentService remoteAgentService: IRemoteAgentService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService ) { - super(URI.from({ scheme: Schemas.vscodeRemote, authority: environmentService.configuration.remoteAuthority, path: '/' }), remoteAgentService); + super(URI.from({ scheme: defaultUriScheme(environmentService, contextService), authority: environmentService.configuration.remoteAuthority, path: '/' }), remoteAgentService); } } +function defaultUriScheme(environmentService: IWorkbenchEnvironmentService, contextService: IWorkspaceContextService): string { + if (environmentService.configuration.remoteAuthority) { + return Schemas.vscodeRemote; + } + + const firstFolder = contextService.getWorkspace().folders[0]; + if (!firstFolder) { + throw new Error('Empty workspace is not supported in browser when there is no remote connection.'); + } + + return firstFolder.uri.scheme; +} + registerSingleton(IPathService, BrowserPathService, true); diff --git a/src/vs/workbench/services/path/common/pathService.ts b/src/vs/workbench/services/path/common/pathService.ts index d363203bd9..c2ab8a7a0e 100644 --- a/src/vs/workbench/services/path/common/pathService.ts +++ b/src/vs/workbench/services/path/common/pathService.ts @@ -28,6 +28,14 @@ export interface IPathService { */ readonly path: Promise; + /** + * Determines the best default URI scheme for the current workspace. + * It uses information about whether we're running remote, in browser, + * or native combined with information about the current workspace to + * find the best default scheme. + */ + readonly defaultUriScheme: string; + /** * Converts the given path to a file URI to use for the target * environment. If the environment is connected to a remote, it @@ -60,6 +68,8 @@ export abstract class AbstractPathService implements IPathService { private resolveUserHome: Promise; private maybeUnresolvedUserHome: URI | undefined; + abstract readonly defaultUriScheme: string; + constructor( private localUserHome: URI, @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService diff --git a/src/vs/workbench/services/path/electron-browser/pathService.ts b/src/vs/workbench/services/path/electron-sandbox/pathService.ts similarity index 75% rename from src/vs/workbench/services/path/electron-browser/pathService.ts rename to src/vs/workbench/services/path/electron-sandbox/pathService.ts index b9d34aa803..a4dca2cd06 100644 --- a/src/vs/workbench/services/path/electron-browser/pathService.ts +++ b/src/vs/workbench/services/path/electron-sandbox/pathService.ts @@ -6,14 +6,17 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { IPathService, AbstractPathService } from 'vs/workbench/services/path/common/pathService'; -import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; +import { Schemas } from 'vs/base/common/network'; export class NativePathService extends AbstractPathService { + readonly defaultUriScheme = this.environmentService.configuration.remoteAuthority ? Schemas.vscodeRemote : Schemas.file; + constructor( @IRemoteAgentService remoteAgentService: IRemoteAgentService, - @IWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService + @IWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService ) { super(environmentService.userHome, remoteAgentService); } diff --git a/src/vs/workbench/services/preferences/browser/preferencesService.ts b/src/vs/workbench/services/preferences/browser/preferencesService.ts index 254777a58e..6c0cd38ab5 100644 --- a/src/vs/workbench/services/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/services/preferences/browser/preferencesService.ts @@ -7,7 +7,6 @@ import { Emitter } from 'vs/base/common/event'; import { parse } from 'vs/base/common/json'; import { Disposable } from 'vs/base/common/lifecycle'; import * as network from 'vs/base/common/network'; -import { assign } from 'vs/base/common/objects'; import { URI } from 'vs/base/common/uri'; import { getCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IPosition } from 'vs/editor/common/core/position'; @@ -346,7 +345,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic if (!options) { options = { pinned: true }; } else { - options = assign(options, { pinned: true }); + options = { ...options, pinned: true }; } if (openDefaultSettings) { @@ -368,7 +367,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic if (!options) { options = { pinned: true }; } else { - options = assign(options, { pinned: true }); + options = { ...options, pinned: true }; } const defaultPreferencesEditorInput = this.instantiationService.createInstance(DefaultPreferencesEditorInput, this.getDefaultSettingsResource(configurationTarget)); diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index b9838d403c..a193783bc2 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -228,9 +228,9 @@ export class ProgressService extends Disposable implements IProgressService { // Create a promise that we can resolve as needed // when the outside calls dispose on us let promiseResolve: () => void; - const promise = new Promise(resolve => promiseResolve = resolve); + const promise = new Promise(resolve => promiseResolve = resolve); - this.withWindowProgress({ + this.withWindowProgress({ location: ProgressLocation.Window, title: options.title ? parseLinkedText(options.title).toString() : undefined, // convert markdown links => string command: 'notifications.showList' diff --git a/src/vs/workbench/services/search/node/searchService.ts b/src/vs/workbench/services/search/electron-browser/searchService.ts similarity index 92% rename from src/vs/workbench/services/search/node/searchService.ts rename to src/vs/workbench/services/search/electron-browser/searchService.ts index eaf8fb01ef..ad4d7f0272 100644 --- a/src/vs/workbench/services/search/node/searchService.ts +++ b/src/vs/workbench/services/search/electron-browser/searchService.ts @@ -12,12 +12,14 @@ import { URI as uri } from 'vs/base/common/uri'; import { getNextTickChannel } from 'vs/base/parts/ipc/common/ipc'; import { Client, IIPCOptions } from 'vs/base/parts/ipc/node/ipc.cp'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IDebugParams, IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { parseSearchPort, INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { IDebugParams } from 'vs/platform/environment/common/environment'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; +import { parseSearchPort } from 'vs/platform/environment/node/environmentService'; import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; import { FileMatch, IFileMatch, IFileQuery, IProgressMessage, IRawSearchService, ISearchComplete, ISearchConfiguration, ISearchProgressItem, ISearchResultProvider, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, isSerializedSearchComplete, isSerializedSearchSuccess, ITextQuery, ISearchService, isFileMatch } from 'vs/workbench/services/search/common/search'; -import { SearchChannelClient } from './searchIpc'; +import { SearchChannelClient } from 'vs/workbench/services/search/node/searchIpc'; import { SearchService } from 'vs/workbench/services/search/common/searchService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -34,7 +36,7 @@ export class LocalSearchService extends SearchService { @ILogService logService: ILogService, @IExtensionService extensionService: IExtensionService, @IFileService fileService: IFileService, - @IEnvironmentService readonly environmentService: INativeEnvironmentService, + @IWorkbenchEnvironmentService readonly environmentService: INativeWorkbenchEnvironmentService, @IInstantiationService readonly instantiationService: IInstantiationService ) { super(modelService, editorService, telemetryService, logService, extensionService, fileService); diff --git a/src/vs/workbench/services/search/test/node/rawSearchService.test.ts b/src/vs/workbench/services/search/test/electron-browser/rawSearchService.test.ts similarity index 98% rename from src/vs/workbench/services/search/test/node/rawSearchService.test.ts rename to src/vs/workbench/services/search/test/electron-browser/rawSearchService.test.ts index 348a393fe3..cbbf2f02e2 100644 --- a/src/vs/workbench/services/search/test/node/rawSearchService.test.ts +++ b/src/vs/workbench/services/search/test/electron-browser/rawSearchService.test.ts @@ -11,13 +11,13 @@ import * as path from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; import { IFileQuery, IFileSearchStats, IFolderQuery, IProgressMessage, IRawFileMatch, ISearchEngine, ISearchEngineStats, ISearchEngineSuccess, ISearchProgressItem, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, ISerializedSearchSuccess, isFileMatch, QueryType } from 'vs/workbench/services/search/common/search'; import { IProgressCallback, SearchService as RawSearchService } from 'vs/workbench/services/search/node/rawSearchService'; -import { DiskSearch } from 'vs/workbench/services/search/node/searchService'; +import { DiskSearch } from 'vs/workbench/services/search/electron-browser/searchService'; const TEST_FOLDER_QUERIES = [ { folder: URI.file(path.normalize('/some/where')) } ]; -const TEST_FIXTURES = path.normalize(getPathFromAmdModule(require, './fixtures')); +const TEST_FIXTURES = path.normalize(getPathFromAmdModule(require, '../node/fixtures')); const MULTIROOT_QUERIES: IFolderQuery[] = [ { folder: URI.file(path.join(TEST_FIXTURES, 'examples')) }, { folder: URI.file(path.join(TEST_FIXTURES, 'more')) } diff --git a/src/vs/workbench/services/sharedProcess/electron-browser/sharedProcessService.ts b/src/vs/workbench/services/sharedProcess/electron-browser/sharedProcessService.ts index 842231ac75..1d4b228290 100644 --- a/src/vs/workbench/services/sharedProcess/electron-browser/sharedProcessService.ts +++ b/src/vs/workbench/services/sharedProcess/electron-browser/sharedProcessService.ts @@ -10,7 +10,7 @@ import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProces import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron'; export class SharedProcessService implements ISharedProcessService { diff --git a/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts index 6b951d787c..916a5b7628 100644 --- a/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts @@ -8,6 +8,7 @@ import { NullTelemetryService, combinedAppender, LogAppender } from 'vs/platform import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { IProductService } from 'vs/platform/product/common/productService'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc'; @@ -17,7 +18,6 @@ import { resolveWorkbenchCommonProperties } from 'vs/platform/telemetry/node/wor import { TelemetryService as BaseTelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; -import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; export class TelemetryService extends Disposable implements ITelemetryService { diff --git a/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts index b0557ab0ce..dfe9783380 100644 --- a/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts +++ b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts @@ -312,7 +312,7 @@ export abstract class AbstractTextMateService extends Disposable implements ITex grammarFactory.setTheme(theme, tokenColorMap); let colorMap = AbstractTextMateService._toColorMap(tokenColorMap); let cssRules = generateTokensCSSForColorMap(colorMap); - this._styleElement.innerHTML = cssRules; + this._styleElement.textContent = cssRules; TokenizationRegistry.setColorMap(colorMap); } diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 2f0dde9ee8..fd3cb04df4 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -34,7 +34,6 @@ import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/commo import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { UTF8, UTF8_with_bom, UTF16be, UTF16le, encodingExists, UTF8_BOM, detectEncodingByBOMFromBuffer, toEncodeReadable, toDecodeStream, IDecodeStreamResult } from 'vs/workbench/services/textfile/common/encoding'; import { consumeStream } from 'vs/base/common/stream'; import { IModeService } from 'vs/editor/common/services/modeService'; @@ -350,7 +349,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // path. This can happen if the file was created after the untitled file was opened. // See https://github.com/Microsoft/vscode/issues/67946 let write: boolean; - if (sourceModel instanceof UntitledTextEditorModel && sourceModel.hasAssociatedFilePath && targetExists && this.uriIdentityService.extUri.isEqual(target, toLocalResource(sourceModel.resource, this.environmentService.configuration.remoteAuthority))) { + if (sourceModel instanceof UntitledTextEditorModel && sourceModel.hasAssociatedFilePath && targetExists && this.uriIdentityService.extUri.isEqual(target, toLocalResource(sourceModel.resource, this.environmentService.configuration.remoteAuthority, this.pathService.defaultUriScheme))) { write = await this.confirmOverwrite(target); } else { write = true; @@ -431,7 +430,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // Untitled with associated file path if (model.hasAssociatedFilePath) { - return toLocalResource(resource, remoteAuthority); + return toLocalResource(resource, remoteAuthority, this.pathService.defaultUriScheme); } // Untitled without associated file path: use name @@ -529,7 +528,7 @@ export class EncodingOracle extends Disposable implements IResourceEncodings { constructor( @ITextResourceConfigurationService private textResourceConfigurationService: ITextResourceConfigurationService, - @IEnvironmentService private environmentService: IEnvironmentService, + @IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService, @IWorkspaceContextService private contextService: IWorkspaceContextService, @IFileService private fileService: IFileService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService diff --git a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts index f1e7508c3a..f1574edc1b 100644 --- a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts +++ b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts @@ -22,13 +22,13 @@ import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; -import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { ILogService } from 'vs/platform/log/common/log'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { IModeService } from 'vs/editor/common/services/modeService'; diff --git a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts deleted file mode 100644 index 45901847d7..0000000000 --- a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts +++ /dev/null @@ -1,733 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { EncodingMode } from 'vs/workbench/common/editor'; -import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; -import { TextFileEditorModelState, snapshotToString, isTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; -import { createFileEditorInput, workbenchInstantiationService, TestServiceAccessor, TestReadonlyTextFileEditorModel } from 'vs/workbench/test/browser/workbenchTestServices'; -import { toResource } from 'vs/base/test/common/utils'; -import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; -import { FileOperationResult, FileOperationError } from 'vs/platform/files/common/files'; -import { timeout } from 'vs/base/common/async'; -import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; -import { assertIsDefined } from 'vs/base/common/types'; -import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; - -function getLastModifiedTime(model: TextFileEditorModel): number { - const stat = model.getStat(); - - return stat ? stat.mtime : -1; -} - -suite('Files - TextFileEditorModel', () => { - - let instantiationService: IInstantiationService; - let accessor: TestServiceAccessor; - let content: string; - - setup(() => { - instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(TestServiceAccessor); - content = accessor.fileService.getContent(); - }); - - teardown(() => { - (accessor.textFileService.files).dispose(); - accessor.fileService.setContent(content); - }); - - test('basic events', async function () { - const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - - let onDidLoadCounter = 0; - model.onDidLoad(() => onDidLoadCounter++); - - await model.load(); - - assert.equal(onDidLoadCounter, 1); - - let onDidChangeContentCounter = 0; - model.onDidChangeContent(() => onDidChangeContentCounter++); - - let onDidChangeDirtyCounter = 0; - model.onDidChangeDirty(() => onDidChangeDirtyCounter++); - - model.updateTextEditorModel(createTextBufferFactory('bar')); - - assert.equal(onDidChangeContentCounter, 1); - assert.equal(onDidChangeDirtyCounter, 1); - - model.updateTextEditorModel(createTextBufferFactory('foo')); - - assert.equal(onDidChangeContentCounter, 2); - assert.equal(onDidChangeDirtyCounter, 1); - - await model.revert(); - - assert.equal(onDidChangeDirtyCounter, 2); - - model.dispose(); - }); - - test('isTextFileEditorModel', async function () { - const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - - assert.equal(isTextFileEditorModel(model), true); - - model.dispose(); - }); - - test('save', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - - await model.load(); - - assert.equal(accessor.workingCopyService.dirtyCount, 0); - - let savedEvent = false; - model.onDidSave(() => savedEvent = true); - - await model.save(); - assert.ok(!savedEvent); - - model.updateTextEditorModel(createTextBufferFactory('bar')); - assert.ok(getLastModifiedTime(model) <= Date.now()); - assert.ok(model.hasState(TextFileEditorModelState.DIRTY)); - - assert.equal(accessor.workingCopyService.dirtyCount, 1); - assert.equal(accessor.workingCopyService.isDirty(model.resource), true); - - let workingCopyEvent = false; - accessor.workingCopyService.onDidChangeDirty(e => { - if (e.resource.toString() === model.resource.toString()) { - workingCopyEvent = true; - } - }); - - const pendingSave = model.save(); - assert.ok(model.hasState(TextFileEditorModelState.PENDING_SAVE)); - - await pendingSave; - - assert.ok(model.hasState(TextFileEditorModelState.SAVED)); - assert.ok(!model.isDirty()); - assert.ok(savedEvent); - assert.ok(workingCopyEvent); - - assert.equal(accessor.workingCopyService.dirtyCount, 0); - assert.equal(accessor.workingCopyService.isDirty(model.resource), false); - - savedEvent = false; - - await model.save({ force: true }); - assert.ok(savedEvent); - - model.dispose(); - assert.ok(!accessor.modelService.getModel(model.resource)); - }); - - test('save - touching also emits saved event', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - - await model.load(); - - let savedEvent = false; - model.onDidSave(() => savedEvent = true); - - let workingCopyEvent = false; - accessor.workingCopyService.onDidChangeDirty(e => { - if (e.resource.toString() === model.resource.toString()) { - workingCopyEvent = true; - } - }); - - await model.save({ force: true }); - - assert.ok(savedEvent); - assert.ok(!workingCopyEvent); - - model.dispose(); - assert.ok(!accessor.modelService.getModel(model.resource)); - }); - - test('save - touching with error turns model dirty', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - - await model.load(); - - let saveErrorEvent = false; - model.onDidSaveError(() => saveErrorEvent = true); - - let savedEvent = false; - model.onDidSave(() => savedEvent = true); - - accessor.fileService.writeShouldThrowError = new Error('failed to write'); - try { - await model.save({ force: true }); - - assert.ok(model.hasState(TextFileEditorModelState.ERROR)); - assert.ok(model.isDirty()); - assert.ok(saveErrorEvent); - - assert.equal(accessor.workingCopyService.dirtyCount, 1); - assert.equal(accessor.workingCopyService.isDirty(model.resource), true); - } finally { - accessor.fileService.writeShouldThrowError = undefined; - } - - await model.save({ force: true }); - - assert.ok(savedEvent); - assert.ok(!model.isDirty()); - - model.dispose(); - assert.ok(!accessor.modelService.getModel(model.resource)); - }); - - test('save error (generic)', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - - await model.load(); - - model.updateTextEditorModel(createTextBufferFactory('bar')); - - let saveErrorEvent = false; - model.onDidSaveError(() => saveErrorEvent = true); - - accessor.fileService.writeShouldThrowError = new Error('failed to write'); - try { - const pendingSave = model.save(); - assert.ok(model.hasState(TextFileEditorModelState.PENDING_SAVE)); - - await pendingSave; - - assert.ok(model.hasState(TextFileEditorModelState.ERROR)); - assert.ok(model.isDirty()); - assert.ok(saveErrorEvent); - - assert.equal(accessor.workingCopyService.dirtyCount, 1); - assert.equal(accessor.workingCopyService.isDirty(model.resource), true); - - model.dispose(); - } finally { - accessor.fileService.writeShouldThrowError = undefined; - } - }); - - test('save error (conflict)', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - - await model.load(); - - model.updateTextEditorModel(createTextBufferFactory('bar')); - - let saveErrorEvent = false; - model.onDidSaveError(() => saveErrorEvent = true); - - accessor.fileService.writeShouldThrowError = new FileOperationError('save conflict', FileOperationResult.FILE_MODIFIED_SINCE); - try { - const pendingSave = model.save(); - assert.ok(model.hasState(TextFileEditorModelState.PENDING_SAVE)); - - await pendingSave; - - assert.ok(model.hasState(TextFileEditorModelState.CONFLICT)); - assert.ok(model.isDirty()); - assert.ok(saveErrorEvent); - - assert.equal(accessor.workingCopyService.dirtyCount, 1); - assert.equal(accessor.workingCopyService.isDirty(model.resource), true); - - model.dispose(); - } finally { - accessor.fileService.writeShouldThrowError = undefined; - } - }); - - test('setEncoding - encode', function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - - let encodingEvent = false; - model.onDidChangeEncoding(() => encodingEvent = true); - - model.setEncoding('utf8', EncodingMode.Encode); // no-op - assert.equal(getLastModifiedTime(model), -1); - - assert.ok(!encodingEvent); - - model.setEncoding('utf16', EncodingMode.Encode); - - assert.ok(encodingEvent); - - assert.ok(getLastModifiedTime(model) <= Date.now()); // indicates model was saved due to encoding change - - model.dispose(); - }); - - test('setEncoding - decode', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - - model.setEncoding('utf16', EncodingMode.Decode); - - await timeout(0); - assert.ok(model.isResolved()); // model got loaded due to decoding - model.dispose(); - }); - - test('create with mode', async function () { - const mode = 'text-file-model-test'; - ModesRegistry.registerLanguage({ - id: mode, - }); - - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', mode); - - await model.load(); - - assert.equal(model.textEditorModel!.getModeId(), mode); - - model.dispose(); - assert.ok(!accessor.modelService.getModel(model.resource)); - }); - - test('disposes when underlying model is destroyed', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - - await model.load(); - - model.textEditorModel!.dispose(); - assert.ok(model.isDisposed()); - }); - - test('Load does not trigger save', async function () { - const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index.txt'), 'utf8', undefined); - assert.ok(model.hasState(TextFileEditorModelState.SAVED)); - - model.onDidSave(() => assert.fail()); - model.onDidChangeDirty(() => assert.fail()); - - await model.load(); - assert.ok(model.isResolved()); - model.dispose(); - assert.ok(!accessor.modelService.getModel(model.resource)); - }); - - test('Load returns dirty model as long as model is dirty', async function () { - const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - - await model.load(); - model.updateTextEditorModel(createTextBufferFactory('foo')); - assert.ok(model.isDirty()); - assert.ok(model.hasState(TextFileEditorModelState.DIRTY)); - - await model.load(); - assert.ok(model.isDirty()); - model.dispose(); - }); - - test('Revert', async function () { - let eventCounter = 0; - - const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - - model.onDidRevert(() => eventCounter++); - - let workingCopyEvent = false; - accessor.workingCopyService.onDidChangeDirty(e => { - if (e.resource.toString() === model.resource.toString()) { - workingCopyEvent = true; - } - }); - - await model.load(); - model.updateTextEditorModel(createTextBufferFactory('foo')); - assert.ok(model.isDirty()); - - assert.equal(accessor.workingCopyService.dirtyCount, 1); - assert.equal(accessor.workingCopyService.isDirty(model.resource), true); - - await model.revert(); - assert.ok(!model.isDirty()); - assert.equal(model.textEditorModel!.getValue(), 'Hello Html'); - assert.equal(eventCounter, 1); - - assert.ok(workingCopyEvent); - assert.equal(accessor.workingCopyService.dirtyCount, 0); - assert.equal(accessor.workingCopyService.isDirty(model.resource), false); - - model.dispose(); - }); - - test('Revert (soft)', async function () { - let eventCounter = 0; - - const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - - model.onDidRevert(() => eventCounter++); - - let workingCopyEvent = false; - accessor.workingCopyService.onDidChangeDirty(e => { - if (e.resource.toString() === model.resource.toString()) { - workingCopyEvent = true; - } - }); - - await model.load(); - model.updateTextEditorModel(createTextBufferFactory('foo')); - assert.ok(model.isDirty()); - - assert.equal(accessor.workingCopyService.dirtyCount, 1); - assert.equal(accessor.workingCopyService.isDirty(model.resource), true); - - await model.revert({ soft: true }); - assert.ok(!model.isDirty()); - assert.equal(model.textEditorModel!.getValue(), 'foo'); - assert.equal(eventCounter, 1); - - assert.ok(workingCopyEvent); - assert.equal(accessor.workingCopyService.dirtyCount, 0); - assert.equal(accessor.workingCopyService.isDirty(model.resource), false); - - model.dispose(); - }); - - test('Undo to saved state turns model non-dirty', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - await model.load(); - model.updateTextEditorModel(createTextBufferFactory('Hello Text')); - assert.ok(model.isDirty()); - - model.textEditorModel!.undo(); - assert.ok(!model.isDirty()); - }); - - test('Load and undo turns model dirty', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - await model.load(); - accessor.fileService.setContent('Hello Change'); - - await model.load(); - model.textEditorModel!.undo(); - assert.ok(model.isDirty()); - - assert.equal(accessor.workingCopyService.dirtyCount, 1); - assert.equal(accessor.workingCopyService.isDirty(model.resource), true); - }); - - test('Update Dirty', async function () { - let eventCounter = 0; - - const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - - model.setDirty(true); - assert.ok(!model.isDirty()); // needs to be resolved - - await model.load(); - model.updateTextEditorModel(createTextBufferFactory('foo')); - assert.ok(model.isDirty()); - - await model.revert({ soft: true }); - assert.ok(!model.isDirty()); - - model.onDidChangeDirty(() => eventCounter++); - - let workingCopyEvent = false; - accessor.workingCopyService.onDidChangeDirty(e => { - if (e.resource.toString() === model.resource.toString()) { - workingCopyEvent = true; - } - }); - - model.setDirty(true); - assert.ok(model.isDirty()); - assert.equal(eventCounter, 1); - assert.ok(workingCopyEvent); - - model.setDirty(false); - assert.ok(!model.isDirty()); - assert.equal(eventCounter, 2); - - model.dispose(); - }); - - test('No Dirty or saving for readonly models', async function () { - let workingCopyEvent = false; - accessor.workingCopyService.onDidChangeDirty(e => { - if (e.resource.toString() === model.resource.toString()) { - workingCopyEvent = true; - } - }); - - const model = instantiationService.createInstance(TestReadonlyTextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - - let saveEvent = false; - model.onDidSave(() => { - saveEvent = true; - }); - - await model.load(); - model.updateTextEditorModel(createTextBufferFactory('foo')); - assert.ok(!model.isDirty()); - - await model.save({ force: true }); - assert.equal(saveEvent, false); - - await model.revert({ soft: true }); - assert.ok(!model.isDirty()); - - assert.ok(!workingCopyEvent); - - model.dispose(); - }); - - test('File not modified error is handled gracefully', async function () { - let model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - - await model.load(); - - const mtime = getLastModifiedTime(model); - accessor.textFileService.setResolveTextContentErrorOnce(new FileOperationError('error', FileOperationResult.FILE_NOT_MODIFIED_SINCE)); - - model = await model.load() as TextFileEditorModel; - - assert.ok(model); - assert.equal(getLastModifiedTime(model), mtime); - model.dispose(); - }); - - test('Load error is handled gracefully if model already exists', async function () { - let model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - - await model.load(); - accessor.textFileService.setResolveTextContentErrorOnce(new FileOperationError('error', FileOperationResult.FILE_NOT_FOUND)); - - model = await model.load() as TextFileEditorModel; - assert.ok(model); - model.dispose(); - }); - - test('save() and isDirty() - proper with check for mtimes', async function () { - const input1 = createFileEditorInput(instantiationService, toResource.call(this, '/path/index_async2.txt')); - const input2 = createFileEditorInput(instantiationService, toResource.call(this, '/path/index_async.txt')); - - const model1 = await input1.resolve() as TextFileEditorModel; - const model2 = await input2.resolve() as TextFileEditorModel; - - model1.updateTextEditorModel(createTextBufferFactory('foo')); - - const m1Mtime = assertIsDefined(model1.getStat()).mtime; - const m2Mtime = assertIsDefined(model2.getStat()).mtime; - assert.ok(m1Mtime > 0); - assert.ok(m2Mtime > 0); - - assert.ok(accessor.textFileService.isDirty(toResource.call(this, '/path/index_async2.txt'))); - assert.ok(!accessor.textFileService.isDirty(toResource.call(this, '/path/index_async.txt'))); - - model2.updateTextEditorModel(createTextBufferFactory('foo')); - assert.ok(accessor.textFileService.isDirty(toResource.call(this, '/path/index_async.txt'))); - - await timeout(10); - await accessor.textFileService.save(toResource.call(this, '/path/index_async.txt')); - await accessor.textFileService.save(toResource.call(this, '/path/index_async2.txt')); - assert.ok(!accessor.textFileService.isDirty(toResource.call(this, '/path/index_async.txt'))); - assert.ok(!accessor.textFileService.isDirty(toResource.call(this, '/path/index_async2.txt'))); - assert.ok(assertIsDefined(model1.getStat()).mtime > m1Mtime); - assert.ok(assertIsDefined(model2.getStat()).mtime > m2Mtime); - - model1.dispose(); - model2.dispose(); - }); - - test('Save Participant', async function () { - let eventCounter = 0; - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - - model.onDidSave(() => { - assert.equal(snapshotToString(model.createSnapshot()!), eventCounter === 1 ? 'bar' : 'foobar'); - assert.ok(!model.isDirty()); - eventCounter++; - }); - - const participant = accessor.textFileService.files.addSaveParticipant({ - participate: async model => { - assert.ok(model.isDirty()); - (model as TextFileEditorModel).updateTextEditorModel(createTextBufferFactory('bar')); - assert.ok(model.isDirty()); - eventCounter++; - } - }); - - await model.load(); - model.updateTextEditorModel(createTextBufferFactory('foo')); - assert.ok(model.isDirty()); - - await model.save(); - assert.equal(eventCounter, 2); - - participant.dispose(); - model.updateTextEditorModel(createTextBufferFactory('foobar')); - assert.ok(model.isDirty()); - - await model.save(); - assert.equal(eventCounter, 3); - - model.dispose(); - }); - - test('Save Participant - skip', async function () { - let eventCounter = 0; - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - - const participant = accessor.textFileService.files.addSaveParticipant({ - participate: async () => { - eventCounter++; - } - }); - - await model.load(); - model.updateTextEditorModel(createTextBufferFactory('foo')); - - await model.save({ skipSaveParticipants: true }); - assert.equal(eventCounter, 0); - - participant.dispose(); - model.dispose(); - }); - - test('Save Participant, async participant', async function () { - let eventCounter = 0; - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - - model.onDidSave(() => { - assert.ok(!model.isDirty()); - eventCounter++; - }); - - const participant = accessor.textFileService.files.addSaveParticipant({ - participate: model => { - assert.ok(model.isDirty()); - (model as TextFileEditorModel).updateTextEditorModel(createTextBufferFactory('bar')); - assert.ok(model.isDirty()); - eventCounter++; - - return timeout(10); - } - }); - - await model.load(); - model.updateTextEditorModel(createTextBufferFactory('foo')); - - const now = Date.now(); - await model.save(); - assert.equal(eventCounter, 2); - assert.ok(Date.now() - now >= 10); - - model.dispose(); - participant.dispose(); - }); - - test('Save Participant, bad participant', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - - const participant = accessor.textFileService.files.addSaveParticipant({ - participate: async () => { - new Error('boom'); - } - }); - - await model.load(); - model.updateTextEditorModel(createTextBufferFactory('foo')); - - await model.save(); - - model.dispose(); - participant.dispose(); - }); - - test('Save Participant, participant cancelled when saved again', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - - let participations: boolean[] = []; - - const participant = accessor.textFileService.files.addSaveParticipant({ - participate: async (model, context, progress, token) => { - await timeout(10); - - if (!token.isCancellationRequested) { - participations.push(true); - } - } - }); - - await model.load(); - - model.updateTextEditorModel(createTextBufferFactory('foo')); - const p1 = model.save(); - - model.updateTextEditorModel(createTextBufferFactory('foo 1')); - const p2 = model.save(); - - model.updateTextEditorModel(createTextBufferFactory('foo 2')); - const p3 = model.save(); - - model.updateTextEditorModel(createTextBufferFactory('foo 3')); - const p4 = model.save(); - - await Promise.all([p1, p2, p3, p4]); - assert.equal(participations.length, 1); - - model.dispose(); - participant.dispose(); - }); - - test('Save Participant, calling save from within is unsupported but does not explode (sync save)', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - - await testSaveFromSaveParticipant(model, false); - - model.dispose(); - }); - - test('Save Participant, calling save from within is unsupported but does not explode (async save)', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - - await testSaveFromSaveParticipant(model, true); - - model.dispose(); - }); - - async function testSaveFromSaveParticipant(model: TextFileEditorModel, async: boolean): Promise { - let savePromise: Promise; - let breakLoop = false; - - const participant = accessor.textFileService.files.addSaveParticipant({ - participate: async model => { - if (breakLoop) { - return; - } - - breakLoop = true; - - if (async) { - await timeout(10); - } - const newSavePromise = model.save(); - - // assert that this is the same promise as the outer one - assert.equal(savePromise, newSavePromise); - } - }); - - await model.load(); - model.updateTextEditorModel(createTextBufferFactory('foo')); - - savePromise = model.save(); - await savePromise; - - participant.dispose(); - } -}); diff --git a/src/vs/workbench/services/textfile/test/browser/textFileEditorModelManager.test.ts b/src/vs/workbench/services/textfile/test/browser/textFileEditorModelManager.test.ts deleted file mode 100644 index b04d7ad3cf..0000000000 --- a/src/vs/workbench/services/textfile/test/browser/textFileEditorModelManager.test.ts +++ /dev/null @@ -1,278 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import { URI } from 'vs/base/common/uri'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; -import { workbenchInstantiationService, TestServiceAccessor, TestTextFileEditorModelManager } from 'vs/workbench/test/browser/workbenchTestServices'; -import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; -import { FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files'; -import { toResource } from 'vs/base/test/common/utils'; -import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; -import { ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; -import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; -import { extUri } from 'vs/base/common/resources'; -import { timeout } from 'vs/base/common/async'; - -suite('Files - TextFileEditorModelManager', () => { - - let instantiationService: IInstantiationService; - let accessor: TestServiceAccessor; - - setup(() => { - instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(TestServiceAccessor); - }); - - test('add, remove, clear, get, getAll', function () { - const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager); - - const model1: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random1.txt'), 'utf8', undefined); - const model2: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random2.txt'), 'utf8', undefined); - const model3: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random3.txt'), 'utf8', undefined); - - manager.add(URI.file('/test.html'), model1); - manager.add(URI.file('/some/other.html'), model2); - manager.add(URI.file('/some/this.txt'), model3); - - const fileUpper = URI.file('/TEST.html'); - - assert(!manager.get(URI.file('foo'))); - assert.strictEqual(manager.get(URI.file('/test.html')), model1); - - assert.ok(!manager.get(fileUpper)); - - let results = manager.models; - assert.strictEqual(3, results.length); - - let result = manager.get(URI.file('/yes')); - assert.ok(!result); - - result = manager.get(URI.file('/some/other.txt')); - assert.ok(!result); - - result = manager.get(URI.file('/some/other.html')); - assert.ok(result); - - result = manager.get(fileUpper); - assert.ok(!result); - - manager.remove(URI.file('')); - - results = manager.models; - assert.strictEqual(3, results.length); - - manager.remove(URI.file('/some/other.html')); - results = manager.models; - assert.strictEqual(2, results.length); - - manager.remove(fileUpper); - results = manager.models; - assert.strictEqual(2, results.length); - - manager.clear(); - results = manager.models; - assert.strictEqual(0, results.length); - - model1.dispose(); - model2.dispose(); - model3.dispose(); - }); - - test('resolve', async () => { - const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager); - const resource = URI.file('/test.html'); - const encoding = 'utf8'; - - const events: ITextFileEditorModel[] = []; - const listener = manager.onDidCreate(model => { - events.push(model); - }); - - const modelPromise = manager.resolve(resource, { encoding }); - assert.ok(manager.get(resource)); // model known even before resolved() - - const model = await modelPromise; - assert.ok(model); - assert.equal(model.getEncoding(), encoding); - assert.equal(manager.get(resource), model); - - const model2 = await manager.resolve(resource, { encoding }); - assert.equal(model2, model); - model.dispose(); - - const model3 = await manager.resolve(resource, { encoding }); - assert.notEqual(model3, model2); - assert.equal(manager.get(resource), model3); - model3.dispose(); - - assert.equal(events.length, 2); - assert.equal(events[0].resource.toString(), model.resource.toString()); - assert.equal(events[1].resource.toString(), model2.resource.toString()); - - listener.dispose(); - }); - - test('removed from cache when model disposed', function () { - const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager); - - const model1: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random1.txt'), 'utf8', undefined); - const model2: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random2.txt'), 'utf8', undefined); - const model3: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random3.txt'), 'utf8', undefined); - - manager.add(URI.file('/test.html'), model1); - manager.add(URI.file('/some/other.html'), model2); - manager.add(URI.file('/some/this.txt'), model3); - - assert.strictEqual(manager.get(URI.file('/test.html')), model1); - - model1.dispose(); - assert(!manager.get(URI.file('/test.html'))); - - model2.dispose(); - model3.dispose(); - }); - - test('events', async function () { - const manager: TextFileEditorModelManager = instantiationService.createInstance(TextFileEditorModelManager); - - const resource1 = toResource.call(this, '/path/index.txt'); - const resource2 = toResource.call(this, '/path/other.txt'); - - let loadedCounter = 0; - let gotDirtyCounter = 0; - let gotNonDirtyCounter = 0; - let revertedCounter = 0; - let savedCounter = 0; - let encodingCounter = 0; - - manager.onDidLoad(({ model }) => { - if (model.resource.toString() === resource1.toString()) { - loadedCounter++; - } - }); - - manager.onDidChangeDirty(model => { - if (model.resource.toString() === resource1.toString()) { - if (model.isDirty()) { - gotDirtyCounter++; - } else { - gotNonDirtyCounter++; - } - } - }); - - manager.onDidRevert(model => { - if (model.resource.toString() === resource1.toString()) { - revertedCounter++; - } - }); - - manager.onDidSave(({ model }) => { - if (model.resource.toString() === resource1.toString()) { - savedCounter++; - } - }); - - manager.onDidChangeEncoding(model => { - if (model.resource.toString() === resource1.toString()) { - encodingCounter++; - } - }); - - const model1 = await manager.resolve(resource1, { encoding: 'utf8' }); - assert.equal(loadedCounter, 1); - - accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource: resource1, type: FileChangeType.DELETED }], extUri)); - accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource: resource1, type: FileChangeType.ADDED }], extUri)); - - const model2 = await manager.resolve(resource2, { encoding: 'utf8' }); - assert.equal(loadedCounter, 2); - - model1.updateTextEditorModel(createTextBufferFactory('changed')); - model1.updatePreferredEncoding('utf16'); - - await model1.revert(); - model1.updateTextEditorModel(createTextBufferFactory('changed again')); - - await model1.save(); - model1.dispose(); - model2.dispose(); - - await model1.revert(); - assert.equal(gotDirtyCounter, 2); - assert.equal(gotNonDirtyCounter, 2); - assert.equal(revertedCounter, 1); - assert.equal(savedCounter, 1); - assert.equal(encodingCounter, 2); - - model1.dispose(); - model2.dispose(); - assert.ok(!accessor.modelService.getModel(resource1)); - assert.ok(!accessor.modelService.getModel(resource2)); - }); - - test('disposing model takes it out of the manager', async function () { - const manager: TextFileEditorModelManager = instantiationService.createInstance(TextFileEditorModelManager); - - const resource = toResource.call(this, '/path/index_something.txt'); - - const model = await manager.resolve(resource, { encoding: 'utf8' }); - model.dispose(); - assert.ok(!manager.get(resource)); - assert.ok(!accessor.modelService.getModel(model.resource)); - manager.dispose(); - }); - - test('canDispose with dirty model', async function () { - const manager: TextFileEditorModelManager = instantiationService.createInstance(TextFileEditorModelManager); - - const resource = toResource.call(this, '/path/index_something.txt'); - - const model = await manager.resolve(resource, { encoding: 'utf8' }); - model.updateTextEditorModel(createTextBufferFactory('make dirty')); - - let canDisposePromise = manager.canDispose(model as TextFileEditorModel); - assert.ok(canDisposePromise instanceof Promise); - - let canDispose = false; - (async () => { - canDispose = await canDisposePromise; - })(); - - assert.equal(canDispose, false); - model.revert({ soft: true }); - - await timeout(0); - - assert.equal(canDispose, true); - - let canDispose2 = manager.canDispose(model as TextFileEditorModel); - assert.equal(canDispose2, true); - - manager.dispose(); - }); - - test('mode', async function () { - const mode = 'text-file-model-manager-test'; - ModesRegistry.registerLanguage({ - id: mode, - }); - - const manager: TextFileEditorModelManager = instantiationService.createInstance(TextFileEditorModelManager); - - const resource = toResource.call(this, '/path/index_something.txt'); - - let model = await manager.resolve(resource, { mode }); - assert.equal(model.textEditorModel!.getModeId(), mode); - - model = await manager.resolve(resource, { mode: 'text' }); - assert.equal(model.textEditorModel!.getModeId(), PLAINTEXT_MODE_ID); - - model.dispose(); - manager.dispose(); - }); -}); diff --git a/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts b/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts deleted file mode 100644 index d34d592864..0000000000 --- a/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts +++ /dev/null @@ -1,168 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import { workbenchInstantiationService, TestServiceAccessor, TestTextFileEditorModelManager } from 'vs/workbench/test/browser/workbenchTestServices'; -import { toResource } from 'vs/base/test/common/utils'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; -import { FileOperation } from 'vs/platform/files/common/files'; -import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; - -suite('Files - TextFileService', () => { - - let instantiationService: IInstantiationService; - let model: TextFileEditorModel; - let accessor: TestServiceAccessor; - - setup(() => { - instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(TestServiceAccessor); - }); - - teardown(() => { - model?.dispose(); - (accessor.textFileService.files).dispose(); - }); - - test('isDirty/getDirty - files and untitled', async function () { - model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.files).add(model.resource, model); - - await model.load(); - - assert.ok(!accessor.textFileService.isDirty(model.resource)); - model.textEditorModel!.setValue('foo'); - - assert.ok(accessor.textFileService.isDirty(model.resource)); - - const untitled = await accessor.textFileService.untitled.resolve(); - - assert.ok(!accessor.textFileService.isDirty(untitled.resource)); - untitled.textEditorModel.setValue('changed'); - - assert.ok(accessor.textFileService.isDirty(untitled.resource)); - - untitled.dispose(); - model.dispose(); - }); - - test('save - file', async function () { - model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.files).add(model.resource, model); - - await model.load(); - model.textEditorModel!.setValue('foo'); - assert.ok(accessor.textFileService.isDirty(model.resource)); - - const res = await accessor.textFileService.save(model.resource); - assert.equal(res?.toString(), model.resource.toString()); - assert.ok(!accessor.textFileService.isDirty(model.resource)); - }); - - test('saveAll - file', async function () { - model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.files).add(model.resource, model); - - await model.load(); - model.textEditorModel!.setValue('foo'); - assert.ok(accessor.textFileService.isDirty(model.resource)); - - const res = await accessor.textFileService.save(model.resource); - assert.equal(res?.toString(), model.resource.toString()); - assert.ok(!accessor.textFileService.isDirty(model.resource)); - }); - - test('saveAs - file', async function () { - model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.files).add(model.resource, model); - accessor.fileDialogService.setPickFileToSave(model.resource); - - await model.load(); - model.textEditorModel!.setValue('foo'); - assert.ok(accessor.textFileService.isDirty(model.resource)); - - const res = await accessor.textFileService.saveAs(model.resource); - assert.equal(res!.toString(), model.resource.toString()); - assert.ok(!accessor.textFileService.isDirty(model.resource)); - }); - - test('revert - file', async function () { - model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.files).add(model.resource, model); - accessor.fileDialogService.setPickFileToSave(model.resource); - - await model.load(); - model!.textEditorModel!.setValue('foo'); - assert.ok(accessor.textFileService.isDirty(model.resource)); - - await accessor.textFileService.revert(model.resource); - assert.ok(!accessor.textFileService.isDirty(model.resource)); - }); - - test('create does not overwrite existing model', async function () { - model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.files).add(model.resource, model); - - await model.load(); - model!.textEditorModel!.setValue('foo'); - assert.ok(accessor.textFileService.isDirty(model.resource)); - - let eventCounter = 0; - - const disposable1 = accessor.workingCopyFileService.addFileOperationParticipant({ - participate: async files => { - assert.equal(files[0].target, model.resource.toString()); - eventCounter++; - } - }); - - const disposable2 = accessor.workingCopyFileService.onDidRunWorkingCopyFileOperation(e => { - assert.equal(e.operation, FileOperation.CREATE); - assert.equal(e.files[0].target.toString(), model.resource.toString()); - eventCounter++; - }); - - await accessor.textFileService.create(model.resource, 'Foo'); - assert.ok(!accessor.textFileService.isDirty(model.resource)); - - assert.equal(eventCounter, 2); - - disposable1.dispose(); - disposable2.dispose(); - }); - - test('Filename Suggestion - Suggest prefix only when there are no relevant extensions', () => { - ModesRegistry.registerLanguage({ - id: 'plumbus0', - extensions: ['.one', '.two'] - }); - - let suggested = accessor.textFileService.suggestFilename('shleem', 'Untitled-1'); - assert.equal(suggested, 'Untitled-1'); - }); - - test('Filename Suggestion - Suggest prefix with first extension', () => { - ModesRegistry.registerLanguage({ - id: 'plumbus1', - extensions: ['.shleem', '.gazorpazorp'], - filenames: ['plumbus'] - }); - - let suggested = accessor.textFileService.suggestFilename('plumbus1', 'Untitled-1'); - assert.equal(suggested, 'Untitled-1.shleem'); - }); - - test('Filename Suggestion - Suggest filename if there are no extensions', () => { - ModesRegistry.registerLanguage({ - id: 'plumbus2', - filenames: ['plumbus', 'shleem', 'gazorpazorp'] - }); - - let suggested = accessor.textFileService.suggestFilename('plumbus2', 'Untitled-1'); - assert.equal(suggested, 'plumbus'); - }); - -}); diff --git a/src/vs/workbench/services/textmodelResolver/test/browser/textModelResolverService.test.ts b/src/vs/workbench/services/textmodelResolver/test/browser/textModelResolverService.test.ts deleted file mode 100644 index 124650d3d1..0000000000 --- a/src/vs/workbench/services/textmodelResolver/test/browser/textModelResolverService.test.ts +++ /dev/null @@ -1,212 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import { ITextModel } from 'vs/editor/common/model'; -import { URI } from 'vs/base/common/uri'; -import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; -import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { workbenchInstantiationService, TestServiceAccessor, TestTextFileEditorModelManager } from 'vs/workbench/test/browser/workbenchTestServices'; -import { toResource } from 'vs/base/test/common/utils'; -import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; -import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; -import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; -import { Event } from 'vs/base/common/event'; -import { timeout } from 'vs/base/common/async'; -import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; -import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; - -suite('Workbench - TextModelResolverService', () => { - - let instantiationService: IInstantiationService; - let accessor: TestServiceAccessor; - let model: TextFileEditorModel; - - setup(() => { - instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(TestServiceAccessor); - }); - - teardown(() => { - if (model) { - model.dispose(); - model = (undefined)!; - } - (accessor.textFileService.files).dispose(); - }); - - test('resolve resource', async () => { - const dispose = accessor.textModelResolverService.registerTextModelContentProvider('test', { - provideTextContent: function (resource: URI): Promise { - if (resource.scheme === 'test') { - let modelContent = 'Hello Test'; - let languageSelection = accessor.modeService.create('json'); - return Promise.resolve(accessor.modelService.createModel(modelContent, languageSelection, resource)); - } - - return Promise.resolve(null!); - } - }); - - let resource = URI.from({ scheme: 'test', authority: null!, path: 'thePath' }); - let input: ResourceEditorInput = instantiationService.createInstance(ResourceEditorInput, resource, 'The Name', 'The Description', undefined); - - const model = await input.resolve(); - assert.ok(model); - assert.equal(snapshotToString(((model as ResourceEditorModel).createSnapshot()!)), 'Hello Test'); - let disposed = false; - let disposedPromise = new Promise(resolve => { - Event.once(model.onDispose)(() => { - disposed = true; - resolve(); - }); - }); - input.dispose(); - - await disposedPromise; - assert.equal(disposed, true); - dispose.dispose(); - }); - - test('resolve file', async function () { - const textModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_resolver.txt'), 'utf8', undefined); - (accessor.textFileService.files).add(textModel.resource, textModel); - - await textModel.load(); - - const ref = await accessor.textModelResolverService.createModelReference(textModel.resource); - - const model = ref.object; - const editorModel = model.textEditorModel; - - assert.ok(editorModel); - assert.equal(editorModel.getValue(), 'Hello Html'); - - let disposed = false; - Event.once(model.onDispose)(() => { - disposed = true; - }); - - ref.dispose(); - await timeout(0); // due to the reference resolving the model first which is async - assert.equal(disposed, true); - }); - - test('resolved dirty file eventually disposes', async function () { - const textModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_resolver.txt'), 'utf8', undefined); - (accessor.textFileService.files).add(textModel.resource, textModel); - - const loadedModel = await textModel.load(); - - loadedModel.updateTextEditorModel(createTextBufferFactory('make dirty')); - - const ref = await accessor.textModelResolverService.createModelReference(textModel.resource); - - let disposed = false; - Event.once(loadedModel.onDispose)(() => { - disposed = true; - }); - - ref.dispose(); - await timeout(0); - assert.equal(disposed, false); // not disposed because model still dirty - - loadedModel.revert(); - - await timeout(0); - assert.equal(disposed, true); // now disposed because model got reverted - }); - - test('resolved dirty file does not dispose when new reference created', async function () { - const textModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_resolver.txt'), 'utf8', undefined); - (accessor.textFileService.files).add(textModel.resource, textModel); - - const loadedModel = await textModel.load(); - - loadedModel.updateTextEditorModel(createTextBufferFactory('make dirty')); - - const ref1 = await accessor.textModelResolverService.createModelReference(textModel.resource); - - let disposed = false; - Event.once(loadedModel.onDispose)(() => { - disposed = true; - }); - - ref1.dispose(); - await timeout(0); - assert.equal(disposed, false); // not disposed because model still dirty - - const ref2 = await accessor.textModelResolverService.createModelReference(textModel.resource); - - loadedModel.revert(); - - await timeout(0); - assert.equal(disposed, false); // not disposed because we got another ref meanwhile - - ref2.dispose(); - - await timeout(0); - assert.equal(disposed, true); // now disposed because last ref got disposed - }); - - test('resolve untitled', async () => { - const service = accessor.untitledTextEditorService; - const untitledModel = service.create(); - const input = instantiationService.createInstance(UntitledTextEditorInput, untitledModel); - - await input.resolve(); - const ref = await accessor.textModelResolverService.createModelReference(input.resource); - const model = ref.object; - assert.equal(untitledModel, model); - const editorModel = model.textEditorModel; - assert.ok(editorModel); - ref.dispose(); - input.dispose(); - model.dispose(); - }); - - test('even loading documents should be refcounted', async () => { - let resolveModel!: Function; - let waitForIt = new Promise(c => resolveModel = c); - - const disposable = accessor.textModelResolverService.registerTextModelContentProvider('test', { - provideTextContent: async (resource: URI): Promise => { - await waitForIt; - - let modelContent = 'Hello Test'; - let languageSelection = accessor.modeService.create('json'); - return accessor.modelService.createModel(modelContent, languageSelection, resource); - } - }); - - const uri = URI.from({ scheme: 'test', authority: null!, path: 'thePath' }); - - const modelRefPromise1 = accessor.textModelResolverService.createModelReference(uri); - const modelRefPromise2 = accessor.textModelResolverService.createModelReference(uri); - - resolveModel(); - - const modelRef1 = await modelRefPromise1; - const model1 = modelRef1.object; - const modelRef2 = await modelRefPromise2; - const model2 = modelRef2.object; - const textModel = model1.textEditorModel; - - assert.equal(model1, model2, 'they are the same model'); - assert(!textModel.isDisposed(), 'the text model should not be disposed'); - - modelRef1.dispose(); - assert(!textModel.isDisposed(), 'the text model should still not be disposed'); - - let p1 = new Promise(resolve => textModel.onWillDispose(resolve)); - modelRef2.dispose(); - - await p1; - assert(textModel.isDisposed(), 'the text model should finally be disposed'); - - disposable.dispose(); - }); -}); diff --git a/src/vs/workbench/services/themes/browser/browserHostColorSchemeService.ts b/src/vs/workbench/services/themes/browser/browserHostColorSchemeService.ts new file mode 100644 index 0000000000..e0d65ff7cc --- /dev/null +++ b/src/vs/workbench/services/themes/browser/browserHostColorSchemeService.ts @@ -0,0 +1,54 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from 'vs/base/common/event'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IHostColorSchemeService } from 'vs/workbench/services/themes/common/hostColorSchemeService'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; + +export class BrowserHostColorSchemeService extends Disposable implements IHostColorSchemeService { + + declare readonly _serviceBrand: undefined; + + private readonly _onDidSchemeChangeEvent = this._register(new Emitter()); + + constructor( + @IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService + ) { + super(); + + this.registerListeners(); + } + + private registerListeners(): void { + + window.matchMedia('(prefers-color-scheme: dark)').addListener(() => { + this._onDidSchemeChangeEvent.fire(); + }); + window.matchMedia('(forced-colors: active)').addListener(() => { + this._onDidSchemeChangeEvent.fire(); + }); + } + + get onDidChangeColorScheme(): Event { + return this._onDidSchemeChangeEvent.event; + } + + get colorScheme(): ColorScheme { + if (window.matchMedia(`(forced-colors: active)`).matches) { + return ColorScheme.HIGH_CONTRAST; + } else if (window.matchMedia(`(prefers-color-scheme: light)`).matches) { + return ColorScheme.LIGHT; + } else if (window.matchMedia(`(prefers-color-scheme: dark)`).matches) { + return ColorScheme.DARK; + } + return this.environmentService.configuration.colorScheme; + } + +} + +registerSingleton(IHostColorSchemeService, BrowserHostColorSchemeService, true); diff --git a/src/vs/workbench/services/themes/browser/fileIconThemeData.ts b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts index 5946a29f69..0ed936e5c5 100644 --- a/src/vs/workbench/services/themes/browser/fileIconThemeData.ts +++ b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts @@ -377,5 +377,5 @@ function _processIconThemeDocument(id: string, iconThemeDocumentLocation: URI, i return result; } function escapeCSS(str: string) { - return (window)['CSS'].escape(str); + return window.CSS.escape(str); } diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index b8efba8d40..1f7b2faf56 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -13,7 +13,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import * as errors from 'vs/base/common/errors'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { ColorThemeData } from 'vs/workbench/services/themes/common/colorThemeData'; -import { IColorTheme, Extensions as ThemingExtensions, IThemingRegistry, ThemeType, LIGHT, DARK, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, Extensions as ThemingExtensions, IThemingRegistry } from 'vs/platform/theme/common/themeService'; import { Event, Emitter } from 'vs/base/common/event'; import { registerFileIconThemeSchemas } from 'vs/workbench/services/themes/common/fileIconThemeSchema'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; @@ -34,6 +34,9 @@ import { ProductIconThemeData, DEFAULT_PRODUCT_ICON_THEME_ID } from 'vs/workbenc import { registerProductIconThemeSchemas } from 'vs/workbench/services/themes/common/productIconThemeSchema'; import { ILogService } from 'vs/platform/log/common/log'; import { isWeb } from 'vs/base/common/platform'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; +import { IHostColorSchemeService } from 'vs/workbench/services/themes/common/hostColorSchemeService'; + // implementation @@ -92,7 +95,6 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { private readonly onProductIconThemeChange: Emitter; private readonly productIconThemeWatcher: ThemeFileWatcher; - private isOSInHighContrast: boolean; // tracking the high contrast state of the OS eventauilly should go out to a seperate service private themeSettingIdBeforeSchemeSwitch: string | undefined; constructor( @@ -104,13 +106,12 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { @IFileService private readonly fileService: IFileService, @IExtensionResourceLoaderService private readonly extensionResourceLoaderService: IExtensionResourceLoaderService, @IWorkbenchLayoutService readonly layoutService: IWorkbenchLayoutService, - @ILogService private readonly logService: ILogService + @ILogService private readonly logService: ILogService, + @IHostColorSchemeService private readonly hostColorService: IHostColorSchemeService, ) { this.container = layoutService.container; this.settings = new ThemeConfiguration(configurationService); - this.isOSInHighContrast = !!environmentService.configuration.highContrast; - this.colorThemeRegistry = new ThemeRegistry(extensionService, colorThemesExtPoint, ColorThemeData.fromExtensionTheme); this.colorThemeWatcher = new ThemeFileWatcher(fileService, environmentService, this.reloadCurrentColorTheme.bind(this)); this.onColorThemeChange = new Emitter({ leakWarningThreshold: 400 }); @@ -144,7 +145,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } } if (!themeData) { - themeData = ColorThemeData.createUnloadedThemeForThemeType(isWeb ? LIGHT : DARK); + themeData = ColorThemeData.createUnloadedThemeForThemeType(isWeb ? ColorScheme.LIGHT : ColorScheme.DARK); } themeData.setCustomizations(this.settings); this.applyTheme(themeData, undefined, true); @@ -217,14 +218,14 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { if (e.affectsConfiguration(ThemeSettings.DETECT_COLOR_SCHEME) || e.affectsConfiguration(ThemeSettings.DETECT_HC)) { this.handlePreferredSchemeUpdated(); } - if (e.affectsConfiguration(ThemeSettings.PREFERRED_DARK_THEME) && this.getPreferredColorScheme() === DARK) { - this.applyPreferredColorTheme(DARK); + if (e.affectsConfiguration(ThemeSettings.PREFERRED_DARK_THEME) && this.getPreferredColorScheme() === ColorScheme.DARK) { + this.applyPreferredColorTheme(ColorScheme.DARK); } - if (e.affectsConfiguration(ThemeSettings.PREFERRED_LIGHT_THEME) && this.getPreferredColorScheme() === LIGHT) { - this.applyPreferredColorTheme(LIGHT); + if (e.affectsConfiguration(ThemeSettings.PREFERRED_LIGHT_THEME) && this.getPreferredColorScheme() === ColorScheme.LIGHT) { + this.applyPreferredColorTheme(ColorScheme.LIGHT); } - if (e.affectsConfiguration(ThemeSettings.PREFERRED_HC_THEME) && this.getPreferredColorScheme() === HIGH_CONTRAST) { - this.applyPreferredColorTheme(HIGH_CONTRAST); + if (e.affectsConfiguration(ThemeSettings.PREFERRED_HC_THEME) && this.getPreferredColorScheme() === ColorScheme.HIGH_CONTRAST) { + this.applyPreferredColorTheme(ColorScheme.HIGH_CONTRAST); } if (e.affectsConfiguration(ThemeSettings.FILE_ICON_THEME)) { this.restoreFileIconTheme(); @@ -325,14 +326,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { // preferred scheme handling private installPreferredSchemeListener() { - window.matchMedia('(prefers-color-scheme: dark)').addListener(async () => this.handlePreferredSchemeUpdated()); - } - - public setOSHighContrast(highContrast: boolean): void { - if (this.isOSInHighContrast !== highContrast) { - this.isOSInHighContrast = highContrast; - this.handlePreferredSchemeUpdated(); - } + this.hostColorService.onDidChangeColorScheme(() => this.handlePreferredSchemeUpdated()); } private async handlePreferredSchemeUpdated() { @@ -357,23 +351,22 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { return undefined; } - private getPreferredColorScheme(): ThemeType | undefined { + private getPreferredColorScheme(): ColorScheme | undefined { const detectHCThemeSetting = this.configurationService.getValue(ThemeSettings.DETECT_HC); - if (this.isOSInHighContrast && detectHCThemeSetting) { - return HIGH_CONTRAST; + if (this.hostColorService.colorScheme === ColorScheme.HIGH_CONTRAST && detectHCThemeSetting) { + return ColorScheme.HIGH_CONTRAST; } if (this.configurationService.getValue(ThemeSettings.DETECT_COLOR_SCHEME)) { - if (window.matchMedia(`(prefers-color-scheme: light)`).matches) { - return LIGHT; - } else if (window.matchMedia(`(prefers-color-scheme: dark)`).matches) { - return DARK; + const osScheme = this.hostColorService.colorScheme; + if (osScheme !== ColorScheme.HIGH_CONTRAST) { + return osScheme; } } return undefined; } - private async applyPreferredColorTheme(type: ThemeType): Promise { - const settingId = type === DARK ? ThemeSettings.PREFERRED_DARK_THEME : type === LIGHT ? ThemeSettings.PREFERRED_LIGHT_THEME : ThemeSettings.PREFERRED_HC_THEME; + private async applyPreferredColorTheme(type: ColorScheme): Promise { + const settingId = type === ColorScheme.DARK ? ThemeSettings.PREFERRED_DARK_THEME : type === ColorScheme.LIGHT ? ThemeSettings.PREFERRED_LIGHT_THEME : ThemeSettings.PREFERRED_HC_THEME; const themeSettingId = this.configurationService.getValue(settingId); if (themeSettingId) { const theme = await this.colorThemeRegistry.findThemeBySettingsId(themeSettingId, undefined); @@ -446,6 +439,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } } }; + ruleCollector.addRule(`.monaco-workbench { forced-color-adjust: none; }`); themingRegistry.getThemingParticipants().forEach(p => p(themeData, ruleCollector, this.environmentService)); _applyRules([...cssRules].join('\n'), colorThemeRulesClassName); } @@ -692,10 +686,10 @@ function _applyRules(styleSheetContent: string, rulesClassName: string) { const elStyle = document.createElement('style'); elStyle.type = 'text/css'; elStyle.className = rulesClassName; - elStyle.innerHTML = styleSheetContent; + elStyle.textContent = styleSheetContent; document.head.appendChild(elStyle); } else { - (themeStyles[0]).innerHTML = styleSheetContent; + (themeStyles[0]).textContent = styleSheetContent; } } diff --git a/src/vs/workbench/services/themes/common/colorThemeData.ts b/src/vs/workbench/services/themes/common/colorThemeData.ts index c2752c4e57..5b471cbb84 100644 --- a/src/vs/workbench/services/themes/common/colorThemeData.ts +++ b/src/vs/workbench/services/themes/common/colorThemeData.ts @@ -14,7 +14,7 @@ import * as objects from 'vs/base/common/objects'; import * as arrays from 'vs/base/common/arrays'; import * as resources from 'vs/base/common/resources'; import { Extensions as ColorRegistryExtensions, IColorRegistry, ColorIdentifier, editorBackground, editorForeground } from 'vs/platform/theme/common/colorRegistry'; -import { ThemeType, ITokenStyle, getThemeTypeSelector } from 'vs/platform/theme/common/themeService'; +import { ITokenStyle, getThemeTypeSelector } from 'vs/platform/theme/common/themeService'; import { Registry } from 'vs/platform/registry/common/platform'; import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages'; import { URI } from 'vs/base/common/uri'; @@ -26,6 +26,7 @@ import { IExtensionResourceLoaderService } from 'vs/workbench/services/extension import { CharCode } from 'vs/base/common/charCode'; import { StorageScope, IStorageService } from 'vs/platform/storage/common/storage'; import { ThemeConfiguration } from 'vs/workbench/services/themes/common/themeConfiguration'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; let colorRegistry = Registry.as(ColorRegistryExtensions.ColorContribution); @@ -540,17 +541,17 @@ export class ColorThemeData implements IWorkbenchColorTheme { return this.id.split(' ')[0]; } - get type(): ThemeType { + get type(): ColorScheme { switch (this.baseTheme) { - case VS_LIGHT_THEME: return 'light'; - case VS_HC_THEME: return 'hc'; - default: return 'dark'; + case VS_LIGHT_THEME: return ColorScheme.LIGHT; + case VS_HC_THEME: return ColorScheme.HIGH_CONTRAST; + default: return ColorScheme.DARK; } } // constructors - static createUnloadedThemeForThemeType(themeType: ThemeType, colorMap?: { [id: string]: string }): ColorThemeData { + static createUnloadedThemeForThemeType(themeType: ColorScheme, colorMap?: { [id: string]: string }): ColorThemeData { return ColorThemeData.createUnloadedTheme(getThemeTypeSelector(themeType), colorMap); } diff --git a/src/vs/workbench/services/themes/common/hostColorSchemeService.ts b/src/vs/workbench/services/themes/common/hostColorSchemeService.ts new file mode 100644 index 0000000000..9b3048c9ab --- /dev/null +++ b/src/vs/workbench/services/themes/common/hostColorSchemeService.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vs/base/common/event'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; + +export const IHostColorSchemeService = createDecorator('hostColorSchemeService'); + +export interface IHostColorSchemeService { + + readonly _serviceBrand: undefined; + + readonly colorScheme: ColorScheme; + readonly onDidChangeColorScheme: Event; + +} diff --git a/src/vs/workbench/services/themes/common/themeConfiguration.ts b/src/vs/workbench/services/themes/common/themeConfiguration.ts index 89614728b1..157ddd66cf 100644 --- a/src/vs/workbench/services/themes/common/themeConfiguration.ts +++ b/src/vs/workbench/services/themes/common/themeConfiguration.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import * as types from 'vs/base/common/types'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationNode, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { textmateColorsSchemaId, textmateColorGroupSchemaId } from 'vs/workbench/services/themes/common/colorThemeSchema'; @@ -96,6 +96,13 @@ const productIconThemeSettingSchema: IConfigurationPropertySchema = { errorMessage: nls.localize('productIconThemeError', "Product icon theme is unknown or not installed.") }; +const detectHCSchemeSettingSchema: IConfigurationPropertySchema = { + type: 'boolean', + default: true, + description: nls.localize('autoDetectHighContrast', "If enabled, will automatically change to high contrast theme if the OS is using a high contrast theme."), + scope: ConfigurationScope.APPLICATION +}; + const themeSettingsConfiguration: IConfigurationNode = { id: 'workbench', order: 7.1, @@ -105,7 +112,6 @@ const themeSettingsConfiguration: IConfigurationNode = { [ThemeSettings.PREFERRED_DARK_THEME]: preferredDarkThemeSettingSchema, [ThemeSettings.PREFERRED_LIGHT_THEME]: preferredLightThemeSettingSchema, [ThemeSettings.PREFERRED_HC_THEME]: preferredHCThemeSettingSchema, - [ThemeSettings.DETECT_COLOR_SCHEME]: detectColorSchemeSettingSchema, [ThemeSettings.FILE_ICON_THEME]: fileIconThemeSettingSchema, [ThemeSettings.COLOR_CUSTOMIZATIONS]: colorCustomizationsSchema, [ThemeSettings.PRODUCT_ICON_THEME]: productIconThemeSettingSchema @@ -113,6 +119,17 @@ const themeSettingsConfiguration: IConfigurationNode = { }; configurationRegistry.registerConfiguration(themeSettingsConfiguration); +const themeSettingsWindowConfiguration: IConfigurationNode = { + id: 'window', + order: 8.1, + type: 'object', + properties: { + [ThemeSettings.DETECT_HC]: detectHCSchemeSettingSchema, + [ThemeSettings.DETECT_COLOR_SCHEME]: detectColorSchemeSettingSchema, + } +}; +configurationRegistry.registerConfiguration(themeSettingsWindowConfiguration); + function tokenGroupSettings(description: string): IJSONSchema { return { description, diff --git a/src/vs/workbench/services/themes/common/workbenchThemeService.ts b/src/vs/workbench/services/themes/common/workbenchThemeService.ts index fc1cd7aa18..7cd6fea329 100644 --- a/src/vs/workbench/services/themes/common/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/common/workbenchThemeService.ts @@ -77,8 +77,6 @@ export interface IWorkbenchThemeService extends IThemeService { getProductIconTheme(): IWorkbenchProductIconTheme; getProductIconThemes(): Promise; onDidProductIconThemeChange: Event; - - setOSHighContrast(highContrast: boolean): void; } export interface IColorCustomizations { diff --git a/src/vs/workbench/services/themes/electron-sandbox/nativeHostColorSchemeService.ts b/src/vs/workbench/services/themes/electron-sandbox/nativeHostColorSchemeService.ts new file mode 100644 index 0000000000..c813982abb --- /dev/null +++ b/src/vs/workbench/services/themes/electron-sandbox/nativeHostColorSchemeService.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter } from 'vs/base/common/event'; +import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; +import { IHostColorSchemeService } from 'vs/workbench/services/themes/common/hostColorSchemeService'; + +export class NativeHostColorSchemeService extends Disposable implements IHostColorSchemeService { + + declare readonly _serviceBrand: undefined; + + constructor( + @IElectronService private readonly electronService: IElectronService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService + ) { + super(); + + this.registerListeners(); + } + + private registerListeners(): void { + + // Color Scheme + this._register(this.electronService.onColorSchemeChange(scheme => { + this._colorScheme = scheme; + + this._onDidChangeColorScheme.fire(); + })); + } + + private readonly _onDidChangeColorScheme = this._register(new Emitter()); + readonly onDidChangeColorScheme = this._onDidChangeColorScheme.event; + + private _colorScheme: ColorScheme = this.environmentService.configuration.colorScheme; + get colorScheme() { return this._colorScheme; } + +} + +registerSingleton(IHostColorSchemeService, NativeHostColorSchemeService, true); diff --git a/src/vs/workbench/services/timer/electron-browser/timerService.ts b/src/vs/workbench/services/timer/electron-browser/timerService.ts index f231df9fb1..7a3ea19819 100644 --- a/src/vs/workbench/services/timer/electron-browser/timerService.ts +++ b/src/vs/workbench/services/timer/electron-browser/timerService.ts @@ -7,6 +7,7 @@ import { virtualMachineHint } from 'vs/base/node/id'; import * as os from 'os'; import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IUpdateService } from 'vs/platform/update/common/update'; @@ -15,7 +16,6 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { IStartupMetrics, AbstractTimerService, Writeable } from 'vs/workbench/services/timer/browser/timerService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; diff --git a/src/vs/workbench/services/userData/browser/userDataInit.ts b/src/vs/workbench/services/userData/browser/userDataInit.ts index e2bbb9bfab..01313c8e46 100644 --- a/src/vs/workbench/services/userData/browser/userDataInit.ts +++ b/src/vs/workbench/services/userData/browser/userDataInit.ts @@ -107,19 +107,23 @@ export class UserDataInitializationService implements IUserDataInitializationSer } async requiresInitialization(): Promise { + this.logService.trace(`UserDataInitializationService#requiresInitialization`); const userDataSyncStoreClient = await this.createUserDataSyncStoreClient(); return !!userDataSyncStoreClient; } async initializeRequiredResources(): Promise { + this.logService.trace(`UserDataInitializationService#initializeRequiredResources`); return this.initialize([SyncResource.Settings, SyncResource.GlobalState]); } async initializeOtherResources(): Promise { + this.logService.trace(`UserDataInitializationService#initializeOtherResources`); return this.initialize([SyncResource.Keybindings, SyncResource.Snippets]); } async initializeExtensions(instantiationService: IInstantiationService): Promise { + this.logService.trace(`UserDataInitializationService#initializeExtensions`); return this.initialize([SyncResource.Extensions], instantiationService); } diff --git a/src/vs/workbench/services/userData/common/fileUserDataProvider.ts b/src/vs/workbench/services/userData/common/fileUserDataProvider.ts index fae06b3cc6..b90740119c 100644 --- a/src/vs/workbench/services/userData/common/fileUserDataProvider.ts +++ b/src/vs/workbench/services/userData/common/fileUserDataProvider.ts @@ -7,7 +7,6 @@ import { Event, Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IFileSystemProviderWithFileReadWriteCapability, IFileChange, IWatchOptions, IStat, FileOverwriteOptions, FileType, FileWriteOptions, FileDeleteOptions, FileSystemProviderCapabilities, IFileSystemProviderWithOpenReadWriteCloseCapability, FileOpenOptions, hasReadWriteCapability, hasOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadStreamCapability, FileReadStreamOptions, hasFileReadStreamCapability } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; -import { BACKUPS } from 'vs/platform/environment/common/environment'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ReadableStreamEvents } from 'vs/base/common/stream'; @@ -26,11 +25,12 @@ export class FileUserDataProvider extends Disposable implements readonly onDidChangeFile: Event = this._onDidChangeFile.event; private readonly userDataHome: URI; + private readonly backupHome: URI | undefined; private extUri: ExtUri; constructor( private readonly fileSystemUserDataHome: URI, - private readonly fileSystemBackupsHome: URI, + private readonly fileSystemBackupsHome: URI | undefined, private readonly fileSystemProvider: IFileSystemProviderWithFileReadWriteCapability | IFileSystemProviderWithOpenReadWriteCloseCapability, environmentService: IWorkbenchEnvironmentService, private readonly logService: ILogService, @@ -38,6 +38,7 @@ export class FileUserDataProvider extends Disposable implements super(); this.userDataHome = environmentService.userRoamingDataHome; + this.backupHome = environmentService.backupWorkspaceHome; this.extUri = !!(this.capabilities & FileSystemProviderCapabilities.PathCaseSensitive) ? extUri : extUriIgnorePathCase; // update extUri as capabilites might change. @@ -139,10 +140,13 @@ export class FileUserDataProvider extends Disposable implements } private toFileSystemResource(userDataResource: URI): URI { - const relativePath = this.extUri.relativePath(this.userDataHome, userDataResource)!; - if (relativePath.startsWith(BACKUPS)) { - return this.extUri.joinPath(this.extUri.dirname(this.fileSystemBackupsHome), relativePath); + // Backup Resource + if (this.backupHome && this.fileSystemBackupsHome && this.extUri.isEqualOrParent(userDataResource, this.backupHome)) { + const relativePath = this.extUri.relativePath(this.backupHome, userDataResource); + return relativePath ? this.extUri.joinPath(this.fileSystemBackupsHome, relativePath) : this.fileSystemBackupsHome; } + + const relativePath = this.extUri.relativePath(this.userDataHome, userDataResource)!; return this.extUri.joinPath(this.fileSystemUserDataHome, relativePath); } @@ -151,9 +155,9 @@ export class FileUserDataProvider extends Disposable implements const relativePath = this.extUri.relativePath(this.fileSystemUserDataHome, fileSystemResource); return relativePath ? this.extUri.joinPath(this.userDataHome, relativePath) : this.userDataHome; } - if (this.extUri.isEqualOrParent(fileSystemResource, this.fileSystemBackupsHome)) { + if (this.backupHome && this.fileSystemBackupsHome && this.extUri.isEqualOrParent(fileSystemResource, this.fileSystemBackupsHome)) { const relativePath = this.extUri.relativePath(this.fileSystemBackupsHome, fileSystemResource); - return relativePath ? this.extUri.joinPath(this.userDataHome, BACKUPS, relativePath) : this.extUri.joinPath(this.userDataHome, BACKUPS); + return relativePath ? this.extUri.joinPath(this.backupHome, relativePath) : this.backupHome; } return null; } diff --git a/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts b/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts index 53beead3c9..c041b0352d 100644 --- a/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts +++ b/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts @@ -7,7 +7,6 @@ import * as assert from 'assert'; import * as os from 'os'; import * as path from 'vs/base/common/path'; import * as uuid from 'vs/base/common/uuid'; -import * as pfs from 'vs/base/node/pfs'; import { IFileService, FileChangeType, IFileChange, IFileSystemProviderWithFileReadWriteCapability, IStat, FileType, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; import { NullLogService } from 'vs/platform/log/common/log'; @@ -17,29 +16,21 @@ import { FileUserDataProvider } from 'vs/workbench/services/userData/common/file import { joinPath, dirname } from 'vs/base/common/resources'; import { VSBuffer } from 'vs/base/common/buffer'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; -import { BACKUPS } from 'vs/platform/environment/common/environment'; import { DisposableStore, IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { BrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { Emitter, Event } from 'vs/base/common/event'; import { timeout } from 'vs/base/common/async'; - -class TestBrowserWorkbenchEnvironmentService extends BrowserWorkbenchEnvironmentService { - - testUserRoamingDataHome!: URI; - - get userRoamingDataHome(): URI { - return this.testUserRoamingDataHome; - } -} +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; suite('FileUserDataProvider', () => { let testObject: IFileService; - let rootPath: string; - let userDataPath: string; - let backupsPath: string; - let userDataResource: URI; + let rootResource: URI; + let userDataHomeOnDisk: URI; + let backupWorkspaceHomeOnDisk: URI; + let environmentService: IWorkbenchEnvironmentService; const disposables = new DisposableStore(); + let fileUserDataProvider: FileUserDataProvider; setup(async () => { const logService = new NullLogService(); @@ -50,237 +41,238 @@ suite('FileUserDataProvider', () => { disposables.add(diskFileSystemProvider); disposables.add(testObject.registerProvider(Schemas.file, diskFileSystemProvider)); - rootPath = path.join(os.tmpdir(), 'vsctests', uuid.generateUuid()); - userDataPath = path.join(rootPath, 'user'); - backupsPath = path.join(rootPath, BACKUPS); - userDataResource = URI.file(userDataPath).with({ scheme: Schemas.userData }); - await Promise.all([pfs.mkdirp(userDataPath), pfs.mkdirp(backupsPath)]); + const workspaceId = 'workspaceId'; + environmentService = new BrowserWorkbenchEnvironmentService({ remoteAuthority: 'remote', workspaceId, logsPath: URI.file('logFile') }); - const environmentService = new TestBrowserWorkbenchEnvironmentService({ remoteAuthority: 'remote', workspaceId: 'workspaceId', logsPath: URI.file('logFile') }); - environmentService.testUserRoamingDataHome = userDataResource; + rootResource = URI.file(path.join(os.tmpdir(), 'vsctests', uuid.generateUuid())); + userDataHomeOnDisk = joinPath(rootResource, 'user'); + const backupHome = joinPath(rootResource, 'Backups'); + backupWorkspaceHomeOnDisk = joinPath(backupHome, workspaceId); + await Promise.all([testObject.createFolder(userDataHomeOnDisk), testObject.createFolder(backupWorkspaceHomeOnDisk)]); - const userDataFileSystemProvider = new FileUserDataProvider(URI.file(userDataPath), URI.file(backupsPath), diskFileSystemProvider, environmentService, logService); - disposables.add(userDataFileSystemProvider); - disposables.add(testObject.registerProvider(Schemas.userData, userDataFileSystemProvider)); + fileUserDataProvider = new FileUserDataProvider(userDataHomeOnDisk, backupWorkspaceHomeOnDisk, diskFileSystemProvider, environmentService, logService); + disposables.add(fileUserDataProvider); + disposables.add(testObject.registerProvider(Schemas.userData, fileUserDataProvider)); }); teardown(async () => { + fileUserDataProvider.dispose(); // need to dispose first, otherwise del will fail (https://github.com/microsoft/vscode/issues/106283) + await testObject.del(rootResource, { recursive: true }); disposables.clear(); - await pfs.rimraf(rootPath, pfs.RimRafMode.MOVE); }); test('exists return false when file does not exist', async () => { - const exists = await testObject.exists(joinPath(userDataResource, 'settings.json')); + const exists = await testObject.exists(environmentService.settingsResource); assert.equal(exists, false); }); test('read file throws error if not exist', async () => { try { - await testObject.readFile(joinPath(userDataResource, 'settings.json')); + await testObject.readFile(environmentService.settingsResource); assert.fail('Should fail since file does not exist'); } catch (e) { } }); test('read existing file', async () => { - await pfs.writeFile(path.join(userDataPath, 'settings.json'), '{}'); - const result = await testObject.readFile(joinPath(userDataResource, 'settings.json')); + await testObject.writeFile(joinPath(userDataHomeOnDisk, 'settings.json'), VSBuffer.fromString('{}')); + const result = await testObject.readFile(environmentService.settingsResource); assert.equal(result.value, '{}'); }); test('create file', async () => { - const resource = joinPath(userDataResource, 'settings.json'); + const resource = environmentService.settingsResource; const actual1 = await testObject.createFile(resource, VSBuffer.fromString('{}')); assert.equal(actual1.resource.toString(), resource.toString()); - const actual2 = await pfs.readFile(path.join(userDataPath, 'settings.json')); - assert.equal(actual2, '{}'); + const actual2 = await testObject.readFile(joinPath(userDataHomeOnDisk, 'settings.json')); + assert.equal(actual2.value.toString(), '{}'); }); test('write file creates the file if not exist', async () => { - const resource = joinPath(userDataResource, 'settings.json'); + const resource = environmentService.settingsResource; const actual1 = await testObject.writeFile(resource, VSBuffer.fromString('{}')); assert.equal(actual1.resource.toString(), resource.toString()); - const actual2 = await pfs.readFile(path.join(userDataPath, 'settings.json')); - assert.equal(actual2, '{}'); + const actual2 = await testObject.readFile(joinPath(userDataHomeOnDisk, 'settings.json')); + assert.equal(actual2.value.toString(), '{}'); }); test('write to existing file', async () => { - const resource = joinPath(userDataResource, 'settings.json'); - await pfs.writeFile(path.join(userDataPath, 'settings.json'), '{}'); + const resource = environmentService.settingsResource; + await testObject.writeFile(joinPath(userDataHomeOnDisk, 'settings.json'), VSBuffer.fromString('{}')); const actual1 = await testObject.writeFile(resource, VSBuffer.fromString('{a:1}')); assert.equal(actual1.resource.toString(), resource.toString()); - const actual2 = await pfs.readFile(path.join(userDataPath, 'settings.json')); - assert.equal(actual2, '{a:1}'); + const actual2 = await testObject.readFile(joinPath(userDataHomeOnDisk, 'settings.json')); + assert.equal(actual2.value.toString(), '{a:1}'); }); test('delete file', async () => { - await pfs.writeFile(path.join(userDataPath, 'settings.json'), ''); - await testObject.del(joinPath(userDataResource, 'settings.json')); - const result = await pfs.exists(path.join(userDataPath, 'settings.json')); + await testObject.writeFile(joinPath(userDataHomeOnDisk, 'settings.json'), VSBuffer.fromString('')); + await testObject.del(environmentService.settingsResource); + const result = await testObject.exists(joinPath(userDataHomeOnDisk, 'settings.json')); assert.equal(false, result); }); test('resolve file', async () => { - await pfs.writeFile(path.join(userDataPath, 'settings.json'), ''); - const result = await testObject.resolve(joinPath(userDataResource, 'settings.json')); + await testObject.writeFile(joinPath(userDataHomeOnDisk, 'settings.json'), VSBuffer.fromString('')); + const result = await testObject.resolve(environmentService.settingsResource); assert.ok(!result.isDirectory); assert.ok(result.children === undefined); }); test('exists return false for folder that does not exist', async () => { - const exists = await testObject.exists(joinPath(userDataResource, 'snippets')); + const exists = await testObject.exists(environmentService.snippetsHome); assert.equal(exists, false); }); test('exists return true for folder that exists', async () => { - await pfs.mkdirp(path.join(userDataPath, 'snippets')); - const exists = await testObject.exists(joinPath(userDataResource, 'snippets')); + await testObject.createFolder(joinPath(userDataHomeOnDisk, 'snippets')); + const exists = await testObject.exists(environmentService.snippetsHome); assert.equal(exists, true); }); test('read file throws error for folder', async () => { - await pfs.mkdirp(path.join(userDataPath, 'snippets')); + await testObject.createFolder(joinPath(userDataHomeOnDisk, 'snippets')); try { - await testObject.readFile(joinPath(userDataResource, 'snippets')); + await testObject.readFile(environmentService.snippetsHome); assert.fail('Should fail since read file is not supported for folders'); } catch (e) { } }); test('read file under folder', async () => { - await pfs.mkdirp(path.join(userDataPath, 'snippets')); - await pfs.writeFile(path.join(userDataPath, 'snippets', 'settings.json'), '{}'); - const resource = joinPath(userDataResource, 'snippets/settings.json'); + await testObject.createFolder(joinPath(userDataHomeOnDisk, 'snippets')); + await testObject.writeFile(joinPath(userDataHomeOnDisk, 'snippets', 'settings.json'), VSBuffer.fromString('{}')); + const resource = joinPath(environmentService.snippetsHome, 'settings.json'); const actual = await testObject.readFile(resource); assert.equal(actual.resource.toString(), resource.toString()); assert.equal(actual.value, '{}'); }); test('read file under sub folder', async () => { - await pfs.mkdirp(path.join(userDataPath, 'snippets', 'java')); - await pfs.writeFile(path.join(userDataPath, 'snippets', 'java', 'settings.json'), '{}'); - const resource = joinPath(userDataResource, 'snippets/java/settings.json'); + await testObject.createFolder(joinPath(userDataHomeOnDisk, 'snippets', 'java')); + await testObject.writeFile(joinPath(userDataHomeOnDisk, 'snippets', 'java', 'settings.json'), VSBuffer.fromString('{}')); + const resource = joinPath(environmentService.snippetsHome, 'java/settings.json'); const actual = await testObject.readFile(resource); assert.equal(actual.resource.toString(), resource.toString()); assert.equal(actual.value, '{}'); }); test('create file under folder that exists', async () => { - await pfs.mkdirp(path.join(userDataPath, 'snippets')); - const resource = joinPath(userDataResource, 'snippets/settings.json'); + await testObject.createFolder(joinPath(userDataHomeOnDisk, 'snippets')); + const resource = joinPath(environmentService.snippetsHome, 'settings.json'); const actual1 = await testObject.createFile(resource, VSBuffer.fromString('{}')); assert.equal(actual1.resource.toString(), resource.toString()); - const actual2 = await pfs.readFile(path.join(userDataPath, 'snippets', 'settings.json')); - assert.equal(actual2, '{}'); + const actual2 = await testObject.readFile(joinPath(userDataHomeOnDisk, 'snippets', 'settings.json')); + assert.equal(actual2.value.toString(), '{}'); }); test('create file under folder that does not exist', async () => { - const resource = joinPath(userDataResource, 'snippets/settings.json'); + const resource = joinPath(environmentService.snippetsHome, 'settings.json'); const actual1 = await testObject.createFile(resource, VSBuffer.fromString('{}')); assert.equal(actual1.resource.toString(), resource.toString()); - const actual2 = await pfs.readFile(path.join(userDataPath, 'snippets', 'settings.json')); - assert.equal(actual2, '{}'); + const actual2 = await testObject.readFile(joinPath(userDataHomeOnDisk, 'snippets', 'settings.json')); + assert.equal(actual2.value.toString(), '{}'); }); test('write to not existing file under container that exists', async () => { - await pfs.mkdirp(path.join(userDataPath, 'snippets')); - const resource = joinPath(userDataResource, 'snippets/settings.json'); + await testObject.createFolder(joinPath(userDataHomeOnDisk, 'snippets')); + const resource = joinPath(environmentService.snippetsHome, 'settings.json'); const actual1 = await testObject.writeFile(resource, VSBuffer.fromString('{}')); assert.equal(actual1.resource.toString(), resource.toString()); - const actual = await pfs.readFile(path.join(userDataPath, 'snippets', 'settings.json')); - assert.equal(actual, '{}'); + const actual = await testObject.readFile(joinPath(userDataHomeOnDisk, 'snippets', 'settings.json')); + assert.equal(actual.value.toString(), '{}'); }); test('write to not existing file under container that does not exists', async () => { - const resource = joinPath(userDataResource, 'snippets/settings.json'); + const resource = joinPath(environmentService.snippetsHome, 'settings.json'); const actual1 = await testObject.writeFile(resource, VSBuffer.fromString('{}')); assert.equal(actual1.resource.toString(), resource.toString()); - const actual = await pfs.readFile(path.join(userDataPath, 'snippets', 'settings.json')); - assert.equal(actual, '{}'); + const actual = await testObject.readFile(joinPath(userDataHomeOnDisk, 'snippets', 'settings.json')); + assert.equal(actual.value.toString(), '{}'); }); test('write to existing file under container', async () => { - await pfs.mkdirp(path.join(userDataPath, 'snippets')); - await pfs.writeFile(path.join(userDataPath, 'snippets', 'settings.json'), '{}'); - const resource = joinPath(userDataResource, 'snippets/settings.json'); + await testObject.createFolder(joinPath(userDataHomeOnDisk, 'snippets')); + await testObject.writeFile(joinPath(userDataHomeOnDisk, 'snippets', 'settings.json'), VSBuffer.fromString('{}')); + const resource = joinPath(environmentService.snippetsHome, 'settings.json'); const actual1 = await testObject.writeFile(resource, VSBuffer.fromString('{a:1}')); assert.equal(actual1.resource.toString(), resource.toString()); - const actual = await pfs.readFile(path.join(userDataPath, 'snippets', 'settings.json')); - assert.equal(actual.toString(), '{a:1}'); + const actual = await testObject.readFile(joinPath(userDataHomeOnDisk, 'snippets', 'settings.json')); + assert.equal(actual.value.toString(), '{a:1}'); }); test('write file under sub container', async () => { - const resource = joinPath(userDataResource, 'snippets/java/settings.json'); + const resource = joinPath(environmentService.snippetsHome, 'java/settings.json'); const actual1 = await testObject.writeFile(resource, VSBuffer.fromString('{}')); assert.equal(actual1.resource.toString(), resource.toString()); - const actual = await pfs.readFile(path.join(userDataPath, 'snippets', 'java', 'settings.json')); - assert.equal(actual, '{}'); + const actual = await testObject.readFile(joinPath(userDataHomeOnDisk, 'snippets', 'java', 'settings.json')); + assert.equal(actual.value.toString(), '{}'); }); test('delete throws error for folder that does not exist', async () => { try { - await testObject.del(joinPath(userDataResource, 'snippets')); + await testObject.del(environmentService.snippetsHome); assert.fail('Should fail the folder does not exist'); } catch (e) { } }); test('delete not existing file under container that exists', async () => { - await pfs.mkdirp(path.join(userDataPath, 'snippets')); + await testObject.createFolder(joinPath(userDataHomeOnDisk, 'snippets')); try { - await testObject.del(joinPath(userDataResource, 'snippets/settings.json')); + await testObject.del(joinPath(environmentService.snippetsHome, 'settings.json')); assert.fail('Should fail since file does not exist'); } catch (e) { } }); test('delete not existing file under container that does not exists', async () => { try { - await testObject.del(joinPath(userDataResource, 'snippets/settings.json')); + await testObject.del(joinPath(environmentService.snippetsHome, 'settings.json')); assert.fail('Should fail since file does not exist'); } catch (e) { } }); test('delete existing file under folder', async () => { - await pfs.mkdirp(path.join(userDataPath, 'snippets')); - await pfs.writeFile(path.join(userDataPath, 'snippets', 'settings.json'), '{}'); - await testObject.del(joinPath(userDataResource, 'snippets/settings.json')); - const exists = await pfs.exists(path.join(userDataPath, 'snippets', 'settings.json')); + await testObject.createFolder(joinPath(userDataHomeOnDisk, 'snippets')); + await testObject.writeFile(joinPath(userDataHomeOnDisk, 'snippets', 'settings.json'), VSBuffer.fromString('{}')); + await testObject.del(joinPath(environmentService.snippetsHome, 'settings.json')); + const exists = await testObject.exists(joinPath(userDataHomeOnDisk, 'snippets', 'settings.json')); assert.equal(exists, false); }); test('resolve folder', async () => { - await pfs.mkdirp(path.join(userDataPath, 'snippets')); - await pfs.writeFile(path.join(userDataPath, 'snippets', 'settings.json'), '{}'); - const result = await testObject.resolve(joinPath(userDataResource, 'snippets')); + await testObject.createFolder(joinPath(userDataHomeOnDisk, 'snippets')); + await testObject.writeFile(joinPath(userDataHomeOnDisk, 'snippets', 'settings.json'), VSBuffer.fromString('{}')); + const result = await testObject.resolve(environmentService.snippetsHome); assert.ok(result.isDirectory); assert.ok(result.children !== undefined); assert.equal(result.children!.length, 1); - assert.equal(result.children![0].resource.toString(), joinPath(userDataResource, 'snippets/settings.json').toString()); + assert.equal(result.children![0].resource.toString(), joinPath(environmentService.snippetsHome, 'settings.json').toString()); }); test('read backup file', async () => { - await pfs.writeFile(path.join(backupsPath, 'backup.json'), '{}'); - const result = await testObject.readFile(joinPath(userDataResource, `${BACKUPS}/backup.json`)); + await testObject.writeFile(joinPath(backupWorkspaceHomeOnDisk, 'backup.json'), VSBuffer.fromString('{}')); + const result = await testObject.readFile(joinPath(environmentService.backupWorkspaceHome!, `backup.json`)); assert.equal(result.value, '{}'); }); test('create backup file', async () => { - await testObject.createFile(joinPath(userDataResource, `${BACKUPS}/backup.json`), VSBuffer.fromString('{}')); - const result = await pfs.readFile(path.join(backupsPath, 'backup.json')); - assert.equal(result, '{}'); + await testObject.createFile(joinPath(environmentService.backupWorkspaceHome!, `backup.json`), VSBuffer.fromString('{}')); + const result = await testObject.readFile(joinPath(backupWorkspaceHomeOnDisk, 'backup.json')); + assert.equal(result.value.toString(), '{}'); }); test('write backup file', async () => { - await pfs.writeFile(path.join(backupsPath, 'backup.json'), '{}'); - await testObject.writeFile(joinPath(userDataResource, `${BACKUPS}/backup.json`), VSBuffer.fromString('{a:1}')); - const result = await pfs.readFile(path.join(backupsPath, 'backup.json')); - assert.equal(result, '{a:1}'); + await testObject.writeFile(joinPath(backupWorkspaceHomeOnDisk, 'backup.json'), VSBuffer.fromString('{}')); + await testObject.writeFile(joinPath(environmentService.backupWorkspaceHome!, `backup.json`), VSBuffer.fromString('{a:1}')); + const result = await testObject.readFile(joinPath(backupWorkspaceHomeOnDisk, 'backup.json')); + assert.equal(result.value.toString(), '{a:1}'); }); test('resolve backups folder', async () => { - await pfs.writeFile(path.join(backupsPath, 'backup.json'), '{}'); - const result = await testObject.resolve(joinPath(userDataResource, BACKUPS)); + await testObject.writeFile(joinPath(backupWorkspaceHomeOnDisk, 'backup.json'), VSBuffer.fromString('{}')); + const result = await testObject.resolve(environmentService.backupWorkspaceHome!); assert.ok(result.isDirectory); assert.ok(result.children !== undefined); assert.equal(result.children!.length, 1); - assert.equal(result.children![0].resource.toString(), joinPath(userDataResource, `${BACKUPS}/backup.json`).toString()); + assert.equal(result.children![0].resource.toString(), joinPath(environmentService.backupWorkspaceHome!, `backup.json`).toString()); }); }); @@ -315,7 +307,7 @@ suite('FileUserDataProvider - Watching', () => { let testObject: IFileService; let localBackupsResource: URI; let localUserDataResource: URI; - let userDataResource: URI; + let environmentService: IWorkbenchEnvironmentService; const disposables = new DisposableStore(); const fileEventEmitter: Emitter = new Emitter(); @@ -323,15 +315,11 @@ suite('FileUserDataProvider - Watching', () => { setup(() => { - const rootPath = path.join(os.tmpdir(), 'vsctests', uuid.generateUuid()); - const userDataPath = path.join(rootPath, 'user'); - const backupsPath = path.join(rootPath, BACKUPS); - localBackupsResource = URI.file(backupsPath); - localUserDataResource = URI.file(userDataPath); - userDataResource = localUserDataResource.with({ scheme: Schemas.userData }); + environmentService = new BrowserWorkbenchEnvironmentService({ remoteAuthority: 'remote', workspaceId: 'workspaceId', logsPath: URI.file('logFile') }); - const environmentService = new TestBrowserWorkbenchEnvironmentService({ remoteAuthority: 'remote', workspaceId: 'workspaceId', logsPath: URI.file('logFile') }); - environmentService.testUserRoamingDataHome = userDataResource; + const rootResource = URI.file(path.join(os.tmpdir(), 'vsctests', uuid.generateUuid())); + localUserDataResource = joinPath(rootResource, 'user'); + localBackupsResource = joinPath(rootResource, 'Backups'); const userDataFileSystemProvider = new FileUserDataProvider(localUserDataResource, localBackupsResource, new TestFileSystemProvider(fileEventEmitter.event), environmentService, new NullLogService()); disposables.add(userDataFileSystemProvider); @@ -341,12 +329,10 @@ suite('FileUserDataProvider - Watching', () => { disposables.add(testObject.registerProvider(Schemas.userData, userDataFileSystemProvider)); }); - teardown(() => { - disposables.clear(); - }); + teardown(() => disposables.clear()); test('file added change event', done => { - const expected = joinPath(userDataResource, 'settings.json'); + const expected = environmentService.settingsResource; const target = joinPath(localUserDataResource, 'settings.json'); testObject.onDidFilesChange(e => { if (e.contains(expected, FileChangeType.ADDED)) { @@ -360,7 +346,7 @@ suite('FileUserDataProvider - Watching', () => { }); test('file updated change event', done => { - const expected = joinPath(userDataResource, 'settings.json'); + const expected = environmentService.settingsResource; const target = joinPath(localUserDataResource, 'settings.json'); testObject.onDidFilesChange(e => { if (e.contains(expected, FileChangeType.UPDATED)) { @@ -374,7 +360,7 @@ suite('FileUserDataProvider - Watching', () => { }); test('file deleted change event', done => { - const expected = joinPath(userDataResource, 'settings.json'); + const expected = environmentService.settingsResource; const target = joinPath(localUserDataResource, 'settings.json'); testObject.onDidFilesChange(e => { if (e.contains(expected, FileChangeType.DELETED)) { @@ -388,7 +374,7 @@ suite('FileUserDataProvider - Watching', () => { }); test('file under folder created change event', done => { - const expected = joinPath(userDataResource, 'snippets', 'settings.json'); + const expected = joinPath(environmentService.snippetsHome, 'settings.json'); const target = joinPath(localUserDataResource, 'snippets', 'settings.json'); testObject.onDidFilesChange(e => { if (e.contains(expected, FileChangeType.ADDED)) { @@ -402,7 +388,7 @@ suite('FileUserDataProvider - Watching', () => { }); test('file under folder updated change event', done => { - const expected = joinPath(userDataResource, 'snippets', 'settings.json'); + const expected = joinPath(environmentService.snippetsHome, 'settings.json'); const target = joinPath(localUserDataResource, 'snippets', 'settings.json'); testObject.onDidFilesChange(e => { if (e.contains(expected, FileChangeType.UPDATED)) { @@ -416,7 +402,7 @@ suite('FileUserDataProvider - Watching', () => { }); test('file under folder deleted change event', done => { - const expected = joinPath(userDataResource, 'snippets', 'settings.json'); + const expected = joinPath(environmentService.snippetsHome, 'settings.json'); const target = joinPath(localUserDataResource, 'snippets', 'settings.json'); testObject.onDidFilesChange(e => { if (e.contains(expected, FileChangeType.DELETED)) { @@ -444,7 +430,7 @@ suite('FileUserDataProvider - Watching', () => { }); test('backup file created change event', done => { - const expected = joinPath(userDataResource, BACKUPS, 'settings.json'); + const expected = joinPath(environmentService.backupWorkspaceHome!, 'settings.json'); const target = joinPath(localBackupsResource, 'settings.json'); testObject.onDidFilesChange(e => { if (e.contains(expected, FileChangeType.ADDED)) { @@ -458,7 +444,7 @@ suite('FileUserDataProvider - Watching', () => { }); test('backup file update change event', done => { - const expected = joinPath(userDataResource, BACKUPS, 'settings.json'); + const expected = joinPath(environmentService.backupWorkspaceHome!, 'settings.json'); const target = joinPath(localBackupsResource, 'settings.json'); testObject.onDidFilesChange(e => { if (e.contains(expected, FileChangeType.UPDATED)) { @@ -472,7 +458,7 @@ suite('FileUserDataProvider - Watching', () => { }); test('backup file delete change event', done => { - const expected = joinPath(userDataResource, BACKUPS, 'settings.json'); + const expected = joinPath(environmentService.backupWorkspaceHome!, 'settings.json'); const target = joinPath(localBackupsResource, 'settings.json'); testObject.onDidFilesChange(e => { if (e.contains(expected, FileChangeType.DELETED)) { diff --git a/src/vs/workbench/services/views/common/viewContainerModel.ts b/src/vs/workbench/services/views/common/viewContainerModel.ts index 5c0b29a13a..192cbf7b11 100644 --- a/src/vs/workbench/services/views/common/viewContainerModel.ts +++ b/src/vs/workbench/services/views/common/viewContainerModel.ts @@ -12,7 +12,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; -import { firstIndex, move } from 'vs/base/common/arrays'; +import { move } from 'vs/base/common/arrays'; import { isUndefined, isUndefinedOrNull } from 'vs/base/common/types'; import { isEqual } from 'vs/base/common/resources'; @@ -442,8 +442,8 @@ export class ViewContainerModel extends Disposable implements IViewContainerMode } move(from: string, to: string): void { - const fromIndex = firstIndex(this.viewDescriptorItems, v => v.viewDescriptor.id === from); - const toIndex = firstIndex(this.viewDescriptorItems, v => v.viewDescriptor.id === to); + const fromIndex = this.viewDescriptorItems.findIndex(v => v.viewDescriptor.id === from); + const toIndex = this.viewDescriptorItems.findIndex(v => v.viewDescriptor.id === to); const fromViewDescriptor = this.viewDescriptorItems[fromIndex]; const toViewDescriptor = this.viewDescriptorItems[toIndex]; @@ -531,7 +531,7 @@ export class ViewContainerModel extends Disposable implements IViewContainerMode this.contextKeys.delete(key); } } - const index = firstIndex(this.viewDescriptorItems, i => i.viewDescriptor.id === viewDescriptor.id); + const index = this.viewDescriptorItems.findIndex(i => i.viewDescriptor.id === viewDescriptor.id); if (index !== -1) { removed.push(viewDescriptor); const viewDescriptorItem = this.viewDescriptorItems[index]; diff --git a/src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts b/src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts deleted file mode 100644 index f28e9ed87a..0000000000 --- a/src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts +++ /dev/null @@ -1,467 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import * as sinon from 'sinon'; -import { IViewsRegistry, IViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewContainerModel, IViewDescriptorService, ViewContainer } from 'vs/workbench/common/views'; -import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; -import { move } from 'vs/base/common/arrays'; -import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; -import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; -import { ViewDescriptorService } from 'vs/workbench/services/views/browser/viewDescriptorService'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; - -const ViewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); -const ViewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); - -class ViewDescriptorSequence { - - readonly elements: IViewDescriptor[]; - private disposables: IDisposable[] = []; - - constructor(model: IViewContainerModel) { - this.elements = [...model.visibleViewDescriptors]; - model.onDidAddVisibleViewDescriptors(added => added.forEach(({ viewDescriptor, index }) => this.elements.splice(index, 0, viewDescriptor)), null, this.disposables); - model.onDidRemoveVisibleViewDescriptors(removed => removed.sort((a, b) => b.index - a.index).forEach(({ index }) => this.elements.splice(index, 1)), null, this.disposables); - model.onDidMoveVisibleViewDescriptors(({ from, to }) => move(this.elements, from.index, to.index), null, this.disposables); - } - - dispose() { - this.disposables = dispose(this.disposables); - } -} - -suite('ViewContainerModel', () => { - - let container: ViewContainer; - let disposableStore: DisposableStore; - let contextKeyService: IContextKeyService; - let viewDescriptorService: IViewDescriptorService; - let storageService: IStorageService; - - setup(() => { - disposableStore = new DisposableStore(); - const instantiationService: TestInstantiationService = workbenchInstantiationService(); - contextKeyService = instantiationService.createInstance(ContextKeyService); - instantiationService.stub(IContextKeyService, contextKeyService); - storageService = instantiationService.get(IStorageService); - viewDescriptorService = instantiationService.createInstance(ViewDescriptorService); - }); - - teardown(() => { - disposableStore.dispose(); - ViewsRegistry.deregisterViews(ViewsRegistry.getViews(container), container); - ViewContainerRegistry.deregisterViewContainer(container); - }); - - test('empty model', function () { - container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); - const testObject = viewDescriptorService.getViewContainerModel(container); - assert.equal(testObject.visibleViewDescriptors.length, 0); - }); - - test('register/unregister', () => { - container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); - const testObject = viewDescriptorService.getViewContainerModel(container); - const target = disposableStore.add(new ViewDescriptorSequence(testObject)); - - assert.equal(testObject.visibleViewDescriptors.length, 0); - assert.equal(target.elements.length, 0); - - const viewDescriptor: IViewDescriptor = { - id: 'view1', - ctorDescriptor: null!, - name: 'Test View 1' - }; - - ViewsRegistry.registerViews([viewDescriptor], container); - - assert.equal(testObject.visibleViewDescriptors.length, 1); - assert.equal(target.elements.length, 1); - assert.deepEqual(testObject.visibleViewDescriptors[0], viewDescriptor); - assert.deepEqual(target.elements[0], viewDescriptor); - - ViewsRegistry.deregisterViews([viewDescriptor], container); - - assert.equal(testObject.visibleViewDescriptors.length, 0); - assert.equal(target.elements.length, 0); - }); - - test('when contexts', 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)); - assert.equal(testObject.visibleViewDescriptors.length, 0); - assert.equal(target.elements.length, 0); - - const viewDescriptor: IViewDescriptor = { - id: 'view1', - ctorDescriptor: null!, - name: 'Test View 1', - when: ContextKeyExpr.equals('showview1', true) - }; - - ViewsRegistry.registerViews([viewDescriptor], container); - assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should not appear since context isnt in'); - assert.equal(target.elements.length, 0); - - const key = contextKeyService.createKey('showview1', false); - assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should still not appear since showview1 isnt true'); - assert.equal(target.elements.length, 0); - - key.set(true); - await new Promise(c => setTimeout(c, 30)); - assert.equal(testObject.visibleViewDescriptors.length, 1, 'view should appear'); - assert.equal(target.elements.length, 1); - assert.deepEqual(testObject.visibleViewDescriptors[0], viewDescriptor); - assert.equal(target.elements[0], viewDescriptor); - - key.set(false); - await new Promise(c => setTimeout(c, 30)); - assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should disappear'); - assert.equal(target.elements.length, 0); - - ViewsRegistry.deregisterViews([viewDescriptor], container); - assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should not be there anymore'); - assert.equal(target.elements.length, 0); - - key.set(true); - await new Promise(c => setTimeout(c, 30)); - assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should not be there anymore'); - assert.equal(target.elements.length, 0); - }); - - test('when contexts - multiple', 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 view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1' }; - const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2', when: ContextKeyExpr.equals('showview2', true) }; - - ViewsRegistry.registerViews([view1, view2], container); - assert.deepEqual(testObject.visibleViewDescriptors, [view1], 'only view1 should be visible'); - assert.deepEqual(target.elements, [view1], 'only view1 should be visible'); - - const key = contextKeyService.createKey('showview2', false); - assert.deepEqual(testObject.visibleViewDescriptors, [view1], 'still only view1 should be visible'); - assert.deepEqual(target.elements, [view1], 'still only view1 should be visible'); - - key.set(true); - await new Promise(c => setTimeout(c, 30)); - assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2], 'both views should be visible'); - assert.deepEqual(target.elements, [view1, view2], 'both views should be visible'); - - ViewsRegistry.deregisterViews([view1, view2], container); - }); - - test('when contexts - multiple 2', 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 view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1', when: ContextKeyExpr.equals('showview1', true) }; - const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2' }; - - ViewsRegistry.registerViews([view1, view2], container); - assert.deepEqual(testObject.visibleViewDescriptors, [view2], 'only view2 should be visible'); - assert.deepEqual(target.elements, [view2], 'only view2 should be visible'); - - const key = contextKeyService.createKey('showview1', false); - assert.deepEqual(testObject.visibleViewDescriptors, [view2], 'still only view2 should be visible'); - assert.deepEqual(target.elements, [view2], 'still only view2 should be visible'); - - key.set(true); - await new Promise(c => setTimeout(c, 30)); - assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2], 'both views should be visible'); - assert.deepEqual(target.elements, [view1, view2], 'both views should be visible'); - - ViewsRegistry.deregisterViews([view1, view2], container); - }); - - test('setVisible', () => { - 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 view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1', canToggleVisibility: true }; - const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2', canToggleVisibility: true }; - const view3: IViewDescriptor = { id: 'view3', ctorDescriptor: null!, name: 'Test View 3', canToggleVisibility: true }; - - ViewsRegistry.registerViews([view1, view2, view3], container); - assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2, view3]); - assert.deepEqual(target.elements, [view1, view2, view3]); - - testObject.setVisible('view2', true); - assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2, view3], 'nothing should happen'); - assert.deepEqual(target.elements, [view1, view2, view3]); - - testObject.setVisible('view2', false); - assert.deepEqual(testObject.visibleViewDescriptors, [view1, view3], 'view2 should hide'); - assert.deepEqual(target.elements, [view1, view3]); - - testObject.setVisible('view1', false); - assert.deepEqual(testObject.visibleViewDescriptors, [view3], 'view1 should hide'); - assert.deepEqual(target.elements, [view3]); - - testObject.setVisible('view3', false); - assert.deepEqual(testObject.visibleViewDescriptors, [], 'view3 shoud hide'); - assert.deepEqual(target.elements, []); - - testObject.setVisible('view1', true); - assert.deepEqual(testObject.visibleViewDescriptors, [view1], 'view1 should show'); - assert.deepEqual(target.elements, [view1]); - - testObject.setVisible('view3', true); - assert.deepEqual(testObject.visibleViewDescriptors, [view1, view3], 'view3 should show'); - assert.deepEqual(target.elements, [view1, view3]); - - testObject.setVisible('view2', true); - assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2, view3], 'view2 should show'); - assert.deepEqual(target.elements, [view1, view2, view3]); - - ViewsRegistry.deregisterViews([view1, view2, view3], container); - assert.deepEqual(testObject.visibleViewDescriptors, []); - assert.deepEqual(target.elements, []); - }); - - test('move', () => { - 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 view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1' }; - const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2' }; - const view3: IViewDescriptor = { id: 'view3', ctorDescriptor: null!, name: 'Test View 3' }; - - ViewsRegistry.registerViews([view1, view2, view3], container); - assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2, view3], 'model views should be OK'); - assert.deepEqual(target.elements, [view1, view2, view3], 'sql views should be OK'); - - testObject.move('view3', 'view1'); - assert.deepEqual(testObject.visibleViewDescriptors, [view3, view1, view2], 'view3 should go to the front'); - assert.deepEqual(target.elements, [view3, view1, view2]); - - testObject.move('view1', 'view2'); - assert.deepEqual(testObject.visibleViewDescriptors, [view3, view2, view1], 'view1 should go to the end'); - assert.deepEqual(target.elements, [view3, view2, view1]); - - testObject.move('view1', 'view3'); - assert.deepEqual(testObject.visibleViewDescriptors, [view1, view3, view2], 'view1 should go to the front'); - assert.deepEqual(target.elements, [view1, view3, view2]); - - testObject.move('view2', 'view3'); - assert.deepEqual(testObject.visibleViewDescriptors, [view1, view2, view3], 'view2 should go to the middle'); - assert.deepEqual(target.elements, [view1, view2, view3]); - }); - - test('view states', async function () { - storageService.store(`${container.id}.state.hidden`, JSON.stringify([{ id: 'view1', isHidden: true }]), StorageScope.GLOBAL); - container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); - const testObject = viewDescriptorService.getViewContainerModel(container); - const target = disposableStore.add(new ViewDescriptorSequence(testObject)); - - assert.equal(testObject.visibleViewDescriptors.length, 0); - assert.equal(target.elements.length, 0); - - const viewDescriptor: IViewDescriptor = { - id: 'view1', - ctorDescriptor: null!, - name: 'Test View 1' - }; - - ViewsRegistry.registerViews([viewDescriptor], container); - assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should not appear since it was set not visible in view state'); - assert.equal(target.elements.length, 0); - }); - - test('view states and when contexts', async function () { - storageService.store(`${container.id}.state.hidden`, JSON.stringify([{ id: 'view1', isHidden: true }]), StorageScope.GLOBAL); - container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); - const testObject = viewDescriptorService.getViewContainerModel(container); - const target = disposableStore.add(new ViewDescriptorSequence(testObject)); - - assert.equal(testObject.visibleViewDescriptors.length, 0); - assert.equal(target.elements.length, 0); - - const viewDescriptor: IViewDescriptor = { - id: 'view1', - ctorDescriptor: null!, - name: 'Test View 1', - when: ContextKeyExpr.equals('showview1', true) - }; - - ViewsRegistry.registerViews([viewDescriptor], container); - assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should not appear since context isnt in'); - assert.equal(target.elements.length, 0); - - const key = contextKeyService.createKey('showview1', false); - assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should still not appear since showview1 isnt true'); - assert.equal(target.elements.length, 0); - - key.set(true); - await new Promise(c => setTimeout(c, 30)); - assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should still not appear since it was set not visible in view state'); - assert.equal(target.elements.length, 0); - }); - - test('view states and when contexts multiple views', async function () { - storageService.store(`${container.id}.state.hidden`, JSON.stringify([{ id: 'view1', isHidden: true }]), StorageScope.GLOBAL); - container = ViewContainerRegistry.registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); - const testObject = viewDescriptorService.getViewContainerModel(container); - const target = disposableStore.add(new ViewDescriptorSequence(testObject)); - - assert.equal(testObject.visibleViewDescriptors.length, 0); - assert.equal(target.elements.length, 0); - - const view1: IViewDescriptor = { - id: 'view1', - ctorDescriptor: null!, - name: 'Test View 1', - when: ContextKeyExpr.equals('showview', true) - }; - const view2: IViewDescriptor = { - id: 'view2', - ctorDescriptor: null!, - name: 'Test View 2', - }; - const view3: IViewDescriptor = { - id: 'view3', - ctorDescriptor: null!, - name: 'Test View 3', - when: ContextKeyExpr.equals('showview', true) - }; - - ViewsRegistry.registerViews([view1, view2, view3], container); - assert.deepEqual(testObject.visibleViewDescriptors, [view2], 'Only view2 should be visible'); - assert.deepEqual(target.elements, [view2]); - - const key = contextKeyService.createKey('showview', false); - assert.deepEqual(testObject.visibleViewDescriptors, [view2], 'Only view2 should be visible'); - assert.deepEqual(target.elements, [view2]); - - key.set(true); - await new Promise(c => setTimeout(c, 30)); - assert.deepEqual(testObject.visibleViewDescriptors, [view2, view3], 'view3 should be visible'); - assert.deepEqual(target.elements, [view2, view3]); - - key.set(false); - await new Promise(c => setTimeout(c, 30)); - assert.deepEqual(testObject.visibleViewDescriptors, [view2], 'Only view2 should be visible'); - assert.deepEqual(target.elements, [view2]); - }); - - test('remove event is not triggered if view was hidden and removed', 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 - }; - - ViewsRegistry.registerViews([viewDescriptor], container); - - const key = contextKeyService.createKey('showview1', true); - await new Promise(c => setTimeout(c, 30)); - assert.equal(testObject.visibleViewDescriptors.length, 1, 'view should appear after context is set'); - assert.equal(target.elements.length, 1); - - testObject.setVisible('view1', false); - assert.equal(testObject.visibleViewDescriptors.length, 0, 'view should disappear after setting visibility to false'); - assert.equal(target.elements.length, 0); - - const targetEvent = sinon.spy(testObject.onDidRemoveVisibleViewDescriptors); - key.set(false); - await new Promise(c => setTimeout(c, 30)); - 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/services/workspaces/browser/abstractWorkspaceEditingService.ts b/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts index 36123a99cd..58c0f3b868 100644 --- a/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts +++ b/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts @@ -307,7 +307,7 @@ export abstract class AbstractWorkspaceEditingService implements IWorkspaceEditi ); } - abstract async enterWorkspace(path: URI): Promise; + abstract enterWorkspace(path: URI): Promise; protected async doEnterWorkspace(path: URI): Promise { if (!!this.environmentService.extensionTestsLocationURI) { diff --git a/src/vs/workbench/services/workspaces/electron-browser/workspaceEditingService.ts b/src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts similarity index 96% rename from src/vs/workbench/services/workspaces/electron-browser/workspaceEditingService.ts rename to src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts index c1a630d2d0..ec2dadd79c 100644 --- a/src/vs/workbench/services/workspaces/electron-browser/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts @@ -13,12 +13,12 @@ import { WorkspaceService } from 'vs/workbench/services/configuration/browser/co import { IStorageService } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { toBackupWorkspaceResource } from 'vs/workbench/services/backup/electron-browser/backup'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { basename } from 'vs/base/common/resources'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IFileService } from 'vs/platform/files/common/files'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { ILifecycleService, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; import { IFileDialogService, IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -31,7 +31,6 @@ import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron import { isMacintosh } from 'vs/base/common/platform'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { BackupFileService } from 'vs/workbench/services/backup/common/backupFileService'; -import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingService { @@ -173,7 +172,6 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi // Reinitialize backup service this.environmentService.configuration.backupPath = result.backupPath; - this.environmentService.configuration.backupWorkspaceResource = result.backupPath ? toBackupWorkspaceResource(result.backupPath, this.environmentService) : undefined; if (this.backupFileService instanceof BackupFileService) { this.backupFileService.reinitialize(); } diff --git a/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts b/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts index 0dc925be42..f5b2d2a7e1 100644 --- a/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts +++ b/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts @@ -1011,6 +1011,29 @@ suite('ExtHostLanguageFeatureCommands', function () { }); }); + test('What\'s the condition for DocumentLink target to be undefined? #106308', async function () { + disposables.push(extHost.registerDocumentLinkProvider(nullExtensionDescription, defaultSelector, { + provideDocumentLinks(): any { + return [new types.DocumentLink(new types.Range(0, 0, 0, 20), undefined)]; + }, + resolveDocumentLink(link) { + link.target = URI.parse('foo:bar'); + return link; + } + })); + + await rpcProtocol.sync(); + + const links1 = await commands.executeCommand('vscode.executeLinkProvider', model.uri); + assert.equal(links1.length, 1); + assert.equal(links1[0].target, undefined); + + const links2 = await commands.executeCommand('vscode.executeLinkProvider', model.uri, 1000); + assert.equal(links2.length, 1); + assert.equal(links2[0].target!.toString(), URI.parse('foo:bar').toString()); + + }); + test('Color provider', function () { diff --git a/src/vs/workbench/test/browser/api/extHostTextEditors.test.ts b/src/vs/workbench/test/browser/api/extHostBulkEdits.test.ts similarity index 67% rename from src/vs/workbench/test/browser/api/extHostTextEditors.test.ts rename to src/vs/workbench/test/browser/api/extHostBulkEdits.test.ts index 391680a2dd..7f68aa230e 100644 --- a/src/vs/workbench/test/browser/api/extHostTextEditors.test.ts +++ b/src/vs/workbench/test/browser/api/extHostBulkEdits.test.ts @@ -4,26 +4,26 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; -import { MainContext, MainThreadTextEditorsShape, IWorkspaceEditDto, WorkspaceEditType } from 'vs/workbench/api/common/extHost.protocol'; +import { MainContext, IWorkspaceEditDto, WorkspaceEditType, MainThreadBulkEditsShape } from 'vs/workbench/api/common/extHost.protocol'; import { URI } from 'vs/base/common/uri'; import { mock } from 'vs/base/test/common/mock'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { SingleProxyRPCProtocol, TestRPCProtocol } from 'vs/workbench/test/browser/api/testRPCProtocol'; -import { ExtHostEditors } from 'vs/workbench/api/common/extHostTextEditors'; import { NullLogService } from 'vs/platform/log/common/log'; import { assertType } from 'vs/base/common/types'; +import { ExtHostBulkEdits } from 'vs/workbench/api/common/extHostBulkEdits'; -suite('ExtHostTextEditors.applyWorkspaceEdit', () => { +suite('ExtHostBulkEdits.applyWorkspaceEdit', () => { const resource = URI.parse('foo:bar'); - let editors: ExtHostEditors; + let bulkEdits: ExtHostBulkEdits; let workspaceResourceEdits: IWorkspaceEditDto; setup(() => { workspaceResourceEdits = null!; let rpcProtocol = new TestRPCProtocol(); - rpcProtocol.set(MainContext.MainThreadTextEditors, new class extends mock() { + rpcProtocol.set(MainContext.MainThreadBulkEdits, new class extends mock() { $tryApplyWorkspaceEdit(_workspaceResourceEdits: IWorkspaceEditDto): Promise { workspaceResourceEdits = _workspaceResourceEdits; return Promise.resolve(true); @@ -40,23 +40,13 @@ suite('ExtHostTextEditors.applyWorkspaceEdit', () => { EOL: '\n', }] }); - editors = new ExtHostEditors(rpcProtocol, documentsAndEditors, null!); - }); - - test('uses version id if document available', async () => { - let edit = new extHostTypes.WorkspaceEdit(); - edit.replace(resource, new extHostTypes.Range(0, 0, 0, 0), 'hello'); - await editors.applyWorkspaceEdit(edit); - assert.equal(workspaceResourceEdits.edits.length, 1); - const [first] = workspaceResourceEdits.edits; - assertType(first._type === WorkspaceEditType.Text); - assert.equal(first.modelVersionId, 1337); + bulkEdits = new ExtHostBulkEdits(rpcProtocol, documentsAndEditors, null!); }); test('does not use version id if document is not available', async () => { let edit = new extHostTypes.WorkspaceEdit(); edit.replace(URI.parse('foo:bar2'), new extHostTypes.Range(0, 0, 0, 0), 'hello'); - await editors.applyWorkspaceEdit(edit); + await bulkEdits.applyWorkspaceEdit(edit); assert.equal(workspaceResourceEdits.edits.length, 1); const [first] = workspaceResourceEdits.edits; assertType(first._type === WorkspaceEditType.Text); diff --git a/src/vs/workbench/test/browser/api/extHostConfiguration.test.ts b/src/vs/workbench/test/browser/api/extHostConfiguration.test.ts index 0a243d6f3b..a5a05105fa 100644 --- a/src/vs/workbench/test/browser/api/extHostConfiguration.test.ts +++ b/src/vs/workbench/test/browser/api/extHostConfiguration.test.ts @@ -14,7 +14,6 @@ import { mock } from 'vs/base/test/common/mock'; import { IWorkspaceFolder, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ConfigurationTarget, IConfigurationModel, IConfigurationChange } from 'vs/platform/configuration/common/configuration'; import { NullLogService } from 'vs/platform/log/common/log'; -import { assign } from 'vs/base/common/objects'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; suite('ExtHostConfiguration', function () { @@ -211,20 +210,22 @@ suite('ExtHostConfiguration', function () { }), JSON.stringify(actual)); actual = all.getConfiguration('workbench').get('emptyobjectkey'); - actual = assign(actual || {}, { + actual = { + ...(actual || {}), 'statusBar.background': `#0ff`, 'statusBar.foreground': `#ff0`, - }); + }; assert.deepEqual(JSON.stringify({ 'statusBar.background': `#0ff`, 'statusBar.foreground': `#ff0`, }), JSON.stringify(actual)); actual = all.getConfiguration('workbench').get('unknownkey'); - actual = assign(actual || {}, { + actual = { + ...(actual || {}), 'statusBar.background': `#0ff`, 'statusBar.foreground': `#ff0`, - }); + }; assert.deepEqual(JSON.stringify({ 'statusBar.background': `#0ff`, 'statusBar.foreground': `#ff0`, diff --git a/src/vs/workbench/test/browser/api/extHostDocumentSaveParticipant.test.ts b/src/vs/workbench/test/browser/api/extHostDocumentSaveParticipant.test.ts index aa7be7ba07..8d9704990f 100644 --- a/src/vs/workbench/test/browser/api/extHostDocumentSaveParticipant.test.ts +++ b/src/vs/workbench/test/browser/api/extHostDocumentSaveParticipant.test.ts @@ -7,7 +7,7 @@ import { URI } from 'vs/base/common/uri'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { TextDocumentSaveReason, TextEdit, Position, EndOfLine } from 'vs/workbench/api/common/extHostTypes'; -import { MainThreadTextEditorsShape, IWorkspaceEditDto, IWorkspaceTextEditDto } from 'vs/workbench/api/common/extHost.protocol'; +import { MainThreadTextEditorsShape, IWorkspaceEditDto, IWorkspaceTextEditDto, MainThreadBulkEditsShape } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostDocumentSaveParticipant } from 'vs/workbench/api/common/extHostDocumentSaveParticipant'; import { SingleProxyRPCProtocol } from './testRPCProtocol'; import { SaveReason } from 'vs/workbench/common/editor'; @@ -20,7 +20,7 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio suite('ExtHostDocumentSaveParticipant', () => { let resource = URI.parse('foo:bar'); - let mainThreadEditors = new class extends mock() { }; + let mainThreadBulkEdits = new class extends mock() { }; let documents: ExtHostDocuments; let nullLogService = new NullLogService(); let nullExtensionDescription: IExtensionDescription = { @@ -51,12 +51,12 @@ suite('ExtHostDocumentSaveParticipant', () => { }); test('no listeners, no problem', () => { - const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadEditors); + const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadBulkEdits); return participant.$participateInSave(resource, SaveReason.EXPLICIT).then(() => assert.ok(true)); }); test('event delivery', () => { - const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadEditors); + const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadBulkEdits); let event: vscode.TextDocumentWillSaveEvent; let sub = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (e) { @@ -73,7 +73,7 @@ suite('ExtHostDocumentSaveParticipant', () => { }); test('event delivery, immutable', () => { - const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadEditors); + const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadBulkEdits); let event: vscode.TextDocumentWillSaveEvent; let sub = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (e) { @@ -89,7 +89,7 @@ suite('ExtHostDocumentSaveParticipant', () => { }); test('event delivery, bad listener', () => { - const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadEditors); + const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadBulkEdits); let sub = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (e) { throw new Error('💀'); @@ -104,7 +104,7 @@ suite('ExtHostDocumentSaveParticipant', () => { }); test('event delivery, bad listener doesn\'t prevent more events', () => { - const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadEditors); + const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadBulkEdits); let sub1 = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (e) { throw new Error('💀'); @@ -123,7 +123,7 @@ suite('ExtHostDocumentSaveParticipant', () => { }); test('event delivery, in subscriber order', () => { - const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadEditors); + const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadBulkEdits); let counter = 0; let sub1 = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (event) { @@ -141,7 +141,7 @@ suite('ExtHostDocumentSaveParticipant', () => { }); test('event delivery, ignore bad listeners', async () => { - const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadEditors, { timeout: 5, errors: 1 }); + const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadBulkEdits, { timeout: 5, errors: 1 }); let callCount = 0; let sub = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (event) { @@ -159,7 +159,7 @@ suite('ExtHostDocumentSaveParticipant', () => { }); test('event delivery, overall timeout', () => { - const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadEditors, { timeout: 20, errors: 5 }); + const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadBulkEdits, { timeout: 20, errors: 5 }); let callCount = 0; let sub1 = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (event) { @@ -187,7 +187,7 @@ suite('ExtHostDocumentSaveParticipant', () => { }); test('event delivery, waitUntil', () => { - const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadEditors); + const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadBulkEdits); let sub = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (event) { @@ -203,11 +203,11 @@ suite('ExtHostDocumentSaveParticipant', () => { }); test('event delivery, waitUntil must be called sync', () => { - const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadEditors); + const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadBulkEdits); let sub = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (event) { - event.waitUntil(new Promise((resolve, reject) => { + event.waitUntil(new Promise((resolve, reject) => { setTimeout(() => { try { assert.throws(() => event.waitUntil(timeout(10))); @@ -226,7 +226,7 @@ suite('ExtHostDocumentSaveParticipant', () => { }); test('event delivery, waitUntil will timeout', () => { - const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadEditors, { timeout: 5, errors: 3 }); + const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadBulkEdits, { timeout: 5, errors: 3 }); let sub = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (event) { event.waitUntil(timeout(15)); @@ -241,7 +241,7 @@ suite('ExtHostDocumentSaveParticipant', () => { }); test('event delivery, waitUntil failure handling', () => { - const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadEditors); + const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadBulkEdits); let sub1 = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (e) { e.waitUntil(Promise.reject(new Error('dddd'))); @@ -380,7 +380,7 @@ suite('ExtHostDocumentSaveParticipant', () => { error(message: string | Error, ...args: any[]): void { didLogSomething = true; } - }, documents, mainThreadEditors); + }, documents, mainThreadBulkEdits); let sub = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (e) { diff --git a/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts index 8ceb5f04b5..3219933170 100644 --- a/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts @@ -22,7 +22,7 @@ import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { getDocumentSymbols } from 'vs/editor/contrib/gotoSymbol/documentSymbols'; import * as modes from 'vs/editor/common/modes'; -import { getCodeLensData } from 'vs/editor/contrib/codelens/codelens'; +import { getCodeLensModel } from 'vs/editor/contrib/codelens/codelens'; import { getDefinitionsAtPosition, getImplementationsAtPosition, getTypeDefinitionsAtPosition, getDeclarationsAtPosition, getReferencesAtPosition } from 'vs/editor/contrib/gotoSymbol/goToSymbol'; import { getHover } from 'vs/editor/contrib/hover/getHover'; import { getOccurrencesAtPosition } from 'vs/editor/contrib/wordHighlighter/wordHighlighter'; @@ -189,7 +189,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const value = await getCodeLensData(model, CancellationToken.None); + const value = await getCodeLensModel(model, CancellationToken.None); assert.equal(value.lenses.length, 1); }); @@ -207,7 +207,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const value = await getCodeLensData(model, CancellationToken.None); + const value = await getCodeLensModel(model, CancellationToken.None); assert.equal(value.lenses.length, 1); const [data] = value.lenses; const symbol = await Promise.resolve(data.provider.resolveCodeLens!(model, data.symbol, CancellationToken.None)); @@ -224,7 +224,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const value = await getCodeLensData(model, CancellationToken.None); + const value = await getCodeLensModel(model, CancellationToken.None); assert.equal(value.lenses.length, 1); let [data] = value.lenses; const symbol = await Promise.resolve(data.provider.resolveCodeLens!(model, data.symbol, CancellationToken.None)); diff --git a/src/vs/workbench/test/browser/api/extHostNotebook.test.ts b/src/vs/workbench/test/browser/api/extHostNotebook.test.ts index 1d4f417370..8fd91cad5a 100644 --- a/src/vs/workbench/test/browser/api/extHostNotebook.test.ts +++ b/src/vs/workbench/test/browser/api/extHostNotebook.test.ts @@ -11,7 +11,8 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { NullLogService } from 'vs/platform/log/common/log'; import { mock } from 'vs/base/test/common/mock'; import { IModelAddedData, MainContext, MainThreadCommandsShape, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol'; -import { ExtHostNotebookDocument, ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; +import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; +import { ExtHostNotebookDocument } from 'vs/workbench/api/common/extHostNotebookDocument'; import { CellKind, CellUri, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { URI } from 'vs/base/common/uri'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; @@ -56,7 +57,6 @@ suite('NotebookCell#Document', function () { }); extHostNotebooks.$acceptDocumentAndEditorsDelta({ addedDocuments: [{ - handle: 0, uri: notebookUri, viewType: 'test', versionId: 0, @@ -135,7 +135,7 @@ suite('NotebookCell#Document', function () { test('cell document is vscode.TextDocument after changing it', async function () { - const p = new Promise((resolve, reject) => { + const p = new Promise((resolve, reject) => { extHostNotebooks.onDidChangeNotebookCells(e => { try { assert.strictEqual(e.changes.length, 1); @@ -160,25 +160,29 @@ suite('NotebookCell#Document', function () { }); extHostNotebooks.$acceptModelChanged(notebookUri, { - kind: NotebookCellsChangeType.ModelChange, versionId: notebook.notebookDocument.version + 1, - changes: [[0, 0, [{ - handle: 2, - uri: CellUri.generate(notebookUri, 2), - source: ['Hello', 'World', 'Hello World!'], - eol: '\n', - language: 'test', - cellKind: CellKind.Code, - outputs: [], - }, { - handle: 3, - uri: CellUri.generate(notebookUri, 3), - source: ['Hallo', 'Welt', 'Hallo Welt!'], - eol: '\n', - language: 'test', - cellKind: CellKind.Code, - outputs: [], - }]]] + rawEvents: [ + { + kind: NotebookCellsChangeType.ModelChange, + changes: [[0, 0, [{ + handle: 2, + uri: CellUri.generate(notebookUri, 2), + source: ['Hello', 'World', 'Hello World!'], + eol: '\n', + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }, { + handle: 3, + uri: CellUri.generate(notebookUri, 3), + source: ['Hallo', 'Welt', 'Hallo Welt!'], + eol: '\n', + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }]]] + } + ] }, false); await p; @@ -232,9 +236,13 @@ suite('NotebookCell#Document', function () { const [cell1, cell2] = notebook.notebookDocument.cells; extHostNotebooks.$acceptModelChanged(notebook.uri, { - kind: NotebookCellsChangeType.ModelChange, versionId: 2, - changes: [[0, 1, []]] + rawEvents: [ + { + kind: NotebookCellsChangeType.ModelChange, + changes: [[0, 1, []]] + } + ] }, false); assert.equal(notebook.notebookDocument.cells.length, 1); @@ -249,4 +257,51 @@ suite('NotebookCell#Document', function () { assert.equal(cells.document.notebook === notebook.notebookDocument, true); } }); + + test('cell#index', function () { + + assert.equal(notebook.notebookDocument.cells.length, 2); + const [first, second] = notebook.notebookDocument.cells; + assert.equal(first.index, 0); + assert.equal(second.index, 1); + + // remove first cell + extHostNotebooks.$acceptModelChanged(notebook.uri, { + versionId: notebook.notebookDocument.version + 1, + rawEvents: [{ + kind: NotebookCellsChangeType.ModelChange, + changes: [[0, 1, []]] + }] + }, false); + + assert.equal(notebook.notebookDocument.cells.length, 1); + assert.equal(second.index, 0); + + extHostNotebooks.$acceptModelChanged(notebookUri, { + versionId: notebook.notebookDocument.version + 1, + rawEvents: [{ + kind: NotebookCellsChangeType.ModelChange, + changes: [[0, 0, [{ + handle: 2, + uri: CellUri.generate(notebookUri, 2), + source: ['Hello', 'World', 'Hello World!'], + eol: '\n', + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }, { + handle: 3, + uri: CellUri.generate(notebookUri, 3), + source: ['Hallo', 'Welt', 'Hallo Welt!'], + eol: '\n', + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }]]] + }] + }, false); + + assert.equal(notebook.notebookDocument.cells.length, 3); + assert.equal(second.index, 2); + }); }); diff --git a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts index e724ecb7ca..ff4c354ec5 100644 --- a/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts +++ b/src/vs/workbench/test/browser/api/extHostNotebookConcatDocument.test.ts @@ -9,7 +9,8 @@ import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; 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 { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; +import { ExtHostNotebookDocument } from 'vs/workbench/api/common/extHostNotebookDocument'; import { URI } from 'vs/base/common/uri'; import { CellKind, CellUri, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Position, Location, Range } from 'vs/workbench/api/common/extHostTypes'; @@ -56,7 +57,6 @@ suite('NotebookConcatDocument', function () { }); extHostNotebooks.$acceptDocumentAndEditorsDelta({ addedDocuments: [{ - handle: 0, uri: notebookUri, viewType: 'test', cells: [{ @@ -125,25 +125,28 @@ suite('NotebookConcatDocument', function () { const cellUri2 = CellUri.generate(notebook.uri, 2); extHostNotebooks.$acceptModelChanged(notebookUri, { - kind: NotebookCellsChangeType.ModelChange, versionId: notebook.notebookDocument.version + 1, - changes: [[0, 0, [{ - handle: 1, - uri: cellUri1, - source: ['Hello', 'World', 'Hello World!'], - eol: '\n', - language: 'test', - cellKind: CellKind.Code, - outputs: [], - }, { - handle: 2, - uri: cellUri2, - source: ['Hallo', 'Welt', 'Hallo Welt!'], - eol: '\n', - language: 'test', - cellKind: CellKind.Code, - outputs: [], - }]]] + rawEvents: [{ + kind: NotebookCellsChangeType.ModelChange, + changes: [[0, 0, [{ + handle: 1, + uri: cellUri1, + source: ['Hello', 'World', 'Hello World!'], + eol: '\n', + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }, { + handle: 2, + uri: cellUri2, + source: ['Hallo', 'Welt', 'Hallo Welt!'], + eol: '\n', + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }]] + ] + }] }, false); @@ -159,25 +162,29 @@ suite('NotebookConcatDocument', function () { test('location, position mapping', function () { extHostNotebooks.$acceptModelChanged(notebookUri, { - kind: NotebookCellsChangeType.ModelChange, versionId: notebook.notebookDocument.version + 1, - changes: [[0, 0, [{ - handle: 1, - uri: CellUri.generate(notebook.uri, 1), - source: ['Hello', 'World', 'Hello World!'], - eol: '\n', - language: 'test', - cellKind: CellKind.Code, - outputs: [], - }, { - handle: 2, - uri: CellUri.generate(notebook.uri, 2), - source: ['Hallo', 'Welt', 'Hallo Welt!'], - eol: '\n', - language: 'test', - cellKind: CellKind.Code, - outputs: [], - }]]] + rawEvents: [ + { + kind: NotebookCellsChangeType.ModelChange, + changes: [[0, 0, [{ + handle: 1, + uri: CellUri.generate(notebook.uri, 1), + source: ['Hello', 'World', 'Hello World!'], + eol: '\n', + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }, { + handle: 2, + uri: CellUri.generate(notebook.uri, 2), + source: ['Hallo', 'Welt', 'Hallo Welt!'], + eol: '\n', + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }]]] + } + ] }, false); @@ -200,17 +207,21 @@ suite('NotebookConcatDocument', function () { // UPDATE 1 extHostNotebooks.$acceptModelChanged(notebookUri, { - kind: NotebookCellsChangeType.ModelChange, versionId: notebook.notebookDocument.version + 1, - changes: [[0, 0, [{ - handle: 1, - uri: CellUri.generate(notebook.uri, 1), - source: ['Hello', 'World', 'Hello World!'], - eol: '\n', - language: 'test', - cellKind: CellKind.Code, - outputs: [], - }]]] + rawEvents: [ + { + kind: NotebookCellsChangeType.ModelChange, + changes: [[0, 0, [{ + handle: 1, + uri: CellUri.generate(notebook.uri, 1), + source: ['Hello', 'World', 'Hello World!'], + eol: '\n', + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }]]] + } + ] }, false); assert.equal(notebook.notebookDocument.cells.length, 1 + 1); assert.equal(doc.version, 1); @@ -223,17 +234,21 @@ suite('NotebookConcatDocument', function () { // UPDATE 2 extHostNotebooks.$acceptModelChanged(notebookUri, { - kind: NotebookCellsChangeType.ModelChange, versionId: notebook.notebookDocument.version + 1, - changes: [[1, 0, [{ - handle: 2, - uri: CellUri.generate(notebook.uri, 2), - source: ['Hallo', 'Welt', 'Hallo Welt!'], - eol: '\n', - language: 'test', - cellKind: CellKind.Code, - outputs: [], - }]]] + rawEvents: [ + { + kind: NotebookCellsChangeType.ModelChange, + changes: [[1, 0, [{ + handle: 2, + uri: CellUri.generate(notebook.uri, 2), + source: ['Hallo', 'Welt', 'Hallo Welt!'], + eol: '\n', + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }]]] + } + ] }, false); assert.equal(notebook.notebookDocument.cells.length, 1 + 2); @@ -247,9 +262,13 @@ suite('NotebookConcatDocument', function () { // UPDATE 3 (remove cell #2 again) extHostNotebooks.$acceptModelChanged(notebookUri, { - kind: NotebookCellsChangeType.ModelChange, versionId: notebook.notebookDocument.version + 1, - changes: [[1, 1, []]] + rawEvents: [ + { + kind: NotebookCellsChangeType.ModelChange, + changes: [[1, 1, []]] + } + ] }, false); assert.equal(notebook.notebookDocument.cells.length, 1 + 1); assert.equal(doc.version, 3); @@ -265,25 +284,30 @@ suite('NotebookConcatDocument', function () { // UPDATE 1 extHostNotebooks.$acceptModelChanged(notebookUri, { - kind: NotebookCellsChangeType.ModelChange, versionId: notebook.notebookDocument.version + 1, - changes: [[0, 0, [{ - handle: 1, - uri: CellUri.generate(notebook.uri, 1), - source: ['Hello', 'World', 'Hello World!'], - eol: '\n', - language: 'test', - cellKind: CellKind.Code, - outputs: [], - }, { - handle: 2, - uri: CellUri.generate(notebook.uri, 2), - source: ['Hallo', 'Welt', 'Hallo Welt!'], - eol: '\n', - language: 'test', - cellKind: CellKind.Code, - outputs: [], - }]]] + rawEvents: [ + { + + kind: NotebookCellsChangeType.ModelChange, + changes: [[0, 0, [{ + handle: 1, + uri: CellUri.generate(notebook.uri, 1), + source: ['Hello', 'World', 'Hello World!'], + eol: '\n', + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }, { + handle: 2, + uri: CellUri.generate(notebook.uri, 2), + source: ['Hallo', 'Welt', 'Hallo Welt!'], + eol: '\n', + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }]]] + } + ] }, false); assert.equal(notebook.notebookDocument.cells.length, 1 + 2); assert.equal(doc.version, 1); @@ -319,25 +343,29 @@ suite('NotebookConcatDocument', function () { test('selector', function () { extHostNotebooks.$acceptModelChanged(notebookUri, { - kind: NotebookCellsChangeType.ModelChange, versionId: notebook.notebookDocument.version + 1, - changes: [[0, 0, [{ - handle: 1, - uri: CellUri.generate(notebook.uri, 1), - source: ['fooLang-document'], - eol: '\n', - language: 'fooLang', - cellKind: CellKind.Code, - outputs: [], - }, { - handle: 2, - uri: CellUri.generate(notebook.uri, 2), - source: ['barLang-document'], - eol: '\n', - language: 'barLang', - cellKind: CellKind.Code, - outputs: [], - }]]] + rawEvents: [ + { + kind: NotebookCellsChangeType.ModelChange, + changes: [[0, 0, [{ + handle: 1, + uri: CellUri.generate(notebook.uri, 1), + source: ['fooLang-document'], + eol: '\n', + language: 'fooLang', + cellKind: CellKind.Code, + outputs: [], + }, { + handle: 2, + uri: CellUri.generate(notebook.uri, 2), + source: ['barLang-document'], + eol: '\n', + language: 'barLang', + cellKind: CellKind.Code, + outputs: [], + }]]] + } + ] }, false); const mixedDoc = new ExtHostNotebookConcatDocument(extHostNotebooks, extHostDocuments, notebook.notebookDocument, undefined); @@ -349,17 +377,21 @@ suite('NotebookConcatDocument', function () { assertLines(barLangDoc, 'barLang-document'); extHostNotebooks.$acceptModelChanged(notebookUri, { - kind: NotebookCellsChangeType.ModelChange, versionId: notebook.notebookDocument.version + 1, - changes: [[2, 0, [{ - handle: 3, - uri: CellUri.generate(notebook.uri, 3), - source: ['barLang-document2'], - eol: '\n', - language: 'barLang', - cellKind: CellKind.Code, - outputs: [], - }]]] + rawEvents: [ + { + kind: NotebookCellsChangeType.ModelChange, + changes: [[2, 0, [{ + handle: 3, + uri: CellUri.generate(notebook.uri, 3), + source: ['barLang-document2'], + eol: '\n', + language: 'barLang', + cellKind: CellKind.Code, + outputs: [], + }]]] + } + ] }, false); assertLines(mixedDoc, 'fooLang-document', 'barLang-document', 'barLang-document2'); @@ -383,25 +415,29 @@ suite('NotebookConcatDocument', function () { test('offsetAt(position) <-> positionAt(offset)', function () { extHostNotebooks.$acceptModelChanged(notebookUri, { - kind: NotebookCellsChangeType.ModelChange, versionId: notebook.notebookDocument.version + 1, - changes: [[0, 0, [{ - handle: 1, - uri: CellUri.generate(notebook.uri, 1), - source: ['Hello', 'World', 'Hello World!'], - eol: '\n', - language: 'test', - cellKind: CellKind.Code, - outputs: [], - }, { - handle: 2, - uri: CellUri.generate(notebook.uri, 2), - source: ['Hallo', 'Welt', 'Hallo Welt!'], - eol: '\n', - language: 'test', - cellKind: CellKind.Code, - outputs: [], - }]]] + rawEvents: [ + { + kind: NotebookCellsChangeType.ModelChange, + changes: [[0, 0, [{ + handle: 1, + uri: CellUri.generate(notebook.uri, 1), + source: ['Hello', 'World', 'Hello World!'], + eol: '\n', + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }, { + handle: 2, + uri: CellUri.generate(notebook.uri, 2), + source: ['Hallo', 'Welt', 'Hallo Welt!'], + eol: '\n', + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }]]] + } + ] }, false); assert.equal(notebook.notebookDocument.cells.length, 1 + 2); // markdown and code @@ -436,25 +472,29 @@ suite('NotebookConcatDocument', function () { test('locationAt(position) <-> positionAt(location)', function () { extHostNotebooks.$acceptModelChanged(notebookUri, { - kind: NotebookCellsChangeType.ModelChange, versionId: notebook.notebookDocument.version + 1, - changes: [[0, 0, [{ - handle: 1, - uri: CellUri.generate(notebook.uri, 1), - source: ['Hello', 'World', 'Hello World!'], - eol: '\n', - language: 'test', - cellKind: CellKind.Code, - outputs: [], - }, { - handle: 2, - uri: CellUri.generate(notebook.uri, 2), - source: ['Hallo', 'Welt', 'Hallo Welt!'], - eol: '\n', - language: 'test', - cellKind: CellKind.Code, - outputs: [], - }]]] + rawEvents: [ + { + kind: NotebookCellsChangeType.ModelChange, + changes: [[0, 0, [{ + handle: 1, + uri: CellUri.generate(notebook.uri, 1), + source: ['Hello', 'World', 'Hello World!'], + eol: '\n', + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }, { + handle: 2, + uri: CellUri.generate(notebook.uri, 2), + source: ['Hallo', 'Welt', 'Hallo Welt!'], + eol: '\n', + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }]]] + } + ] }, false); assert.equal(notebook.notebookDocument.cells.length, 1 + 2); // markdown and code @@ -473,25 +513,29 @@ suite('NotebookConcatDocument', function () { test('getText(range)', function () { extHostNotebooks.$acceptModelChanged(notebookUri, { - kind: NotebookCellsChangeType.ModelChange, versionId: notebook.notebookDocument.version + 1, - changes: [[0, 0, [{ - handle: 1, - uri: CellUri.generate(notebook.uri, 1), - source: ['Hello', 'World', 'Hello World!'], - eol: '\n', - language: 'test', - cellKind: CellKind.Code, - outputs: [], - }, { - handle: 2, - uri: CellUri.generate(notebook.uri, 2), - source: ['Hallo', 'Welt', 'Hallo Welt!'], - eol: '\n', - language: 'test', - cellKind: CellKind.Code, - outputs: [], - }]]] + rawEvents: [ + { + kind: NotebookCellsChangeType.ModelChange, + changes: [[0, 0, [{ + handle: 1, + uri: CellUri.generate(notebook.uri, 1), + source: ['Hello', 'World', 'Hello World!'], + eol: '\n', + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }, { + handle: 2, + uri: CellUri.generate(notebook.uri, 2), + source: ['Hallo', 'Welt', 'Hallo Welt!'], + eol: '\n', + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }]]] + } + ] }, false); assert.equal(notebook.notebookDocument.cells.length, 1 + 2); // markdown and code @@ -507,25 +551,29 @@ suite('NotebookConcatDocument', function () { test('validateRange/Position', function () { extHostNotebooks.$acceptModelChanged(notebookUri, { - kind: NotebookCellsChangeType.ModelChange, versionId: notebook.notebookDocument.version + 1, - changes: [[0, 0, [{ - handle: 1, - uri: CellUri.generate(notebook.uri, 1), - source: ['Hello', 'World', 'Hello World!'], - eol: '\n', - language: 'test', - cellKind: CellKind.Code, - outputs: [], - }, { - handle: 2, - uri: CellUri.generate(notebook.uri, 2), - source: ['Hallo', 'Welt', 'Hallo Welt!'], - eol: '\n', - language: 'test', - cellKind: CellKind.Code, - outputs: [], - }]]] + rawEvents: [ + { + kind: NotebookCellsChangeType.ModelChange, + changes: [[0, 0, [{ + handle: 1, + uri: CellUri.generate(notebook.uri, 1), + source: ['Hello', 'World', 'Hello World!'], + eol: '\n', + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }, { + handle: 2, + uri: CellUri.generate(notebook.uri, 2), + source: ['Hallo', 'Welt', 'Hallo Welt!'], + eol: '\n', + language: 'test', + cellKind: CellKind.Code, + outputs: [], + }]]] + } + ] }, false); assert.equal(notebook.notebookDocument.cells.length, 1 + 2); // markdown and code diff --git a/src/vs/workbench/test/browser/api/extHostTextEditor.test.ts b/src/vs/workbench/test/browser/api/extHostTextEditor.test.ts index 1e47aa00f7..51946e1ccf 100644 --- a/src/vs/workbench/test/browser/api/extHostTextEditor.test.ts +++ b/src/vs/workbench/test/browser/api/extHostTextEditor.test.ts @@ -84,7 +84,6 @@ suite('ExtHostTextEditorOptions', () => { $tryRevealRange: undefined!, $trySetSelections: undefined!, $tryApplyEdits: undefined!, - $tryApplyWorkspaceEdit: undefined!, $tryInsertSnippet: undefined!, $getDiffInformation: undefined! }; diff --git a/src/vs/workbench/test/browser/api/extHostTreeViews.test.ts b/src/vs/workbench/test/browser/api/extHostTreeViews.test.ts index 2c6e0def11..46c7e9202c 100644 --- a/src/vs/workbench/test/browser/api/extHostTreeViews.test.ts +++ b/src/vs/workbench/test/browser/api/extHostTreeViews.test.ts @@ -248,7 +248,7 @@ suite.skip('ExtHostTreeView', function () { }); async function runWithEventMerging(action: (resolve: () => void) => void) { - await new Promise((resolve) => { + await new Promise((resolve) => { let subscription: IDisposable | undefined = undefined; subscription = target.onRefresh.event(() => { subscription!.dispose(); @@ -256,7 +256,7 @@ suite.skip('ExtHostTreeView', function () { }); onDidChangeTreeNode.fire(getNode('b')); }); - await new Promise(action); + await new Promise(action); } test('refresh parent and child node trigger refresh only on parent - scenario 1', async () => { diff --git a/src/vs/workbench/test/browser/api/mainThreadDocumentContentProviders.test.ts b/src/vs/workbench/test/browser/api/mainThreadDocumentContentProviders.test.ts index 5ddb19291e..267ae97d93 100644 --- a/src/vs/workbench/test/browser/api/mainThreadDocumentContentProviders.test.ts +++ b/src/vs/workbench/test/browser/api/mainThreadDocumentContentProviders.test.ts @@ -35,7 +35,7 @@ suite('MainThreadDocumentContentProviders', function () { }, ); - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { let expectedEvents = 1; model.onDidChangeContent(e => { expectedEvents -= 1; diff --git a/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts b/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts index 7ee1d686c8..13adf7451d 100644 --- a/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts +++ b/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts @@ -13,7 +13,7 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile import { ExtHostDocumentsAndEditorsShape, IDocumentsAndEditorsDelta } from 'vs/workbench/api/common/extHost.protocol'; import { createTestCodeEditor, ITestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { mock } from 'vs/base/test/common/mock'; -import { TestEditorService, TestEditorGroupsService, TestEnvironmentService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestEditorService, TestEditorGroupsService, TestEnvironmentService, TestPathService } from 'vs/workbench/test/browser/workbenchTestServices'; import { Event } from 'vs/base/common/event'; import { ITextModel } from 'vs/editor/common/model'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -99,7 +99,8 @@ suite('MainThreadDocumentsAndEditors', () => { readText() { return Promise.resolve('clipboard_contents'); } - } + }, + new TestPathService() ); }); diff --git a/src/vs/workbench/test/browser/quickAccess.test.ts b/src/vs/workbench/test/browser/quickAccess.test.ts deleted file mode 100644 index 721038121e..0000000000 --- a/src/vs/workbench/test/browser/quickAccess.test.ts +++ /dev/null @@ -1,314 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IQuickAccessRegistry, Extensions, IQuickAccessProvider, QuickAccessRegistry } from 'vs/platform/quickinput/common/quickAccess'; -import { IQuickPick, IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { TestServiceAccessor, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; -import { DisposableStore, toDisposable, IDisposable } from 'vs/base/common/lifecycle'; -import { timeout } from 'vs/base/common/async'; -import { PickerQuickAccessProvider, FastAndSlowPicks } from 'vs/platform/quickinput/browser/pickerQuickAccess'; - -suite('QuickAccess', () => { - - let instantiationService: IInstantiationService; - let accessor: TestServiceAccessor; - - let providerDefaultCalled = false; - let providerDefaultCanceled = false; - let providerDefaultDisposed = false; - - let provider1Called = false; - let provider1Canceled = false; - let provider1Disposed = false; - - let provider2Called = false; - let provider2Canceled = false; - let provider2Disposed = false; - - let provider3Called = false; - let provider3Canceled = false; - let provider3Disposed = false; - - class TestProviderDefault implements IQuickAccessProvider { - - constructor(@IQuickInputService private readonly quickInputService: IQuickInputService, disposables: DisposableStore) { } - - provide(picker: IQuickPick, token: CancellationToken): IDisposable { - assert.ok(picker); - providerDefaultCalled = true; - token.onCancellationRequested(() => providerDefaultCanceled = true); - - // bring up provider #3 - setTimeout(() => this.quickInputService.quickAccess.show(providerDescriptor3.prefix)); - - return toDisposable(() => providerDefaultDisposed = true); - } - } - - class TestProvider1 implements IQuickAccessProvider { - provide(picker: IQuickPick, token: CancellationToken): IDisposable { - assert.ok(picker); - provider1Called = true; - token.onCancellationRequested(() => provider1Canceled = true); - - return toDisposable(() => provider1Disposed = true); - } - } - - class TestProvider2 implements IQuickAccessProvider { - provide(picker: IQuickPick, token: CancellationToken): IDisposable { - assert.ok(picker); - provider2Called = true; - token.onCancellationRequested(() => provider2Canceled = true); - - return toDisposable(() => provider2Disposed = true); - } - } - - class TestProvider3 implements IQuickAccessProvider { - provide(picker: IQuickPick, token: CancellationToken): IDisposable { - assert.ok(picker); - provider3Called = true; - token.onCancellationRequested(() => provider3Canceled = true); - - // hide without picking - setTimeout(() => picker.hide()); - - return toDisposable(() => provider3Disposed = true); - } - } - - const providerDescriptorDefault = { ctor: TestProviderDefault, prefix: '', helpEntries: [] }; - const providerDescriptor1 = { ctor: TestProvider1, prefix: 'test', helpEntries: [] }; - const providerDescriptor2 = { ctor: TestProvider2, prefix: 'test something', helpEntries: [] }; - const providerDescriptor3 = { ctor: TestProvider3, prefix: 'changed', helpEntries: [] }; - - setup(() => { - instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(TestServiceAccessor); - }); - - test('registry', () => { - const registry = (Registry.as(Extensions.Quickaccess)); - const restore = (registry as QuickAccessRegistry).clear(); - - assert.ok(!registry.getQuickAccessProvider('test')); - - const disposables = new DisposableStore(); - - disposables.add(registry.registerQuickAccessProvider(providerDescriptorDefault)); - assert(registry.getQuickAccessProvider('') === providerDescriptorDefault); - assert(registry.getQuickAccessProvider('test') === providerDescriptorDefault); - - const disposable = disposables.add(registry.registerQuickAccessProvider(providerDescriptor1)); - assert(registry.getQuickAccessProvider('test') === providerDescriptor1); - - const providers = registry.getQuickAccessProviders(); - assert(providers.some(provider => provider.prefix === 'test')); - - disposable.dispose(); - assert(registry.getQuickAccessProvider('test') === providerDescriptorDefault); - - disposables.dispose(); - assert.ok(!registry.getQuickAccessProvider('test')); - - restore(); - }); - - test('provider', async () => { - const registry = (Registry.as(Extensions.Quickaccess)); - const restore = (registry as QuickAccessRegistry).clear(); - - const disposables = new DisposableStore(); - - disposables.add(registry.registerQuickAccessProvider(providerDescriptorDefault)); - disposables.add(registry.registerQuickAccessProvider(providerDescriptor1)); - disposables.add(registry.registerQuickAccessProvider(providerDescriptor2)); - disposables.add(registry.registerQuickAccessProvider(providerDescriptor3)); - - accessor.quickInputService.quickAccess.show('test'); - assert.equal(providerDefaultCalled, false); - assert.equal(provider1Called, true); - assert.equal(provider2Called, false); - assert.equal(provider3Called, false); - assert.equal(providerDefaultCanceled, false); - assert.equal(provider1Canceled, false); - assert.equal(provider2Canceled, false); - assert.equal(provider3Canceled, false); - assert.equal(providerDefaultDisposed, false); - assert.equal(provider1Disposed, false); - assert.equal(provider2Disposed, false); - assert.equal(provider3Disposed, false); - provider1Called = false; - - accessor.quickInputService.quickAccess.show('test something'); - assert.equal(providerDefaultCalled, false); - assert.equal(provider1Called, false); - assert.equal(provider2Called, true); - assert.equal(provider3Called, false); - assert.equal(providerDefaultCanceled, false); - assert.equal(provider1Canceled, true); - assert.equal(provider2Canceled, false); - assert.equal(provider3Canceled, false); - assert.equal(providerDefaultDisposed, false); - assert.equal(provider1Disposed, true); - assert.equal(provider2Disposed, false); - assert.equal(provider3Disposed, false); - provider2Called = false; - provider1Canceled = false; - provider1Disposed = false; - - accessor.quickInputService.quickAccess.show('usedefault'); - assert.equal(providerDefaultCalled, true); - assert.equal(provider1Called, false); - assert.equal(provider2Called, false); - assert.equal(provider3Called, false); - assert.equal(providerDefaultCanceled, false); - assert.equal(provider1Canceled, false); - assert.equal(provider2Canceled, true); - assert.equal(provider3Canceled, false); - assert.equal(providerDefaultDisposed, false); - assert.equal(provider1Disposed, false); - assert.equal(provider2Disposed, true); - assert.equal(provider3Disposed, false); - - await timeout(1); - - assert.equal(providerDefaultCanceled, true); - assert.equal(providerDefaultDisposed, true); - assert.equal(provider3Called, true); - - await timeout(1); - - assert.equal(provider3Canceled, true); - assert.equal(provider3Disposed, true); - - disposables.dispose(); - - restore(); - }); - - let fastProviderCalled = false; - let slowProviderCalled = false; - let fastAndSlowProviderCalled = false; - - let slowProviderCanceled = false; - let fastAndSlowProviderCanceled = false; - - class FastTestQuickPickProvider extends PickerQuickAccessProvider { - - constructor() { - super('fast'); - } - - protected getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Array { - fastProviderCalled = true; - - return [{ label: 'Fast Pick' }]; - } - } - - class SlowTestQuickPickProvider extends PickerQuickAccessProvider { - - constructor() { - super('slow'); - } - - protected async getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Promise> { - slowProviderCalled = true; - - await timeout(1); - - if (token.isCancellationRequested) { - slowProviderCanceled = true; - } - - return [{ label: 'Slow Pick' }]; - } - } - - class FastAndSlowTestQuickPickProvider extends PickerQuickAccessProvider { - - constructor() { - super('bothFastAndSlow'); - } - - protected getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): FastAndSlowPicks { - fastAndSlowProviderCalled = true; - - return { - picks: [{ label: 'Fast Pick' }], - additionalPicks: (async () => { - await timeout(1); - - if (token.isCancellationRequested) { - fastAndSlowProviderCanceled = true; - } - - return [{ label: 'Slow Pick' }]; - })() - }; - } - } - - const fastProviderDescriptor = { ctor: FastTestQuickPickProvider, prefix: 'fast', helpEntries: [] }; - const slowProviderDescriptor = { ctor: SlowTestQuickPickProvider, prefix: 'slow', helpEntries: [] }; - const fastAndSlowProviderDescriptor = { ctor: FastAndSlowTestQuickPickProvider, prefix: 'bothFastAndSlow', helpEntries: [] }; - - test('quick pick access', async () => { - const registry = (Registry.as(Extensions.Quickaccess)); - const restore = (registry as QuickAccessRegistry).clear(); - - const disposables = new DisposableStore(); - - disposables.add(registry.registerQuickAccessProvider(fastProviderDescriptor)); - disposables.add(registry.registerQuickAccessProvider(slowProviderDescriptor)); - disposables.add(registry.registerQuickAccessProvider(fastAndSlowProviderDescriptor)); - - accessor.quickInputService.quickAccess.show('fast'); - assert.equal(fastProviderCalled, true); - assert.equal(slowProviderCalled, false); - assert.equal(fastAndSlowProviderCalled, false); - fastProviderCalled = false; - - accessor.quickInputService.quickAccess.show('slow'); - await timeout(2); - - assert.equal(fastProviderCalled, false); - assert.equal(slowProviderCalled, true); - assert.equal(slowProviderCanceled, false); - assert.equal(fastAndSlowProviderCalled, false); - slowProviderCalled = false; - - accessor.quickInputService.quickAccess.show('bothFastAndSlow'); - await timeout(2); - - assert.equal(fastProviderCalled, false); - assert.equal(slowProviderCalled, false); - assert.equal(fastAndSlowProviderCalled, true); - assert.equal(fastAndSlowProviderCanceled, false); - fastAndSlowProviderCalled = false; - - accessor.quickInputService.quickAccess.show('slow'); - accessor.quickInputService.quickAccess.show('bothFastAndSlow'); - accessor.quickInputService.quickAccess.show('fast'); - - assert.equal(fastProviderCalled, true); - assert.equal(slowProviderCalled, true); - assert.equal(fastAndSlowProviderCalled, true); - - await timeout(2); - assert.equal(slowProviderCanceled, true); - assert.equal(fastAndSlowProviderCanceled, true); - - disposables.dispose(); - - restore(); - }); -}); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index aae9b42a06..0620fa3a44 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -75,7 +75,6 @@ import { Schemas } from 'vs/base/common/network'; import { IProductService } from 'vs/platform/product/common/productService'; import product from 'vs/platform/product/common/product'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { find } from 'vs/base/common/arrays'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { IFilesConfigurationService, FilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; @@ -114,6 +113,7 @@ import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFil import { newWriteableStream, ReadableStreamEvents } from 'vs/base/common/stream'; import { EncodingOracle, IEncodingOverride } from 'vs/workbench/services/textfile/browser/textFileService'; import { UTF16le, UTF16be, UTF8_with_bom } from 'vs/workbench/services/textfile/common/encoding'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; export function createFileEditorInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined, undefined); @@ -555,7 +555,7 @@ export class TestEditorGroupsService implements IEditorGroupsService { get count(): number { return this.groups.length; } getGroups(_order?: GroupsOrder): ReadonlyArray { return this.groups; } - getGroup(identifier: number): IEditorGroup | undefined { return find(this.groups, group => group.id === identifier); } + getGroup(identifier: number): IEditorGroup | undefined { return this.groups.find(group => group.id === identifier); } getLabel(_identifier: number): string { return 'Group 1'; } findGroup(_scope: IFindGroupScope, _source?: number | IEditorGroup, _wrap?: boolean): IEditorGroup { throw new Error('not implemented'); } activateGroup(_group: number | IEditorGroup): IEditorGroup { throw new Error('not implemented'); } @@ -1037,6 +1037,9 @@ export class TestHostService implements IHostService { async openWindow(arg1?: IOpenEmptyWindowOptions | IWindowOpenable[], arg2?: IOpenWindowOptions): Promise { } async toggleFullScreen(): Promise { } + + readonly colorScheme = ColorScheme.DARK; + onDidChangeColorScheme = Event.None; } export class TestFilesConfigurationService extends FilesConfigurationService { @@ -1227,6 +1230,8 @@ export class TestPathService implements IPathService { async fileURI(path: string): Promise { return URI.file(path); } + + readonly defaultUriScheme = Schemas.vscodeRemote; } export class TestTextFileEditorModelManager extends TextFileEditorModelManager { 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 1b840e70ca..88b580af38 100644 --- a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts @@ -15,7 +15,7 @@ import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbe import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import * as minimist from 'minimist'; import * as path from 'vs/base/common/path'; -import { LocalSearchService } from 'vs/workbench/services/search/node/searchService'; +import { LocalSearchService } from 'vs/workbench/services/search/electron-browser/searchService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { TestEditorService, TestEditorGroupsService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestEnvironmentService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; diff --git a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts index e6bf9eefc4..0f0459bd06 100644 --- a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts @@ -6,7 +6,7 @@ import { workbenchInstantiationService as browserWorkbenchInstantiationService, ITestInstantiationService, TestLifecycleService, TestFilesConfigurationService, TestFileService, TestFileDialogService, TestPathService, TestEncodingOracle } from 'vs/workbench/test/browser/workbenchTestServices'; import { Event } from 'vs/base/common/event'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; -import { NativeWorkbenchEnvironmentService, INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; +import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { NativeTextFileService, } from 'vs/workbench/services/textfile/electron-browser/nativeTextFileService'; import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron'; import { FileOperationError, IFileService } from 'vs/platform/files/common/files'; @@ -15,6 +15,7 @@ import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INativeWorkbenchConfiguration, INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { IDialogService, IFileDialogService, INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -35,13 +36,14 @@ import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { NodeTestBackupFileService } from 'vs/workbench/services/backup/test/electron-browser/backupFileService.test'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { INativeWindowConfiguration } from 'vs/platform/windows/node/window'; import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { MouseInputEvent } from 'vs/base/parts/sandbox/common/electronTypes'; import { IModeService } from 'vs/editor/common/services/modeService'; +import { IOSProperties } from 'vs/platform/electron/common/electron'; +import { ColorScheme } from 'vs/platform/theme/common/theme'; -export const TestWindowConfiguration: INativeWindowConfiguration = { +export const TestWorkbenchConfiguration: INativeWorkbenchConfiguration = { windowId: 0, machineId: 'testMachineId', sessionId: 'testSessionId', @@ -52,10 +54,11 @@ export const TestWindowConfiguration: INativeWindowConfiguration = { userEnv: {}, execPath: process.execPath, perfEntries: [], + colorScheme: ColorScheme.DARK, ...parseArgs(process.argv, OPTIONS) }; -export const TestEnvironmentService = new NativeWorkbenchEnvironmentService(TestWindowConfiguration, process.execPath); +export const TestEnvironmentService = new NativeWorkbenchEnvironmentService(TestWorkbenchConfiguration); export class TestTextFileService extends NativeTextFileService { private resolveTextContentError!: FileOperationError | null; @@ -164,6 +167,7 @@ export class TestElectronService implements IElectronService { onWindowFocus: Event = Event.None; onWindowBlur: Event = Event.None; onOSResume: Event = Event.None; + onColorSchemeChange = Event.None; windowCount = Promise.resolve(1); getWindowCount(): Promise { return this.windowCount; } @@ -195,6 +199,7 @@ export class TestElectronService implements IElectronService { async setRepresentedFilename(path: string): Promise { } async isAdmin(): Promise { return false; } async getTotalMem(): Promise { return 0; } + async getOS(): Promise { return Object.create(null); } async killProcess(): Promise { } async setDocumentEdited(edited: boolean): Promise { } async openExternal(url: string): Promise { return false; } diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index dfa2f3be0b..cdcb262189 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -309,6 +309,7 @@ import 'vs/workbench/contrib/url/browser/url.contribution'; // Webview import 'vs/workbench/contrib/webview/browser/webview.contribution'; +import 'vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution'; import 'vs/workbench/contrib/webviewView/browser/webviewView.contribution'; import 'vs/workbench/contrib/customEditor/browser/customEditor.contribution'; diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index e7cc0a1f47..bc42c04f03 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -36,21 +36,17 @@ import 'vs/workbench/electron-browser/desktop.main'; import 'vs/workbench/services/integrity/node/integrityService'; import 'vs/workbench/services/textMate/electron-browser/textMateService'; -import 'vs/workbench/services/search/node/searchService'; +import 'vs/workbench/services/search/electron-browser/searchService'; import 'vs/workbench/services/output/electron-browser/outputChannelModelService'; import 'vs/workbench/services/textfile/electron-browser/nativeTextFileService'; -import 'vs/workbench/services/dialogs/electron-browser/dialogService'; import 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService'; import 'vs/workbench/services/extensions/electron-browser/extensionService'; import 'vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService'; import 'vs/workbench/services/extensionManagement/electron-browser/extensionTipsService'; import 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; import 'vs/workbench/services/telemetry/electron-browser/telemetryService'; -import 'vs/workbench/services/configurationResolver/electron-browser/configurationResolverService'; import 'vs/workbench/services/extensionManagement/node/extensionManagementService'; -import 'vs/workbench/services/accessibility/electron-browser/accessibilityService'; import 'vs/workbench/services/backup/node/backupFileService'; -import 'vs/workbench/services/workspaces/electron-browser/workspaceEditingService'; import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncMachinesService'; import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncService'; import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncAccountService'; @@ -58,7 +54,6 @@ import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncStoreMan import 'vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService'; import 'vs/workbench/services/sharedProcess/electron-browser/sharedProcessService'; import 'vs/workbench/services/localizations/electron-browser/localizationsService'; -import 'vs/workbench/services/path/electron-browser/pathService'; import 'vs/workbench/services/experiment/electron-browser/experimentService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -92,9 +87,6 @@ registerSingleton(IQueryHistoryService, QueryHistoryService); //#region --- workbench contributions -// Logs -import 'vs/workbench/contrib/logs/electron-browser/logs.contribution'; - // Tags import 'vs/workbench/contrib/tags/electron-browser/workspaceTagsService'; import 'vs/workbench/contrib/tags/electron-browser/tags.contribution'; diff --git a/src/vs/workbench/workbench.sandbox.main.ts b/src/vs/workbench/workbench.sandbox.main.ts index 0c26d211aa..519ce54439 100644 --- a/src/vs/workbench/workbench.sandbox.main.ts +++ b/src/vs/workbench/workbench.sandbox.main.ts @@ -23,6 +23,7 @@ import 'vs/workbench/services/dialogs/electron-sandbox/fileDialogService'; import 'vs/workbench/services/workspaces/electron-sandbox/workspacesService'; import 'vs/workbench/services/userDataSync/electron-sandbox/storageKeysSyncRegistryService'; import 'vs/workbench/services/menubar/electron-sandbox/menubarService'; +import 'vs/workbench/services/dialogs/electron-sandbox/dialogService'; import 'vs/workbench/services/issue/electron-sandbox/issueService'; import 'vs/workbench/services/update/electron-sandbox/updateService'; import 'vs/workbench/services/url/electron-sandbox/urlService'; @@ -33,12 +34,20 @@ import 'vs/workbench/services/request/electron-sandbox/requestService'; import 'vs/workbench/services/extensionResourceLoader/electron-sandbox/extensionResourceLoaderService'; import 'vs/workbench/services/clipboard/electron-sandbox/clipboardService'; import 'vs/workbench/services/contextmenu/electron-sandbox/contextmenuService'; +import 'vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService'; +import 'vs/workbench/services/configurationResolver/electron-sandbox/configurationResolverService'; +import 'vs/workbench/services/accessibility/electron-sandbox/accessibilityService'; +import 'vs/workbench/services/path/electron-sandbox/pathService'; +import 'vs/workbench/services/themes/electron-sandbox/nativeHostColorSchemeService'; //#endregion //#region --- workbench contributions +// Logs +import 'vs/workbench/contrib/logs/electron-sandbox/logs.contribution'; + // Localizations import 'vs/workbench/contrib/localizations/browser/localizations.contribution'; diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index e6e9f54219..14745c67a9 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -147,12 +147,19 @@ interface IWindowIndicator { command?: string; } +enum ColorScheme { + DARK = 'dark', + LIGHT = 'light', + HIGH_CONTRAST = 'hc' +} + + interface IInitialColorTheme { /** * Initial color theme type. */ - themeType: 'light' | 'dark' | 'hc'; + themeType: ColorScheme; /** * A list of workbench colors to apply initially. diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index f6407e59f7..bac46143c6 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -50,6 +50,7 @@ import 'vs/workbench/services/lifecycle/browser/lifecycleService'; import 'vs/workbench/services/clipboard/browser/clipboardService'; import 'vs/workbench/services/extensionResourceLoader/browser/extensionResourceLoaderService'; import 'vs/workbench/services/path/browser/pathService'; +import 'vs/workbench/services/themes/browser/browserHostColorSchemeService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; @@ -122,8 +123,7 @@ import 'vs/workbench/contrib/preferences/browser/keyboardLayoutPicker'; import 'vs/workbench/contrib/debug/browser/extensionHostDebugService'; // Webview -import 'vs/workbench/contrib/webview/browser/webviewService'; -import 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; +import 'vs/workbench/contrib/webview/browser/webview.web.contribution'; // Terminal import 'vs/workbench/contrib/terminal/browser/terminal.web.contribution'; diff --git a/test/smoke/src/areas/notebook/notebook.test.ts b/test/smoke/src/areas/notebook/notebook.test.ts index f85cba4af7..8780586fd9 100644 --- a/test/smoke/src/areas/notebook/notebook.test.ts +++ b/test/smoke/src/areas/notebook/notebook.test.ts @@ -63,7 +63,7 @@ export function setup() { await app.workbench.notebook.waitForActiveCellEditorContents('code()'); }); - it.skip('cell action execution', async function () { + it('cell action execution', async function () { const app = this.app as Application; await app.workbench.notebook.openNotebook(); await app.workbench.notebook.insertNotebookCell('code'); diff --git a/test/unit/electron/index.js b/test/unit/electron/index.js index c577fab7f1..9007252710 100644 --- a/test/unit/electron/index.js +++ b/test/unit/electron/index.js @@ -130,6 +130,7 @@ app.on('ready', () => { nodeIntegration: true, enableWebSQL: false, enableRemoteModule: false, + spellcheck: false, nativeWindowOpen: true, webviewTag: true } diff --git a/yarn.lock b/yarn.lock index 47fa20d474..6426538872 100644 --- a/yarn.lock +++ b/yarn.lock @@ -709,6 +709,13 @@ agent-base@5: resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c" integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g== +agent-base@6: + version "6.0.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.1.tgz#808007e4e5867decb0ab6ab2f928fbdb5a596db4" + integrity sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg== + dependencies: + debug "4" + agent-base@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" @@ -1769,7 +1776,7 @@ cheerio@^1.0.0-rc.1: lodash "^4.15.0" parse5 "^3.0.1" -chokidar@*, chokidar@3.2.3: +chokidar@*: version "3.2.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.2.3.tgz#b9270a565d14f02f6bfdd537a6a2bbf5549b8c8c" integrity sha512-GtrxGuRf6bzHQmXWRepvsGnXpkQkVU+D2/9a7dAe4a7v1NhrfZOZ2oKf76M3nOs46fFYL8D+Q8JYA4GYeJ8Cjw== @@ -1784,6 +1791,21 @@ chokidar@*, chokidar@3.2.3: optionalDependencies: fsevents "~2.1.1" +chokidar@3.4.2: + version "3.4.2" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.2.tgz#38dc8e658dec3809741eb3ef7bb0a47fe424232d" + integrity sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A== + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.4.0" + optionalDependencies: + fsevents "~2.1.2" + chokidar@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.0.tgz#5fcb70d0b28ebe0867eb0f09d5f6a08f29a1efa0" @@ -2922,10 +2944,10 @@ electron-to-chromium@^1.2.7: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.27.tgz#78ecb8a399066187bb374eede35d9c70565a803d" integrity sha1-eOy4o5kGYYe7N07t412ccFZagD0= -electron@9.2.1: - version "9.2.1" - resolved "https://registry.yarnpkg.com/electron/-/electron-9.2.1.tgz#54ef574e1af4ae967b5efa94312f1b6458d44a02" - integrity sha512-ZsetaQjXB8+9/EFW1FnfK4ukpkwXCxMEaiKiUZhZ0ZLFlLnFCpe0Bg4vdDf7e4boWGcnlgN1jAJpBw7w0eXuqA== +electron@9.3.0: + version "9.3.0" + resolved "https://registry.yarnpkg.com/electron/-/electron-9.3.0.tgz#a4f3dc17f31acc6797eb4c2c4bd0d0e25efb939b" + integrity sha512-7zPLEZ+kOjVJqfawMQ0vVuZZRqvZIeiID3tbjjbVybbxXIlFMpZ2jogoh7PV3rLrtm+dKRfu7Qc4E7ob1d0FqQ== dependencies: "@electron/get" "^1.0.1" "@types/node" "^12.0.12" @@ -4796,14 +4818,6 @@ https-proxy-agent@^2.2.3: agent-base "^4.3.0" debug "^3.1.0" -https-proxy-agent@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz#b8c286433e87602311b01c8ea34413d856a4af81" - integrity sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg== - dependencies: - agent-base "^4.3.0" - debug "^3.1.0" - https-proxy-agent@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b" @@ -4812,6 +4826,14 @@ https-proxy-agent@^4.0.0: agent-base "5" debug "4" +https-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" + integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== + dependencies: + agent-base "6" + debug "4" + husky@^0.13.1: version "0.13.4" resolved "https://registry.yarnpkg.com/husky/-/husky-0.13.4.tgz#48785c5028de3452a51c48c12c4f94b2124a1407" @@ -5458,10 +5480,10 @@ jade@0.26.3: commander "0.6.1" mkdirp "0.3.0" -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== +jpeg-js@^0.4.0: + version "0.4.2" + resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.2.tgz#8b345b1ae4abde64c2da2fe67ea216a114ac279d" + integrity sha512-+az2gi/hvex7eLTMTlbRLOhH6P6WFdk2ITI8HJsaH2VqYO0I594zXSYEP+tf4FW+8Cy68ScDXoAsQdyQanv3sw== jquery@3.5.0: version "3.5.0" @@ -6613,10 +6635,10 @@ node-pre-gyp@^0.10.0: semver "^5.3.0" tar "^4" -node-pty@0.10.0-beta8: - version "0.10.0-beta8" - resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.10.0-beta8.tgz#f4aa56c71a794f4580373a3030dfebd25240c6db" - integrity sha512-Ul/hLsadC0SvvShxpne+kq2ebSMcitewlNhrwoXXBvFdCqxJt7Ai1AgMhH7AKBUp06uBeYXThJ2ihTszrkdnYw== +node-pty@0.10.0-beta17: + version "0.10.0-beta17" + resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.10.0-beta17.tgz#962d4a3f4dc6772385e0cad529c209cef3bc79e6" + integrity sha512-tn7EANQacnAvnOQCImvgag1DL0tVmUoY/1yIZbh3u/BBpvCcGHLZJNn7TXheodRLr6hmGSUS2VbfcUr9p0gOug== dependencies: nan "^2.14.0" @@ -7327,15 +7349,15 @@ pkg-dir@^3.0.0: dependencies: find-up "^3.0.0" -playwright-core@=1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.0.1.tgz#823b6f1afa16917ffd418f3cec0c14688a985738" - integrity sha512-a71FjUDRFqWLG3VBAojVen2TaZiXkuog+ZmI0Nh0+/QndFUbbW3kameOfUTMXFvLUGWx2ipERZx6EQTJMEQDMA== +playwright@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.3.0.tgz#8c33ed29bc0c7d97f82f8322e99be6d7f0d9ff67" + integrity sha512-W3mwXv2XNFugbepSZTZxI314WfI1SAjdZBEeGOu8S5KnPz4RSlunUFgXn6496o8lobPmORLcJ9VTSGyiFfGpaw== dependencies: debug "^4.1.1" extract-zip "^2.0.0" - https-proxy-agent "^3.0.0" - jpeg-js "^0.3.7" + https-proxy-agent "^5.0.0" + jpeg-js "^0.4.0" mime "^2.4.4" pngjs "^5.0.0" progress "^2.0.3" @@ -7343,13 +7365,6 @@ playwright-core@=1.0.1: rimraf "^3.0.2" ws "^6.1.0" -playwright@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.0.1.tgz#326d479829a3505799ddc9988cc8decf5a7f8376" - integrity sha512-kVTE7uvZ7OcDVOBx7MVArUm2nbzzzpauKV9tuVIAH6vWGsOWbGGALUoTWMzNDzsPPTBJXXmxzC4KgI2zN+kVhw== - dependencies: - playwright-core "=1.0.1" - plist@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/plist/-/plist-3.0.1.tgz#a9b931d17c304e8912ef0ba3bdd6182baf2e1f8c" @@ -10216,10 +10231,10 @@ vscode-proxy-agent@^0.5.2: https-proxy-agent "^2.2.3" socks-proxy-agent "^4.0.1" -vscode-ripgrep@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.8.0.tgz#dfe7c2ae2a2032df8a8108765c2feef73474888a" - integrity sha512-/Q5XtePkTLLi8yplr5ai24pVEymRF62xH9xXrtj35GTaDCJg3zq1s1/L1UqhVbfNDv4OcMBYjyIAt/quEi3d5w== +vscode-ripgrep@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.9.0.tgz#d6cdea4d290f3c2919472cdcfe2440d5fb1f99db" + integrity sha512-7jyAC/NNfvMPZgCVkyqIn0STYJ7wIk3PF2qA2cX1sEutx1g/e2VtgKAodXnfpreJq4993JT/BSIigOv/0lBSzg== dependencies: https-proxy-agent "^4.0.0" proxy-from-env "^1.1.0" @@ -10612,15 +10627,15 @@ xterm-addon-unicode11@0.2.0: resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.2.0.tgz#9ed0c482b353908bba27778893ca80823382737c" integrity sha512-rjFDItPc/IDoSiEnoDFwKroNwLD/7t9vYKENjrcKVZg5tgJuuUj8D4rZtP6iVCjSB1LTLYmUs4L/EmCqIyLR/Q== -xterm-addon-webgl@0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.8.0.tgz#4bc6bb4dbfea5b0d2d7978d6c5cef922d584fb4f" - integrity sha512-dlpYPsv0C9S6v6+T/h/d/otSbdUTizMJdxvSoS34tUpMOHev6iW7Zqt5KRFqYxl4vCqpDk9Wmhb3fKL3kwX5fQ== +xterm-addon-webgl@0.9.0-beta.4: + version "0.9.0-beta.4" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.9.0-beta.4.tgz#5f5fde50db5c06b116471bcf56ad9930884b36fa" + integrity sha512-GuCvF7Eg1nKLX6zUbJLkt5cqeeccUjf/G6fugCfrkR0EWWC6Ik5mEsEOs5UWm9vvY2kX9t16BdCrgqp8KJegEg== -xterm@4.9.0-beta.8: - version "4.9.0-beta.8" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.9.0-beta.8.tgz#ca121934d63f88668d2d5b11d9b2fc3bde7bd805" - integrity sha512-EEonYBLANDUBfEeEnHG632bZdgBaAUWst8LFr6oC6f2uLFfJGHQvVJuLaEkPtRvS+jOeoorEXZRPmso1/ANHXA== +xterm@4.9.0-beta.32: + version "4.9.0-beta.32" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.9.0-beta.32.tgz#d1243d3be211cc06aad3418e696e4eced995c20c" + integrity sha512-jloHNBnj6XRJt+oPkapvrXJZVsYq6se/PEgzErl0iZn9qzSB3jmWE4byumoEjXJR6EgU5ZOmNljeeEDA9jO/jA== y18n@^3.2.1: version "3.2.1"