Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/microsoft/vscode.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohannes <johannes.rieken@gmail.com>2022-07-13 13:01:36 +0300
committerJohannes <johannes.rieken@gmail.com>2022-07-13 13:01:36 +0300
commit78693265be2fa394330df097c3fd99925dff21d4 (patch)
treeebbed0a9f40c67fbfb152a7730c9e61774616f01 /src/vs/workbench
parent268c941bf0fd8255d4dd7c106c22e9b911772916 (diff)
parent4404dc63561a286e13f9ba5a669e5403a367425a (diff)
Merge branch 'main' into joh/cellUrijoh/cellUri
Diffstat (limited to 'src/vs/workbench')
-rw-r--r--src/vs/workbench/api/browser/mainThreadBulkEdits.ts31
-rw-r--r--src/vs/workbench/api/browser/mainThreadDebugService.ts7
-rw-r--r--src/vs/workbench/api/browser/mainThreadDialogs.ts9
-rw-r--r--src/vs/workbench/api/browser/mainThreadEditorTabs.ts9
-rw-r--r--src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts5
-rw-r--r--src/vs/workbench/api/browser/mainThreadTask.ts22
-rw-r--r--src/vs/workbench/api/browser/mainThreadTerminalService.ts6
-rw-r--r--src/vs/workbench/api/browser/viewsExtensionPoint.ts27
-rw-r--r--src/vs/workbench/api/common/extHost.api.impl.ts6
-rw-r--r--src/vs/workbench/api/common/extHost.protocol.ts62
-rw-r--r--src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts8
-rw-r--r--src/vs/workbench/api/common/extHostEditorTabs.ts6
-rw-r--r--src/vs/workbench/api/common/extHostFileSystemEventService.ts11
-rw-r--r--src/vs/workbench/api/common/extHostLanguageFeatures.ts14
-rw-r--r--src/vs/workbench/api/common/extHostTerminalService.ts10
-rw-r--r--src/vs/workbench/api/common/extHostTreeViews.ts11
-rw-r--r--src/vs/workbench/api/common/extHostTypeConverters.ts58
-rw-r--r--src/vs/workbench/api/common/extHostTypes.ts166
-rw-r--r--src/vs/workbench/api/common/shared/tasks.ts1
-rw-r--r--src/vs/workbench/api/test/browser/extHostApiCommands.test.ts4
-rw-r--r--src/vs/workbench/api/test/browser/extHostBulkEdits.test.ts9
-rw-r--r--src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts6
-rw-r--r--src/vs/workbench/api/test/browser/extHostTypeConverter.test.ts11
-rw-r--r--src/vs/workbench/api/test/browser/mainThreadEditors.test.ts23
-rw-r--r--src/vs/workbench/browser/actions.ts2
-rw-r--r--src/vs/workbench/browser/actions/layoutActions.ts2
-rw-r--r--src/vs/workbench/browser/parts/editor/editor.ts2
-rw-r--r--src/vs/workbench/browser/parts/editor/editorActions.ts35
-rw-r--r--src/vs/workbench/browser/parts/editor/editorCommands.ts1
-rw-r--r--src/vs/workbench/browser/parts/editor/editorGroupView.ts78
-rw-r--r--src/vs/workbench/browser/parts/editor/editorPart.ts20
-rw-r--r--src/vs/workbench/browser/parts/statusbar/statusbarItem.ts4
-rw-r--r--src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts53
-rw-r--r--src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css59
-rw-r--r--src/vs/workbench/browser/parts/titlebar/windowTitle.ts6
-rw-r--r--src/vs/workbench/browser/web.main.ts19
-rw-r--r--src/vs/workbench/browser/workbench.contribution.ts10
-rw-r--r--src/vs/workbench/browser/workbench.ts7
-rw-r--r--src/vs/workbench/common/editor/editorInput.ts45
-rw-r--r--src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts2
-rw-r--r--src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts35
-rw-r--r--src/vs/workbench/contrib/audioCues/browser/audioCueService.ts10
-rw-r--r--src/vs/workbench/contrib/audioCues/browser/observable.ts690
-rw-r--r--src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts26
-rw-r--r--src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts29
-rw-r--r--src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts5
-rw-r--r--src/vs/workbench/contrib/comments/browser/commentNode.ts2
-rw-r--r--src/vs/workbench/contrib/comments/browser/commentReply.ts8
-rw-r--r--src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts3
-rw-r--r--src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts14
-rw-r--r--src/vs/workbench/contrib/comments/browser/commentsView.ts2
-rw-r--r--src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts4
-rw-r--r--src/vs/workbench/contrib/debug/browser/callStackView.ts4
-rw-r--r--src/vs/workbench/contrib/debug/browser/debug.contribution.ts34
-rw-r--r--src/vs/workbench/contrib/debug/browser/debugCommands.ts183
-rw-r--r--src/vs/workbench/contrib/debug/browser/debugService.ts10
-rw-r--r--src/vs/workbench/contrib/debug/browser/debugSession.ts6
-rw-r--r--src/vs/workbench/contrib/debug/browser/debugToolBar.ts4
-rw-r--r--src/vs/workbench/contrib/debug/common/debug.ts6
-rw-r--r--src/vs/workbench/contrib/debug/common/debugModel.ts26
-rw-r--r--src/vs/workbench/contrib/debug/node/telemetryApp.ts4
-rw-r--r--src/vs/workbench/contrib/debug/test/browser/mockDebug.ts4
-rw-r--r--src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts (renamed from src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts)137
-rw-r--r--src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts (renamed from src/vs/workbench/services/sessionSync/browser/sessionSyncWorkbenchService.ts)65
-rw-r--r--src/vs/workbench/contrib/editSessions/common/editSessions.ts (renamed from src/vs/workbench/services/sessionSync/common/sessionSync.ts)14
-rw-r--r--src/vs/workbench/contrib/editSessions/common/editSessionsLogService.ts50
-rw-r--r--src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts (renamed from src/vs/workbench/contrib/sessionSync/test/browser/sessionSync.test.ts)26
-rw-r--r--src/vs/workbench/contrib/extensions/browser/extensionEditor.ts3
-rw-r--r--src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts15
-rw-r--r--src/vs/workbench/contrib/extensions/browser/extensionsActions.ts179
-rw-r--r--src/vs/workbench/contrib/extensions/browser/extensionsList.ts22
-rw-r--r--src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts7
-rw-r--r--src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts34
-rw-r--r--src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts10
-rw-r--r--src/vs/workbench/contrib/extensions/common/extensions.ts2
-rw-r--r--src/vs/workbench/contrib/files/browser/fileCommands.ts13
-rw-r--r--src/vs/workbench/contrib/files/browser/files.contribution.ts8
-rw-r--r--src/vs/workbench/contrib/files/browser/views/explorerView.ts78
-rw-r--r--src/vs/workbench/contrib/files/common/files.ts2
-rw-r--r--src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts10
-rw-r--r--src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts69
-rw-r--r--src/vs/workbench/contrib/interactive/browser/interactiveCommon.ts5
-rw-r--r--src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts19
-rw-r--r--src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts129
-rw-r--r--src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts2
-rw-r--r--src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts10
-rw-r--r--src/vs/workbench/contrib/localization/browser/localeService.ts51
-rw-r--r--src/vs/workbench/contrib/localization/browser/localizationsActions.ts39
-rw-r--r--src/vs/workbench/contrib/localization/common/locale.ts4
-rw-r--r--src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts52
-rw-r--r--src/vs/workbench/contrib/logs/common/logConstants.ts1
-rw-r--r--src/vs/workbench/contrib/logs/common/logs.contribution.ts1
-rw-r--r--src/vs/workbench/contrib/markers/browser/markersTable.ts9
-rw-r--r--src/vs/workbench/contrib/markers/browser/media/markers.css11
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts142
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts25
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts12
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts140
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts50
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange.ts18
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts8
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/utils.ts9
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts54
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts38
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts272
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts17
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/view/media/mergeEditor.css22
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts125
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts38
-rw-r--r--src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts134
-rw-r--r--src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts90
-rw-r--r--src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts2
-rw-r--r--src/vs/workbench/contrib/notebook/browser/contrib/gettingStarted/notebookGettingStarted.ts5
-rw-r--r--src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts16
-rw-r--r--src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts9
-rw-r--r--src/vs/workbench/contrib/notebook/browser/controller/editActions.ts3
-rw-r--r--src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts8
-rw-r--r--src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts20
-rw-r--r--src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts2
-rw-r--r--src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts2
-rw-r--r--src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts9
-rw-r--r--src/vs/workbench/contrib/notebook/browser/notebookEditor.ts1
-rw-r--r--src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts8
-rw-r--r--src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts25
-rw-r--r--src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts2
-rw-r--r--src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts4
-rw-r--r--src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts7
-rw-r--r--src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts6
-rw-r--r--src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts2
-rw-r--r--src/vs/workbench/contrib/notebook/common/notebookCommon.ts13
-rw-r--r--src/vs/workbench/contrib/notebook/common/notebookOptions.ts4
-rw-r--r--src/vs/workbench/contrib/output/browser/outputServices.ts2
-rw-r--r--src/vs/workbench/contrib/output/common/outputChannelModel.ts28
-rw-r--r--src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css2
-rw-r--r--src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts2
-rw-r--r--src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts14
-rw-r--r--src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts10
-rw-r--r--src/vs/workbench/contrib/remote/common/remote.contribution.ts4
-rw-r--r--src/vs/workbench/contrib/scm/browser/scmViewPane.ts31
-rw-r--r--src/vs/workbench/contrib/search/browser/search.contribution.ts29
-rw-r--r--src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts40
-rw-r--r--src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts14
-rw-r--r--src/vs/workbench/contrib/snippets/browser/snippetsFile.ts11
-rw-r--r--src/vs/workbench/contrib/snippets/browser/snippetsService.ts7
-rw-r--r--src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts36
-rw-r--r--src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts65
-rw-r--r--src/vs/workbench/contrib/tasks/browser/task.contribution.ts32
-rw-r--r--src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts15
-rw-r--r--src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts38
-rw-r--r--src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts9
-rw-r--r--src/vs/workbench/contrib/tasks/common/taskConfiguration.ts17
-rw-r--r--src/vs/workbench/contrib/tasks/common/tasks.ts35
-rw-r--r--src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts30
-rw-r--r--src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts3
-rwxr-xr-xsrc/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh46
-rw-r--r--src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh24
-rw-r--r--src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps116
-rw-r--r--src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts14
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts27
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminal.ts29
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalActions.ts33
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts37
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts3
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalIcon.ts7
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalInstance.ts205
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts6
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalMenus.ts13
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts2
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts5
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts23
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts2
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts11
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalService.ts17
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts2
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalView.ts17
-rw-r--r--src/vs/workbench/contrib/terminal/browser/xterm/commandNavigationAddon.ts4
-rw-r--r--src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts35
-rw-r--r--src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts4
-rw-r--r--src/vs/workbench/contrib/terminal/common/terminal.ts7
-rw-r--r--src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts34
-rw-r--r--src/vs/workbench/contrib/terminal/common/terminalContextKey.ts8
-rw-r--r--src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts10
-rw-r--r--src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts55
-rw-r--r--src/vs/workbench/contrib/terminal/test/browser/links/terminalLocalLinkDetector.test.ts3
-rw-r--r--src/vs/workbench/contrib/terminal/test/browser/xterm/decorationAddon.test.ts8
-rw-r--r--src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts2
-rw-r--r--src/vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper.ts6
-rw-r--r--src/vs/workbench/contrib/testing/browser/testExplorerActions.ts46
-rw-r--r--src/vs/workbench/contrib/testing/browser/testing.contribution.ts1
-rw-r--r--src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts22
-rw-r--r--src/vs/workbench/contrib/testing/browser/testingExplorerView.ts2
-rw-r--r--src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts8
-rw-r--r--src/vs/workbench/contrib/update/browser/update.contribution.ts12
-rw-r--r--src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts18
-rw-r--r--src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts39
-rw-r--r--src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html13
-rw-r--r--src/vs/workbench/contrib/webview/browser/pre/index.html15
-rw-r--r--src/vs/workbench/contrib/webview/browser/webviewElement.ts11
-rw-r--r--src/vs/workbench/contrib/webview/electron-sandbox/webviewElement.ts7
-rw-r--r--src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts5
-rw-r--r--src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts4
-rw-r--r--src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css7
-rw-r--r--src/vs/workbench/electron-sandbox/desktop.contribution.ts8
-rw-r--r--src/vs/workbench/electron-sandbox/desktop.main.ts28
-rw-r--r--src/vs/workbench/electron-sandbox/window.ts15
-rw-r--r--src/vs/workbench/services/actions/common/menusExtensionPoint.ts64
-rw-r--r--src/vs/workbench/services/clipboard/electron-sandbox/clipboardService.ts6
-rw-r--r--src/vs/workbench/services/configuration/browser/configurationService.ts48
-rw-r--r--src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts7
-rw-r--r--src/vs/workbench/services/configuration/test/browser/configurationService.test.ts71
-rw-r--r--src/vs/workbench/services/configurationResolver/browser/baseConfigurationResolverService.ts31
-rw-r--r--src/vs/workbench/services/editor/browser/codeEditorService.ts9
-rw-r--r--src/vs/workbench/services/editor/common/editorGroupsService.ts2
-rw-r--r--src/vs/workbench/services/editor/test/browser/editorService.test.ts8
-rw-r--r--src/vs/workbench/services/environment/browser/environmentService.ts3
-rw-r--r--src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts104
-rw-r--r--src/vs/workbench/services/extensionManagement/common/extensionManagement.ts18
-rw-r--r--src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts7
-rw-r--r--src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts72
-rw-r--r--src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService.ts10
-rw-r--r--src/vs/workbench/services/extensionManagement/electron-sandbox/nativeExtensionManagementService.ts (renamed from src/vs/workbench/services/extensionManagement/common/profileAwareExtensionManagementService.ts)48
-rw-r--r--src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts15
-rw-r--r--src/vs/workbench/services/extensions/browser/extensionService.ts25
-rw-r--r--src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts10
-rw-r--r--src/vs/workbench/services/extensions/common/abstractExtensionService.ts31
-rw-r--r--src/vs/workbench/services/extensions/common/extensionsApiProposals.ts3
-rw-r--r--src/vs/workbench/services/extensions/electron-browser/nativeExtensionService.ts20
-rw-r--r--src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts15
-rw-r--r--src/vs/workbench/services/extensions/electron-sandbox/nativeLocalProcessExtensionHost.ts (renamed from src/vs/workbench/services/extensions/electron-browser/nativeLocalProcessExtensionHost.ts)14
-rw-r--r--src/vs/workbench/services/extensions/electron-sandbox/sandboxExtensionService.ts11
-rw-r--r--src/vs/workbench/services/extensions/test/browser/extensionService.test.ts10
-rw-r--r--src/vs/workbench/services/extensions/test/browser/extensionStorageMigration.test.ts5
-rw-r--r--src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html9
-rw-r--r--src/vs/workbench/services/history/browser/historyService.ts4
-rw-r--r--src/vs/workbench/services/issue/electron-sandbox/issueService.ts13
-rw-r--r--src/vs/workbench/services/keybinding/browser/keybindingService.ts15
-rw-r--r--src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts4
-rw-r--r--src/vs/workbench/services/keybinding/test/node/keyboardMapperTestUtils.ts (renamed from src/vs/workbench/services/keybinding/test/electron-browser/keyboardMapperTestUtils.ts)4
-rw-r--r--src/vs/workbench/services/keybinding/test/node/linux_de_ch.js (renamed from src/vs/workbench/services/keybinding/test/electron-browser/linux_de_ch.js)0
-rw-r--r--src/vs/workbench/services/keybinding/test/node/linux_de_ch.txt (renamed from src/vs/workbench/services/keybinding/test/electron-browser/linux_de_ch.txt)0
-rw-r--r--src/vs/workbench/services/keybinding/test/node/linux_en_uk.js (renamed from src/vs/workbench/services/keybinding/test/electron-browser/linux_en_uk.js)0
-rw-r--r--src/vs/workbench/services/keybinding/test/node/linux_en_uk.txt (renamed from src/vs/workbench/services/keybinding/test/electron-browser/linux_en_uk.txt)0
-rw-r--r--src/vs/workbench/services/keybinding/test/node/linux_en_us.js (renamed from src/vs/workbench/services/keybinding/test/electron-browser/linux_en_us.js)0
-rw-r--r--src/vs/workbench/services/keybinding/test/node/linux_en_us.txt (renamed from src/vs/workbench/services/keybinding/test/electron-browser/linux_en_us.txt)0
-rw-r--r--src/vs/workbench/services/keybinding/test/node/linux_ru.js (renamed from src/vs/workbench/services/keybinding/test/electron-browser/linux_ru.js)0
-rw-r--r--src/vs/workbench/services/keybinding/test/node/linux_ru.txt (renamed from src/vs/workbench/services/keybinding/test/electron-browser/linux_ru.txt)0
-rw-r--r--src/vs/workbench/services/keybinding/test/node/macLinuxFallbackKeyboardMapper.test.ts (renamed from src/vs/workbench/services/keybinding/test/electron-browser/macLinuxFallbackKeyboardMapper.test.ts)2
-rw-r--r--src/vs/workbench/services/keybinding/test/node/macLinuxKeyboardMapper.test.ts (renamed from src/vs/workbench/services/keybinding/test/electron-browser/macLinuxKeyboardMapper.test.ts)2
-rw-r--r--src/vs/workbench/services/keybinding/test/node/mac_de_ch.js (renamed from src/vs/workbench/services/keybinding/test/electron-browser/mac_de_ch.js)0
-rw-r--r--src/vs/workbench/services/keybinding/test/node/mac_de_ch.txt (renamed from src/vs/workbench/services/keybinding/test/electron-browser/mac_de_ch.txt)0
-rw-r--r--src/vs/workbench/services/keybinding/test/node/mac_en_us.js (renamed from src/vs/workbench/services/keybinding/test/electron-browser/mac_en_us.js)0
-rw-r--r--src/vs/workbench/services/keybinding/test/node/mac_en_us.txt (renamed from src/vs/workbench/services/keybinding/test/electron-browser/mac_en_us.txt)0
-rw-r--r--src/vs/workbench/services/keybinding/test/node/mac_zh_hant.js (renamed from src/vs/workbench/services/keybinding/test/electron-browser/mac_zh_hant.js)0
-rw-r--r--src/vs/workbench/services/keybinding/test/node/mac_zh_hant.txt (renamed from src/vs/workbench/services/keybinding/test/electron-browser/mac_zh_hant.txt)0
-rw-r--r--src/vs/workbench/services/keybinding/test/node/mac_zh_hant2.js (renamed from src/vs/workbench/services/keybinding/test/electron-browser/mac_zh_hant2.js)0
-rw-r--r--src/vs/workbench/services/keybinding/test/node/mac_zh_hant2.txt (renamed from src/vs/workbench/services/keybinding/test/electron-browser/mac_zh_hant2.txt)0
-rw-r--r--src/vs/workbench/services/keybinding/test/node/win_de_ch.js (renamed from src/vs/workbench/services/keybinding/test/electron-browser/win_de_ch.js)0
-rw-r--r--src/vs/workbench/services/keybinding/test/node/win_de_ch.txt (renamed from src/vs/workbench/services/keybinding/test/electron-browser/win_de_ch.txt)0
-rw-r--r--src/vs/workbench/services/keybinding/test/node/win_en_us.js (renamed from src/vs/workbench/services/keybinding/test/electron-browser/win_en_us.js)0
-rw-r--r--src/vs/workbench/services/keybinding/test/node/win_en_us.txt (renamed from src/vs/workbench/services/keybinding/test/electron-browser/win_en_us.txt)0
-rw-r--r--src/vs/workbench/services/keybinding/test/node/win_por_ptb.js (renamed from src/vs/workbench/services/keybinding/test/electron-browser/win_por_ptb.js)0
-rw-r--r--src/vs/workbench/services/keybinding/test/node/win_por_ptb.txt (renamed from src/vs/workbench/services/keybinding/test/electron-browser/win_por_ptb.txt)0
-rw-r--r--src/vs/workbench/services/keybinding/test/node/win_ru.js (renamed from src/vs/workbench/services/keybinding/test/electron-browser/win_ru.js)0
-rw-r--r--src/vs/workbench/services/keybinding/test/node/win_ru.txt (renamed from src/vs/workbench/services/keybinding/test/electron-browser/win_ru.txt)0
-rw-r--r--src/vs/workbench/services/keybinding/test/node/windowsKeyboardMapper.test.ts (renamed from src/vs/workbench/services/keybinding/test/electron-browser/windowsKeyboardMapper.test.ts)2
-rw-r--r--src/vs/workbench/services/storage/browser/storageService.ts39
-rw-r--r--src/vs/workbench/services/storage/test/browser/storageService.test.ts6
-rw-r--r--src/vs/workbench/services/telemetry/browser/telemetryService.ts13
-rw-r--r--src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts62
-rw-r--r--src/vs/workbench/services/userDataProfile/common/userDataProfile.ts5
-rw-r--r--src/vs/workbench/services/userDataProfile/common/userDataProfileImportExportService.ts22
-rw-r--r--src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts22
-rw-r--r--src/vs/workbench/test/browser/workbenchTestServices.ts28
-rw-r--r--src/vs/workbench/test/electron-browser/workbenchTestServices.ts11
-rw-r--r--src/vs/workbench/workbench.common.main.ts7
-rw-r--r--src/vs/workbench/workbench.desktop.main.ts176
-rw-r--r--src/vs/workbench/workbench.desktop.sandbox.main.ts32
-rw-r--r--src/vs/workbench/workbench.sandbox.main.ts159
-rw-r--r--src/vs/workbench/workbench.web.main.ts1
279 files changed, 4063 insertions, 3087 deletions
diff --git a/src/vs/workbench/api/browser/mainThreadBulkEdits.ts b/src/vs/workbench/api/browser/mainThreadBulkEdits.ts
index e3e5d652a16..b47e47a7286 100644
--- a/src/vs/workbench/api/browser/mainThreadBulkEdits.ts
+++ b/src/vs/workbench/api/browser/mainThreadBulkEdits.ts
@@ -4,29 +4,28 @@
*--------------------------------------------------------------------------------------------*/
import { IBulkEditService, ResourceEdit, ResourceFileEdit, ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService';
-import { IWorkspaceEditDto, MainThreadBulkEditsShape, MainContext, WorkspaceEditType } from 'vs/workbench/api/common/extHost.protocol';
+import { IWorkspaceEditDto, MainThreadBulkEditsShape, MainContext, reviveWorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol';
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
import { ILogService } from 'vs/platform/log/common/log';
-import { revive } from 'vs/base/common/marshalling';
import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits';
-import { NotebookDto } from 'vs/workbench/api/browser/mainThreadNotebookDto';
-export function reviveWorkspaceEditDto2(data: IWorkspaceEditDto | undefined): ResourceEdit[] {
- if (!data?.edits) {
+export function reviveWorkspaceEditDto2(data: IWorkspaceEditDto): ResourceEdit[] {
+ const edits = reviveWorkspaceEditDto(data)?.edits;
+ if (!edits) {
return [];
}
-
- const result: ResourceEdit[] = [];
- for (const edit of revive<IWorkspaceEditDto>(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, NotebookDto.fromCellEditOperationDto(edit.edit), edit.notebookVersionId, edit.metadata));
+ return edits.map(edit => {
+ if (ResourceTextEdit.is(edit)) {
+ return ResourceTextEdit.lift(edit);
}
- }
- return result;
+ if (ResourceFileEdit.is(edit)) {
+ return ResourceFileEdit.lift(edit);
+ }
+ if (ResourceNotebookCellEdit.is(edit)) {
+ return ResourceNotebookCellEdit.lift(edit);
+ }
+ throw new Error('Unsupported edit');
+ });
}
@extHostNamedCustomer(MainContext.MainThreadBulkEdits)
diff --git a/src/vs/workbench/api/browser/mainThreadDebugService.ts b/src/vs/workbench/api/browser/mainThreadDebugService.ts
index c0ec9d1b550..1b0e0389246 100644
--- a/src/vs/workbench/api/browser/mainThreadDebugService.ts
+++ b/src/vs/workbench/api/browser/mainThreadDebugService.ts
@@ -223,6 +223,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
const folderUri = folder ? uri.revive(folder) : undefined;
const launch = this.debugService.getConfigurationManager().getLaunch(folderUri);
const parentSession = this.getSession(options.parentSessionID);
+ const saveBeforeStart = typeof options.suppressSaveBeforeStart === 'boolean' ? !options.suppressSaveBeforeStart : undefined;
const debugOptions: IDebugSessionOptions = {
noDebug: options.noDebug,
parentSession,
@@ -230,11 +231,11 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
repl: options.repl,
compact: options.compact,
debugUI: options.debugUI,
- compoundRoot: parentSession?.compoundRoot
+ compoundRoot: parentSession?.compoundRoot,
+ saveBeforeStart: saveBeforeStart
};
try {
- const saveBeforeStart = typeof options.suppressSaveBeforeStart === 'boolean' ? !options.suppressSaveBeforeStart : undefined;
- return this.debugService.startDebugging(launch, nameOrConfig, debugOptions, saveBeforeStart);
+ return this.debugService.startDebugging(launch, nameOrConfig, debugOptions);
} catch (err) {
throw new ErrorNoTelemetry(err && err.message ? err.message : 'cannot start debugging');
}
diff --git a/src/vs/workbench/api/browser/mainThreadDialogs.ts b/src/vs/workbench/api/browser/mainThreadDialogs.ts
index 1f8066b2c0d..e9ca6d75502 100644
--- a/src/vs/workbench/api/browser/mainThreadDialogs.ts
+++ b/src/vs/workbench/api/browser/mainThreadDialogs.ts
@@ -6,7 +6,6 @@
import { URI } from 'vs/base/common/uri';
import { MainThreadDiaglogsShape, MainContext, MainThreadDialogOpenOptions, MainThreadDialogSaveOptions } from '../common/extHost.protocol';
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
-import { forEach } from 'vs/base/common/collections';
import { IFileDialogService, IOpenDialogOptions, ISaveDialogOptions } from 'vs/platform/dialogs/common/dialogs';
@extHostNamedCustomer(MainContext.MainThreadDialogs)
@@ -51,7 +50,9 @@ export class MainThreadDialogs implements MainThreadDiaglogsShape {
};
if (options?.filters) {
result.filters = [];
- forEach(options.filters, entry => result.filters!.push({ name: entry.key, extensions: entry.value }));
+ for (const [key, value] of Object.entries(options.filters)) {
+ result.filters!.push({ name: key, extensions: value });
+ }
}
return result;
}
@@ -64,7 +65,9 @@ export class MainThreadDialogs implements MainThreadDiaglogsShape {
};
if (options?.filters) {
result.filters = [];
- forEach(options.filters, entry => result.filters!.push({ name: entry.key, extensions: entry.value }));
+ for (const [key, value] of Object.entries(options.filters)) {
+ result.filters.push({ name: key, extensions: value });
+ }
}
return result;
}
diff --git a/src/vs/workbench/api/browser/mainThreadEditorTabs.ts b/src/vs/workbench/api/browser/mainThreadEditorTabs.ts
index 2bcb34d00d1..c410ad77dc0 100644
--- a/src/vs/workbench/api/browser/mainThreadEditorTabs.ts
+++ b/src/vs/workbench/api/browser/mainThreadEditorTabs.ts
@@ -22,6 +22,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput';
import { isEqual } from 'vs/base/common/resources';
import { isGroupEditorMoveEvent } from 'vs/workbench/common/editor/editorGroupModel';
+import { InteractiveEditorInput } from 'vs/workbench/contrib/interactive/browser/interactiveEditorInput';
interface TabInfo {
tab: IEditorTabDto;
@@ -162,6 +163,14 @@ export class MainThreadEditorTabs implements MainThreadEditorTabsShape {
}
}
+ if (editor instanceof InteractiveEditorInput) {
+ return {
+ kind: TabInputKind.InteractiveEditorInput,
+ uri: editor.resource,
+ inputBoxUri: editor.inputResource
+ };
+ }
+
return { kind: TabInputKind.UnknownInput };
}
diff --git a/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts b/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts
index 54a2887e33b..c7c01c717bf 100644
--- a/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts
+++ b/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts
@@ -185,7 +185,10 @@ registerAction2(class ResetMemento extends Action2 {
constructor() {
super({
id: 'files.participants.resetChoice',
- title: localize('label', "Reset choice for 'File operation needs preview'"),
+ title: {
+ value: localize('label', "Reset choice for 'File operation needs preview'"),
+ original: `Reset choice for 'File operation needs preview'`
+ },
f1: true
});
}
diff --git a/src/vs/workbench/api/browser/mainThreadTask.ts b/src/vs/workbench/api/browser/mainThreadTask.ts
index 69e679cc5b4..be081d8e61f 100644
--- a/src/vs/workbench/api/browser/mainThreadTask.ts
+++ b/src/vs/workbench/api/browser/mainThreadTask.ts
@@ -9,7 +9,7 @@ import { URI, UriComponents } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
import * as Types from 'vs/base/common/types';
import * as Platform from 'vs/base/common/platform';
-import { IStringDictionary, forEach } from 'vs/base/common/collections';
+import { IStringDictionary } from 'vs/base/common/collections';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IWorkspace, IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
@@ -342,7 +342,7 @@ namespace TaskDTO {
return result;
}
- export function to(task: ITaskDTO | undefined, workspace: IWorkspaceContextService, executeOnly: boolean, icon?: { id?: string; color?: string }): ContributedTask | undefined {
+ export function to(task: ITaskDTO | undefined, workspace: IWorkspaceContextService, executeOnly: boolean, icon?: { id?: string; color?: string }, hide?: boolean): ContributedTask | undefined {
if (!task || (typeof task.name !== 'string')) {
return undefined;
}
@@ -383,7 +383,8 @@ namespace TaskDTO {
isBackground: !!task.isBackground,
problemMatchers: task.problemMatchers.slice(),
detail: task.detail,
- icon
+ icon,
+ hide
}
);
return result;
@@ -433,7 +434,9 @@ export class MainThreadTask implements MainThreadTaskShape {
let resolvedDefinition: ITaskDefinitionDTO = execution.task!.definition;
if (execution.task?.execution && CustomExecutionDTO.is(execution.task.execution) && event.resolvedVariables) {
const dictionary: IStringDictionary<string> = {};
- Array.from(event.resolvedVariables.entries()).forEach(entry => dictionary[entry[0]] = entry[1]);
+ for (const [key, value] of event.resolvedVariables.entries()) {
+ dictionary[key] = value;
+ }
resolvedDefinition = await this._configurationResolverService.resolveAnyAsync(task.getWorkspaceFolder(),
execution.task.definition, dictionary);
}
@@ -449,9 +452,9 @@ export class MainThreadTask implements MainThreadTaskShape {
}
public dispose(): void {
- this._providers.forEach((value) => {
+ for (const value of this._providers.values()) {
value.disposable.dispose();
- });
+ }
this._providers.clear();
}
@@ -492,7 +495,7 @@ export class MainThreadTask implements MainThreadTaskShape {
dto.name = ((dto.name === undefined) ? '' : dto.name); // Using an empty name causes the name to default to the one given by the provider.
return Promise.resolve(this._proxy.$resolveTask(handle, dto)).then(resolvedTask => {
if (resolvedTask) {
- return TaskDTO.to(resolvedTask, this._workspaceContextServer, true, task.configurationProperties.icon);
+ return TaskDTO.to(resolvedTask, this._workspaceContextServer, true, task.configurationProperties.icon, task.configurationProperties.hide);
}
return undefined;
@@ -679,10 +682,7 @@ export class MainThreadTask implements MainThreadTaskShape {
const vars: string[] = [];
toResolve.variables.forEach(item => vars.push(item));
return Promise.resolve(this._proxy.$resolveVariables(workspaceFolder.uri, { process: toResolve.process, variables: vars })).then(values => {
- const partiallyResolvedVars = new Array<string>();
- forEach(values.variables, (entry) => {
- partiallyResolvedVars.push(entry.value);
- });
+ const partiallyResolvedVars = Array.from(Object.values(values.variables));
return new Promise<IResolvedVariables | undefined>((resolve, reject) => {
this._configurationResolverService.resolveWithInteraction(workspaceFolder, partiallyResolvedVars, 'tasks', undefined, target).then(resolvedVars => {
if (!resolvedVars) {
diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts
index 62b439b64ab..23802f649dc 100644
--- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts
+++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts
@@ -10,7 +10,7 @@ import { URI } from 'vs/base/common/uri';
import { StopWatch } from 'vs/base/common/stopwatch';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
-import { IProcessProperty, IShellLaunchConfig, IShellLaunchConfigDto, ProcessPropertyType, TerminalLocation, TitleEventSource } from 'vs/platform/terminal/common/terminal';
+import { IProcessProperty, IShellLaunchConfig, IShellLaunchConfigDto, ProcessPropertyType, TerminalExitReason, TerminalLocation, TitleEventSource } from 'vs/platform/terminal/common/terminal';
import { TerminalDataBufferer } from 'vs/platform/terminal/common/terminalDataBuffering';
import { ITerminalEditorService, ITerminalExternalLinkProvider, ITerminalGroupService, ITerminalInstance, ITerminalInstanceService, ITerminalLink, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { TerminalProcessExtHostProxy } from 'vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy';
@@ -183,7 +183,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
}
public async $dispose(id: ExtHostTerminalIdentifier): Promise<void> {
- (await this._getTerminalInstance(id))?.dispose();
+ (await this._getTerminalInstance(id))?.dispose(TerminalExitReason.Extension);
}
public async $sendText(id: ExtHostTerminalIdentifier, text: string, addNewLine: boolean): Promise<void> {
@@ -253,7 +253,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
}
private _onTerminalDisposed(terminalInstance: ITerminalInstance): void {
- this._proxy.$acceptTerminalClosed(terminalInstance.instanceId, terminalInstance.exitCode);
+ this._proxy.$acceptTerminalClosed(terminalInstance.instanceId, terminalInstance.exitCode, terminalInstance.exitReason ?? TerminalExitReason.Unknown);
}
private _onTerminalOpened(terminalInstance: ITerminalInstance): void {
diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts
index 1932e2ce93d..b94fab104e1 100644
--- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts
+++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts
@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import { coalesce } from 'vs/base/common/arrays';
-import { forEach } from 'vs/base/common/collections';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import * as resources from 'vs/base/common/resources';
import { isFalsyOrWhitespace } from 'vs/base/common/strings';
@@ -322,16 +321,16 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
let activityBarOrder = CUSTOM_VIEWS_START_ORDER + viewContainersRegistry.all.filter(v => !!v.extensionId && viewContainersRegistry.getViewContainerLocation(v) === ViewContainerLocation.Sidebar).length;
let panelOrder = 5 + viewContainersRegistry.all.filter(v => !!v.extensionId && viewContainersRegistry.getViewContainerLocation(v) === ViewContainerLocation.Panel).length + 1;
for (const { value, collector, description } of extensionPoints) {
- forEach(value, entry => {
- if (!this.isValidViewsContainer(entry.value, collector)) {
+ Object.entries(value).forEach(([key, value]) => {
+ if (!this.isValidViewsContainer(value, collector)) {
return;
}
- switch (entry.key) {
+ switch (key) {
case 'activitybar':
- activityBarOrder = this.registerCustomViewContainers(entry.value, description, activityBarOrder, existingViewContainers, ViewContainerLocation.Sidebar);
+ activityBarOrder = this.registerCustomViewContainers(value, description, activityBarOrder, existingViewContainers, ViewContainerLocation.Sidebar);
break;
case 'panel':
- panelOrder = this.registerCustomViewContainers(entry.value, description, panelOrder, existingViewContainers, ViewContainerLocation.Panel);
+ panelOrder = this.registerCustomViewContainers(value, description, panelOrder, existingViewContainers, ViewContainerLocation.Panel);
break;
}
});
@@ -455,22 +454,22 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
for (const extension of extensions) {
const { value, collector } = extension;
- forEach(value, entry => {
- if (!this.isValidViewDescriptors(entry.value, collector)) {
+ Object.entries(value).forEach(([key, value]) => {
+ if (!this.isValidViewDescriptors(value, collector)) {
return;
}
- if (entry.key === 'remote' && !isProposedApiEnabled(extension.description, 'contribViewsRemote')) {
- collector.warn(localize('ViewContainerRequiresProposedAPI', "View container '{0}' requires 'enabledApiProposals: [\"contribViewsRemote\"]' to be added to 'Remote'.", entry.key));
+ if (key === 'remote' && !isProposedApiEnabled(extension.description, 'contribViewsRemote')) {
+ collector.warn(localize('ViewContainerRequiresProposedAPI', "View container '{0}' requires 'enabledApiProposals: [\"contribViewsRemote\"]' to be added to 'Remote'.", key));
return;
}
- const viewContainer = this.getViewContainer(entry.key);
+ const viewContainer = this.getViewContainer(key);
if (!viewContainer) {
- collector.warn(localize('ViewContainerDoesnotExist', "View container '{0}' does not exist and all views registered to it will be added to 'Explorer'.", entry.key));
+ collector.warn(localize('ViewContainerDoesnotExist', "View container '{0}' does not exist and all views registered to it will be added to 'Explorer'.", key));
}
const container = viewContainer || this.getDefaultViewContainer();
- const viewDescriptors = coalesce(entry.value.map((item, index) => {
+ const viewDescriptors = coalesce(value.map((item, index) => {
// validate
if (viewIds.has(item.id)) {
collector.error(localize('duplicateView1', "Cannot register multiple views with same id `{0}`", item.id));
@@ -514,7 +513,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
collapsed: this.showCollapsed(container) || initialVisibility === InitialVisibility.Collapsed,
order: order,
extensionId: extension.description.identifier,
- originalContainerId: entry.key,
+ originalContainerId: key,
group: item.group,
remoteAuthority: item.remoteName || (<any>item).remoteAuthority, // TODO@roblou - delete after remote extensions are updated
hideByDefault: initialVisibility === InitialVisibility.Hidden,
diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts
index cd38535f5f6..82f4a40688d 100644
--- a/src/vs/workbench/api/common/extHost.api.impl.ts
+++ b/src/vs/workbench/api/common/extHost.api.impl.ts
@@ -576,7 +576,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
createLanguageStatusItem(id: string, selector: vscode.DocumentSelector): vscode.LanguageStatusItem {
return extHostLanguages.createLanguageStatusItem(extension, id, selector);
},
- registerDocumentOnDropEditProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentOnDropEditProvider): vscode.Disposable {
+ registerDocumentDropEditProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentDropEditProvider): vscode.Disposable {
checkProposedApiEnabled(extension, 'textEditorDrop');
return extHostLanguageFeatures.registerDocumentOnDropEditProvider(extension, selector, provider);
}
@@ -1347,7 +1347,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
TabInputNotebook: extHostTypes.NotebookEditorTabInput,
TabInputNotebookDiff: extHostTypes.NotebookDiffEditorTabInput,
TabInputWebview: extHostTypes.WebviewEditorTabInput,
- TabInputTerminal: extHostTypes.TerminalEditorTabInput
+ TabInputTerminal: extHostTypes.TerminalEditorTabInput,
+ TabInputInteractiveWindow: extHostTypes.InteractiveWindowInput,
+ TerminalExitReason: extHostTypes.TerminalExitReason
};
};
}
diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts
index a7b203695df..8df19f84b76 100644
--- a/src/vs/workbench/api/common/extHost.protocol.ts
+++ b/src/vs/workbench/api/common/extHost.protocol.ts
@@ -40,7 +40,7 @@ import * as quickInput from 'vs/platform/quickinput/common/quickInput';
import { IRemoteConnectionData, TunnelDescription } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { ClassifiedEvent, GDPRClassification, StrictPropertyCheck } from 'vs/platform/telemetry/common/gdprTypings';
import { TelemetryLevel } from 'vs/platform/telemetry/common/telemetry';
-import { ICreateContributedTerminalProfileOptions, IProcessProperty, IShellLaunchConfigDto, ITerminalEnvironment, ITerminalLaunchError, ITerminalProfile, TerminalLocation } from 'vs/platform/terminal/common/terminal';
+import { ICreateContributedTerminalProfileOptions, IProcessProperty, IShellLaunchConfigDto, ITerminalEnvironment, ITerminalLaunchError, ITerminalProfile, TerminalExitReason, TerminalLocation } from 'vs/platform/terminal/common/terminal';
import { ThemeColor, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { ProvidedPortAttributes, TunnelCreationOptions, TunnelOptions, TunnelPrivacyId, TunnelProviderFeatures } from 'vs/platform/tunnel/common/tunnel';
import { WorkspaceTrustRequestOptions } from 'vs/platform/workspace/common/workspaceTrust';
@@ -624,7 +624,8 @@ export const enum TabInputKind {
NotebookDiffInput,
CustomEditorInput,
WebviewEditorInput,
- TerminalEditorInput
+ TerminalEditorInput,
+ InteractiveEditorInput,
}
export const enum TabModelOperationKind {
@@ -673,11 +674,17 @@ export interface WebviewInputDto {
viewType: string;
}
+export interface InteractiveEditorInputDto {
+ kind: TabInputKind.InteractiveEditorInput;
+ uri: UriComponents;
+ inputBoxUri: UriComponents;
+}
+
export interface TabInputDto {
kind: TabInputKind.TerminalEditorInput;
}
-export type AnyInputDto = UnknownInputDto | TextInputDto | TextDiffInputDto | NotebookInputDto | NotebookDiffInputDto | CustomInputDto | WebviewInputDto | TabInputDto;
+export type AnyInputDto = UnknownInputDto | TextInputDto | TextDiffInputDto | NotebookInputDto | NotebookDiffInputDto | CustomInputDto | WebviewInputDto | InteractiveEditorInputDto | TabInputDto;
export interface MainThreadEditorTabsShape extends IDisposable {
// manage tabs: move, close, rearrange etc
@@ -1587,27 +1594,6 @@ export interface IWorkspaceEditEntryMetadataDto {
iconPath?: { id: string } | UriComponents | { light: UriComponents; dark: UriComponents };
}
-export const enum WorkspaceEditType {
- File = 1,
- Text = 2,
- Cell = 3,
-}
-
-export interface IWorkspaceFileEditDto {
- _type: WorkspaceEditType.File;
- oldUri?: UriComponents;
- newUri?: UriComponents;
- options?: languages.WorkspaceFileEditOptions;
- metadata?: IWorkspaceEditEntryMetadataDto;
-}
-
-export interface IWorkspaceTextEditDto {
- _type: WorkspaceEditType.Text;
- resource: UriComponents;
- edit: languages.TextEdit & { insertAsSnippet?: boolean };
- modelVersionId?: number;
- metadata?: IWorkspaceEditEntryMetadataDto;
-}
export type ICellEditOperationDto =
notebookCommon.ICellPartialMetadataEdit
@@ -1619,31 +1605,19 @@ export type ICellEditOperationDto =
cells: NotebookCellDataDto[];
};
-export interface IWorkspaceCellEditDto {
- _type: WorkspaceEditType.Cell;
- resource: UriComponents;
- notebookVersionId?: number;
- metadata?: IWorkspaceEditEntryMetadataDto;
- edit: ICellEditOperationDto;
-}
+export type IWorkspaceCellEditDto = Dto<Omit<notebookCommon.IWorkspaceNotebookCellEdit, 'cellEdit'>> & { cellEdit: ICellEditOperationDto };
+
+export type IWorkspaceFileEditDto = Dto<languages.IWorkspaceFileEdit>;
+
+export type IWorkspaceTextEditDto = Dto<languages.IWorkspaceTextEdit>;
export interface IWorkspaceEditDto {
edits: Array<IWorkspaceFileEditDto | IWorkspaceTextEditDto | IWorkspaceCellEditDto>;
}
-export function reviveWorkspaceEditDto(data: IWorkspaceEditDto | undefined): languages.WorkspaceEdit {
+export function reviveWorkspaceEditDto(data: IWorkspaceEditDto | undefined): languages.WorkspaceEdit | undefined {
if (data && data.edits) {
- for (const edit of data.edits) {
- if (typeof (<IWorkspaceTextEditDto>edit).resource === 'object') {
- (<IWorkspaceTextEditDto>edit).resource = URI.revive((<IWorkspaceTextEditDto>edit).resource);
- } else {
- (<IWorkspaceFileEditDto>edit).newUri = URI.revive((<IWorkspaceFileEditDto>edit).newUri);
- (<IWorkspaceFileEditDto>edit).oldUri = URI.revive((<IWorkspaceFileEditDto>edit).oldUri);
- }
- if (edit.metadata && edit.metadata.iconPath) {
- edit.metadata = revive(edit.metadata);
- }
- }
+ revive<languages.WorkspaceEdit>(data);
}
return <languages.WorkspaceEdit>data;
}
@@ -1818,7 +1792,7 @@ export interface ITerminalDimensionsDto {
}
export interface ExtHostTerminalServiceShape {
- $acceptTerminalClosed(id: number, exitCode: number | undefined): void;
+ $acceptTerminalClosed(id: number, exitCode: number | undefined, exitReason: TerminalExitReason): void;
$acceptTerminalOpened(id: number, extHostTerminalId: string | undefined, name: string, shellLaunchConfig: IShellLaunchConfigDto): void;
$acceptActiveTerminalChanged(id: number | null): void;
$acceptTerminalProcessId(id: number, processId: number): void;
diff --git a/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts b/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts
index 05f7cb963d6..7a90e5db0f9 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, IWorkspaceEditDto, WorkspaceEditType, MainThreadBulkEditsShape } from 'vs/workbench/api/common/extHost.protocol';
+import { ExtHostDocumentSaveParticipantShape, IWorkspaceEditDto, 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';
@@ -146,12 +146,12 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic
if (Array.isArray(value) && (<vscode.TextEdit[]>value).every(e => e instanceof TextEdit)) {
for (const { newText, newEol, range } of value) {
dto.edits.push({
- _type: WorkspaceEditType.Text,
resource: document.uri,
- edit: {
+ versionId: undefined,
+ textEdit: {
range: range && Range.from(range),
text: newText,
- eol: newEol && EndOfLine.from(newEol)
+ eol: newEol && EndOfLine.from(newEol),
}
});
}
diff --git a/src/vs/workbench/api/common/extHostEditorTabs.ts b/src/vs/workbench/api/common/extHostEditorTabs.ts
index 21a42adb1e4..1df76fd88c7 100644
--- a/src/vs/workbench/api/common/extHostEditorTabs.ts
+++ b/src/vs/workbench/api/common/extHostEditorTabs.ts
@@ -9,7 +9,7 @@ import { IEditorTabDto, IEditorTabGroupDto, IExtHostEditorTabsShape, MainContext
import { URI } from 'vs/base/common/uri';
import { Emitter } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
-import { CustomEditorTabInput, NotebookDiffEditorTabInput, NotebookEditorTabInput, TerminalEditorTabInput, TextDiffTabInput, TextTabInput, WebviewEditorTabInput } from 'vs/workbench/api/common/extHostTypes';
+import { CustomEditorTabInput, InteractiveWindowInput, NotebookDiffEditorTabInput, NotebookEditorTabInput, TerminalEditorTabInput, TextDiffTabInput, TextTabInput, WebviewEditorTabInput } from 'vs/workbench/api/common/extHostTypes';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { assertIsDefined } from 'vs/base/common/types';
import { diffSets } from 'vs/base/common/collections';
@@ -21,7 +21,7 @@ export interface IExtHostEditorTabs extends IExtHostEditorTabsShape {
export const IExtHostEditorTabs = createDecorator<IExtHostEditorTabs>('IExtHostEditorTabs');
-type AnyTabInput = TextTabInput | TextDiffTabInput | CustomEditorTabInput | NotebookEditorTabInput | NotebookDiffEditorTabInput | WebviewEditorTabInput | TerminalEditorTabInput;
+type AnyTabInput = TextTabInput | TextDiffTabInput | CustomEditorTabInput | NotebookEditorTabInput | NotebookDiffEditorTabInput | WebviewEditorTabInput | TerminalEditorTabInput | InteractiveWindowInput;
class ExtHostEditorTab {
private _apiObject: vscode.Tab | undefined;
@@ -94,6 +94,8 @@ class ExtHostEditorTab {
return new NotebookDiffEditorTabInput(URI.revive(this._dto.input.original), URI.revive(this._dto.input.modified), this._dto.input.notebookType);
case TabInputKind.TerminalEditorInput:
return new TerminalEditorTabInput();
+ case TabInputKind.InteractiveEditorInput:
+ return new InteractiveWindowInput(URI.revive(this._dto.input.uri), URI.revive(this._dto.input.inputBoxUri));
default:
return undefined;
}
diff --git a/src/vs/workbench/api/common/extHostFileSystemEventService.ts b/src/vs/workbench/api/common/extHostFileSystemEventService.ts
index d4f86a58255..79aa5cd22f4 100644
--- a/src/vs/workbench/api/common/extHostFileSystemEventService.ts
+++ b/src/vs/workbench/api/common/extHostFileSystemEventService.ts
@@ -16,6 +16,7 @@ import { FileOperation } from 'vs/platform/files/common/files';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ILogService } from 'vs/platform/log/common/log';
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
+import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
class FileSystemWatcher implements vscode.FileSystemWatcher {
@@ -223,14 +224,14 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ
private async _fireWillEvent<E extends IWaitUntil>(emitter: AsyncEmitter<E>, data: IWaitUntilData<E>, timeout: number, token: CancellationToken): Promise<IWillRunFileOperationParticipation | undefined> {
const extensionNames = new Set<string>();
- const edits: WorkspaceEdit[] = [];
+ const edits: [IExtensionDescription, WorkspaceEdit][] = [];
- await emitter.fireAsync(data, token, async (thenable, listener) => {
+ await emitter.fireAsync(data, token, async (thenable: Promise<unknown>, listener) => {
// ignore all results except for WorkspaceEdits. Those are stored in an array.
const now = Date.now();
const result = await Promise.resolve(thenable);
if (result instanceof WorkspaceEdit) {
- edits.push(result);
+ edits.push([(<IExtensionListener<E>>listener).extension, result]);
extensionNames.add((<IExtensionListener<E>>listener).extension.displayName ?? (<IExtensionListener<E>>listener).extension.identifier.value);
}
@@ -249,11 +250,11 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ
// concat all WorkspaceEdits collected via waitUntil-call and send them over to the renderer
const dto: IWorkspaceEditDto = { edits: [] };
- for (const edit of edits) {
+ for (const [extension, edit] of edits) {
const { edits } = typeConverter.WorkspaceEdit.from(edit, {
getTextDocumentVersion: uri => this._extHostDocumentsAndEditors.getDocument(uri)?.version,
getNotebookDocumentVersion: () => undefined,
- });
+ }, isProposedApiEnabled(extension, 'snippetWorkspaceEdit'));
dto.edits = dto.edits.concat(edits);
}
return { edit: dto, extensionNames: Array.from(extensionNames) };
diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts
index afdb6fbb015..809b7609426 100644
--- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts
+++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts
@@ -446,7 +446,7 @@ class CodeActionAdapter {
title: candidate.title,
command: candidate.command && this._commands.toInternal(candidate.command, disposables),
diagnostics: candidate.diagnostics && candidate.diagnostics.map(typeConvert.Diagnostic.from),
- edit: candidate.edit && typeConvert.WorkspaceEdit.from(candidate.edit),
+ edit: candidate.edit && typeConvert.WorkspaceEdit.from(candidate.edit, undefined, isProposedApiEnabled(this._extension, 'snippetWorkspaceEdit')),
kind: candidate.kind && candidate.kind.value,
isPreferred: candidate.isPreferred,
disabled: candidate.disabled?.reason
@@ -467,7 +467,7 @@ class CodeActionAdapter {
}
const resolvedItem = (await this._provider.resolveCodeAction(item, token)) ?? item;
return resolvedItem?.edit
- ? typeConvert.WorkspaceEdit.from(resolvedItem.edit)
+ ? typeConvert.WorkspaceEdit.from(resolvedItem.edit, undefined, isProposedApiEnabled(this._extension, 'snippetWorkspaceEdit'))
: undefined;
}
@@ -522,7 +522,7 @@ class DocumentPasteEditProvider {
return {
insertText: typeof edit.insertText === 'string' ? edit.insertText : { snippet: edit.insertText.value },
- additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit) : undefined,
+ additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit, undefined, true) : undefined,
};
}
}
@@ -1791,7 +1791,7 @@ class DocumentOnDropEditAdapter {
constructor(
private readonly _proxy: extHostProtocol.MainThreadLanguageFeaturesShape,
private readonly _documents: ExtHostDocuments,
- private readonly _provider: vscode.DocumentOnDropEditProvider,
+ private readonly _provider: vscode.DocumentDropEditProvider,
private readonly _handle: number,
) { }
@@ -1802,13 +1802,13 @@ class DocumentOnDropEditAdapter {
return (await this._proxy.$resolveDocumentOnDropFileData(this._handle, requestId, index)).buffer;
});
- const edit = await this._provider.provideDocumentOnDropEdits(doc, pos, dataTransfer, token);
+ const edit = await this._provider.provideDocumentDropEdits(doc, pos, dataTransfer, token);
if (!edit) {
return undefined;
}
return {
insertText: typeof edit.insertText === 'string' ? edit.insertText : { snippet: edit.insertText.value },
- additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit) : undefined,
+ additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit, undefined, true) : undefined,
};
}
}
@@ -2446,7 +2446,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
// --- Document on drop
- registerDocumentOnDropEditProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentOnDropEditProvider) {
+ registerDocumentOnDropEditProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentDropEditProvider) {
const handle = this._nextHandle();
this._adapter.set(handle, new AdapterData(new DocumentOnDropEditAdapter(this._proxy, this._documents, provider, handle), extension));
this._proxy.$registerDocumentOnDropEditProvider(handle, this._transformDocumentSelector(selector));
diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts
index f27f16dca52..0f5ebe088fb 100644
--- a/src/vs/workbench/api/common/extHostTerminalService.ts
+++ b/src/vs/workbench/api/common/extHostTerminalService.ts
@@ -10,7 +10,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
import { URI } from 'vs/base/common/uri';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { IDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle';
-import { Disposable as VSCodeDisposable, EnvironmentVariableMutatorType } from './extHostTypes';
+import { Disposable as VSCodeDisposable, EnvironmentVariableMutatorType, TerminalExitReason } from './extHostTypes';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { localize } from 'vs/nls';
import { NotSupportedError } from 'vs/base/common/errors';
@@ -203,8 +203,8 @@ export class ExtHostTerminal {
this._name = name;
}
- public setExitCode(code: number | undefined) {
- this._exitStatus = Object.freeze({ code });
+ public setExitStatus(code: number | undefined, reason: TerminalExitReason) {
+ this._exitStatus = Object.freeze({ code, reason });
}
public setDimensions(cols: number, rows: number): boolean {
@@ -500,11 +500,11 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I
}
}
- public async $acceptTerminalClosed(id: number, exitCode: number | undefined): Promise<void> {
+ public async $acceptTerminalClosed(id: number, exitCode: number | undefined, exitReason: TerminalExitReason): Promise<void> {
const index = this._getTerminalObjectIndexById(this._terminals, id);
if (index !== null) {
const terminal = this._terminals.splice(index, 1)[0];
- terminal.setExitCode(exitCode);
+ terminal.setExitStatus(exitCode, exitReason);
this._onDidCloseTerminal.fire(terminal.value);
}
}
diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts
index fdbb535e422..7eea30b6513 100644
--- a/src/vs/workbench/api/common/extHostTreeViews.ts
+++ b/src/vs/workbench/api/common/extHostTreeViews.ts
@@ -14,7 +14,7 @@ import { DataTransferDTO, ExtHostTreeViewsShape, MainThreadTreeViewsShape } from
import { ITreeItem, TreeViewItemHandleArg, ITreeItemLabel, IRevealOptions } from 'vs/workbench/common/views';
import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/common/extHostCommands';
import { asPromise } from 'vs/base/common/async';
-import { TreeItemCollapsibleState, ThemeIcon, MarkdownString as MarkdownStringType } from 'vs/workbench/api/common/extHostTypes';
+import { TreeItemCollapsibleState, ThemeIcon, MarkdownString as MarkdownStringType, TreeItem } from 'vs/workbench/api/common/extHostTypes';
import { isUndefinedOrNull, isString } from 'vs/base/common/types';
import { equals, coalesce } from 'vs/base/common/arrays';
import { ILogService } from 'vs/platform/log/common/log';
@@ -500,6 +500,7 @@ class ExtHostTreeView<T> extends Disposable {
const node = this.nodes.get(element);
if (node) {
const resolve = await this.dataProvider.resolveTreeItem(node.extensionItem, element, token) ?? node.extensionItem;
+ this.validateTreeItem(resolve);
// Resolvable elements. Currently only tooltip and command.
node.item.tooltip = this.getTooltip(resolve.tooltip);
node.item.command = this.getCommand(node.disposableStore, resolve.command);
@@ -699,7 +700,15 @@ class ExtHostTreeView<T> extends Disposable {
return command ? this.commands.toInternal(command, disposable) : undefined;
}
+ private validateTreeItem(extensionTreeItem: vscode.TreeItem) {
+ if (!TreeItem.isTreeItem(extensionTreeItem)) {
+ // TODO: #154757 we should consider throwing, but let's wait and see if there are tons of reports of this first.
+ console.log(`Extension ${this.extension.identifier.value} has provided an invalid tree item.`);
+ }
+ }
+
private createTreeNode(element: T, extensionTreeItem: vscode.TreeItem, parent: TreeNode | Root): TreeNode {
+ this.validateTreeItem(extensionTreeItem);
const disposableStore = new DisposableStore();
const handle = this.createHandle(element, extensionTreeItem, parent);
const icon = this.getLightIconPath(extensionTreeItem);
diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts
index 9b733dc8951..52b5d2c712e 100644
--- a/src/vs/workbench/api/common/extHostTypeConverters.ts
+++ b/src/vs/workbench/api/common/extHostTypeConverters.ts
@@ -589,47 +589,55 @@ export namespace WorkspaceEdit {
if (entry._type === types.FileEditType.File) {
// file operation
- result.edits.push(<extHostProtocol.IWorkspaceFileEditDto>{
- _type: extHostProtocol.WorkspaceEditType.File,
- oldUri: entry.from,
- newUri: entry.to,
+ result.edits.push(<languages.IWorkspaceFileEdit>{
+ oldResource: entry.from,
+ newResource: entry.to,
options: entry.options,
metadata: entry.metadata
});
} else if (entry._type === types.FileEditType.Text) {
-
// text edits
- const dto = <extHostProtocol.IWorkspaceTextEditDto>{
- _type: extHostProtocol.WorkspaceEditType.Text,
+ result.edits.push(<languages.IWorkspaceTextEdit>{
resource: entry.uri,
- edit: TextEdit.from(entry.edit),
- modelVersionId: !toCreate.has(entry.uri) ? versionInfo?.getTextDocumentVersion(entry.uri) : undefined,
+ textEdit: TextEdit.from(entry.edit),
+ versionId: !toCreate.has(entry.uri) ? versionInfo?.getTextDocumentVersion(entry.uri) : undefined,
metadata: entry.metadata
- };
- if (allowSnippetTextEdit && entry.edit.newText2 instanceof types.SnippetString) {
- dto.edit.insertAsSnippet = true;
- dto.edit.text = entry.edit.newText2.value;
+ });
+ } else if (entry._type === types.FileEditType.Snippet) {
+ // snippet text edits
+ if (!allowSnippetTextEdit) {
+ console.warn(`DROPPING snippet text edit because proposal IS NOT ENABLED`, entry);
+ continue;
}
- result.edits.push(dto);
+ result.edits.push(<languages.IWorkspaceTextEdit>{
+ resource: entry.uri,
+ textEdit: {
+ range: Range.from(entry.range),
+ text: entry.edit.value,
+ insertAsSnippet: true
+ },
+ versionId: !toCreate.has(entry.uri) ? versionInfo?.getTextDocumentVersion(entry.uri) : undefined,
+ metadata: entry.metadata
+ });
} else if (entry._type === types.FileEditType.Cell) {
- result.edits.push(<extHostProtocol.IWorkspaceCellEditDto>{
- _type: extHostProtocol.WorkspaceEditType.Cell,
+ // cell edit
+ result.edits.push(<notebooks.IWorkspaceNotebookCellEdit>{
metadata: entry.metadata,
resource: entry.uri,
- edit: entry.edit,
+ cellEdit: entry.edit,
notebookMetadata: entry.notebookMetadata,
notebookVersionId: versionInfo?.getNotebookDocumentVersion(entry.uri)
});
} else if (entry._type === types.FileEditType.CellReplace) {
- result.edits.push({
- _type: extHostProtocol.WorkspaceEditType.Cell,
+ // cell replace
+ result.edits.push(<extHostProtocol.IWorkspaceCellEditDto>{
metadata: entry.metadata,
resource: entry.uri,
notebookVersionId: versionInfo?.getNotebookDocumentVersion(entry.uri),
- edit: {
+ cellEdit: {
editType: notebooks.CellEditType.Replace,
index: entry.index,
count: entry.count,
@@ -645,16 +653,16 @@ export namespace WorkspaceEdit {
export function to(value: extHostProtocol.IWorkspaceEditDto) {
const result = new types.WorkspaceEdit();
for (const edit of value.edits) {
- if ((<extHostProtocol.IWorkspaceTextEditDto>edit).edit) {
+ if ((<extHostProtocol.IWorkspaceTextEditDto>edit).textEdit) {
result.replace(
URI.revive((<extHostProtocol.IWorkspaceTextEditDto>edit).resource),
- Range.to((<extHostProtocol.IWorkspaceTextEditDto>edit).edit.range),
- (<extHostProtocol.IWorkspaceTextEditDto>edit).edit.text
+ Range.to((<extHostProtocol.IWorkspaceTextEditDto>edit).textEdit.range),
+ (<extHostProtocol.IWorkspaceTextEditDto>edit).textEdit.text
);
} else {
result.renameFile(
- URI.revive((<extHostProtocol.IWorkspaceFileEditDto>edit).oldUri!),
- URI.revive((<extHostProtocol.IWorkspaceFileEditDto>edit).newUri!),
+ URI.revive((<extHostProtocol.IWorkspaceFileEditDto>edit).oldResource!),
+ URI.revive((<extHostProtocol.IWorkspaceFileEditDto>edit).newResource!),
(<extHostProtocol.IWorkspaceFileEditDto>edit).options
);
}
diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts
index ce30f68d26e..e50faecd595 100644
--- a/src/vs/workbench/api/common/extHostTypes.ts
+++ b/src/vs/workbench/api/common/extHostTypes.ts
@@ -10,7 +10,7 @@ import { MarkdownString as BaseMarkdownString } from 'vs/base/common/htmlContent
import { ResourceMap } from 'vs/base/common/map';
import { Mimes, normalizeMimeType } from 'vs/base/common/mime';
import { nextCharLength } from 'vs/base/common/strings';
-import { isArray, isStringArray } from 'vs/base/common/types';
+import { isArray, isString, isStringArray } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
import { FileSystemProviderErrorCode, markAsFileSystemProviderError } from 'vs/platform/files/common/files';
@@ -19,16 +19,6 @@ import { IRelativePatternDto } from 'vs/workbench/api/common/extHost.protocol';
import { CellEditType, ICellPartialMetadataEdit, IDocumentMetadataEdit } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import type * as vscode from 'vscode';
-function es5ClassCompat(target: Function): any {
- ///@ts-expect-error
- function _() { return Reflect.construct(target, arguments, this.constructor); }
- Object.defineProperty(_, 'name', Object.getOwnPropertyDescriptor(target, 'name')!);
- Object.setPrototypeOf(_, target);
- Object.setPrototypeOf(_.prototype, target.prototype);
- return _;
-}
-
-@es5ClassCompat
export class Disposable {
static from(...inDisposables: { dispose(): any }[]): Disposable {
@@ -59,7 +49,6 @@ export class Disposable {
}
}
-@es5ClassCompat
export class Position {
static Min(...positions: Position[]): Position {
@@ -240,7 +229,6 @@ export class Position {
}
}
-@es5ClassCompat
export class Range {
static isRange(thing: any): thing is vscode.Range {
@@ -386,7 +374,6 @@ export class Range {
}
}
-@es5ClassCompat
export class Selection extends Range {
static isSelection(thing: any): thing is Selection {
@@ -515,7 +502,6 @@ export enum EnvironmentVariableMutatorType {
Prepend = 3
}
-@es5ClassCompat
export class TextEdit {
static isTextEdit(thing: any): thing is TextEdit {
@@ -549,7 +535,6 @@ export class TextEdit {
protected _range: Range;
protected _newText: string | null;
- newText2?: string | SnippetString;
protected _newEol?: EndOfLine;
get range(): Range {
@@ -599,7 +584,6 @@ export class TextEdit {
}
}
-@es5ClassCompat
export class NotebookEdit implements vscode.NotebookEdit {
static isNotebookCellEdit(thing: any): thing is NotebookEdit {
@@ -660,6 +644,7 @@ export const enum FileEditType {
Text = 2,
Cell = 3,
CellReplace = 5,
+ Snippet = 6,
}
export interface IFileOperation {
@@ -677,6 +662,14 @@ export interface IFileTextEdit {
metadata?: vscode.WorkspaceEditEntryMetadata;
}
+export interface IFileSnippetTextEdit {
+ _type: FileEditType.Snippet;
+ uri: URI;
+ range: vscode.Range;
+ edit: vscode.SnippetString;
+ metadata?: vscode.WorkspaceEditEntryMetadata;
+}
+
export interface IFileCellEdit {
_type: FileEditType.Cell;
uri: URI;
@@ -695,9 +688,8 @@ export interface ICellEdit {
}
-type WorkspaceEditEntry = IFileOperation | IFileTextEdit | IFileCellEdit | ICellEdit;
+type WorkspaceEditEntry = IFileOperation | IFileTextEdit | IFileSnippetTextEdit | IFileCellEdit | ICellEdit;
-@es5ClassCompat
export class WorkspaceEdit implements vscode.WorkspaceEdit {
private readonly _edits: WorkspaceEditEntry[] = [];
@@ -762,8 +754,12 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit {
// --- text
- replace(uri: URI, range: Range, newText: string, metadata?: vscode.WorkspaceEditEntryMetadata): void {
- this._edits.push({ _type: FileEditType.Text, uri, edit: new TextEdit(range, newText), metadata });
+ replace(uri: URI, range: Range, newText: string | vscode.SnippetString, metadata?: vscode.WorkspaceEditEntryMetadata): void {
+ if (typeof newText === 'string') {
+ this._edits.push({ _type: FileEditType.Text, uri, edit: new TextEdit(range, newText), metadata });
+ } else {
+ this._edits.push({ _type: FileEditType.Snippet, uri, range, edit: newText, metadata });
+ }
}
insert(resource: URI, position: Position, newText: string, metadata?: vscode.WorkspaceEditEntryMetadata): void {
@@ -844,7 +840,6 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit {
}
}
-@es5ClassCompat
export class SnippetString {
static isSnippetString(thing: any): thing is SnippetString {
@@ -951,7 +946,6 @@ export enum DiagnosticSeverity {
Error = 0
}
-@es5ClassCompat
export class Location {
static isLocation(thing: any): thing is vscode.Location {
@@ -990,7 +984,6 @@ export class Location {
}
}
-@es5ClassCompat
export class DiagnosticRelatedInformation {
static is(thing: any): thing is DiagnosticRelatedInformation {
@@ -1024,7 +1017,6 @@ export class DiagnosticRelatedInformation {
}
}
-@es5ClassCompat
export class Diagnostic {
range: Range;
@@ -1075,7 +1067,6 @@ export class Diagnostic {
}
}
-@es5ClassCompat
export class Hover {
public contents: (vscode.MarkdownString | vscode.MarkedString)[];
@@ -1103,7 +1094,6 @@ export enum DocumentHighlightKind {
Write = 2
}
-@es5ClassCompat
export class DocumentHighlight {
range: Range;
@@ -1155,7 +1145,6 @@ export enum SymbolTag {
Deprecated = 1,
}
-@es5ClassCompat
export class SymbolInformation {
static validate(candidate: SymbolInformation): void {
@@ -1200,7 +1189,6 @@ export class SymbolInformation {
}
}
-@es5ClassCompat
export class DocumentSymbol {
static validate(candidate: DocumentSymbol): void {
@@ -1239,7 +1227,6 @@ export enum CodeActionTriggerKind {
Automatic = 2,
}
-@es5ClassCompat
export class CodeAction {
title: string;
@@ -1260,7 +1247,6 @@ export class CodeAction {
}
-@es5ClassCompat
export class CodeActionKind {
private static readonly sep = '.';
@@ -1300,7 +1286,6 @@ CodeActionKind.Source = CodeActionKind.Empty.append('source');
CodeActionKind.SourceOrganizeImports = CodeActionKind.Source.append('organizeImports');
CodeActionKind.SourceFixAll = CodeActionKind.Source.append('fixAll');
-@es5ClassCompat
export class SelectionRange {
range: Range;
@@ -1367,7 +1352,6 @@ export enum LanguageStatusSeverity {
}
-@es5ClassCompat
export class CodeLens {
range: Range;
@@ -1384,7 +1368,6 @@ export class CodeLens {
}
}
-@es5ClassCompat
export class MarkdownString implements vscode.MarkdownString {
readonly #delegate: BaseMarkdownString;
@@ -1455,7 +1438,6 @@ export class MarkdownString implements vscode.MarkdownString {
}
}
-@es5ClassCompat
export class ParameterInformation {
label: string | [number, number];
@@ -1467,7 +1449,6 @@ export class ParameterInformation {
}
}
-@es5ClassCompat
export class SignatureInformation {
label: string;
@@ -1482,7 +1463,6 @@ export class SignatureInformation {
}
}
-@es5ClassCompat
export class SignatureHelp {
signatures: SignatureInformation[];
@@ -1506,7 +1486,6 @@ export enum InlayHintKind {
Parameter = 2,
}
-@es5ClassCompat
export class InlayHintLabelPart {
value: string;
@@ -1519,7 +1498,6 @@ export class InlayHintLabelPart {
}
}
-@es5ClassCompat
export class InlayHint implements vscode.InlayHint {
label: string | InlayHintLabelPart[];
@@ -1588,7 +1566,6 @@ export interface CompletionItemLabel {
description?: string;
}
-@es5ClassCompat
export class CompletionItem implements vscode.CompletionItem {
label: string | CompletionItemLabel;
@@ -1627,7 +1604,6 @@ export class CompletionItem implements vscode.CompletionItem {
}
}
-@es5ClassCompat
export class CompletionList {
isIncomplete?: boolean;
@@ -1639,7 +1615,6 @@ export class CompletionList {
}
}
-@es5ClassCompat
export class InlineSuggestion implements vscode.InlineCompletionItem {
filterText?: string;
@@ -1654,7 +1629,6 @@ export class InlineSuggestion implements vscode.InlineCompletionItem {
}
}
-@es5ClassCompat
export class InlineSuggestionList implements vscode.InlineCompletionList {
items: vscode.InlineCompletionItemNew[];
@@ -1665,7 +1639,6 @@ export class InlineSuggestionList implements vscode.InlineCompletionList {
}
}
-@es5ClassCompat
export class InlineSuggestionNew implements vscode.InlineCompletionItemNew {
insertText: string;
range?: Range;
@@ -1678,7 +1651,6 @@ export class InlineSuggestionNew implements vscode.InlineCompletionItemNew {
}
}
-@es5ClassCompat
export class InlineSuggestionsNew implements vscode.InlineCompletionListNew {
items: vscode.InlineCompletionItemNew[];
@@ -1772,7 +1744,6 @@ export namespace TextEditorSelectionChangeKind {
}
}
-@es5ClassCompat
export class DocumentLink {
range: Range;
@@ -1793,7 +1764,6 @@ export class DocumentLink {
}
}
-@es5ClassCompat
export class Color {
readonly red: number;
readonly green: number;
@@ -1810,7 +1780,6 @@ export class Color {
export type IColorFormat = string | { opaque: string; transparent: string };
-@es5ClassCompat
export class ColorInformation {
range: Range;
@@ -1828,7 +1797,6 @@ export class ColorInformation {
}
}
-@es5ClassCompat
export class ColorPresentation {
label: string;
textEdit?: TextEdit;
@@ -1854,6 +1822,14 @@ export enum SourceControlInputBoxValidationType {
Information = 2
}
+export enum TerminalExitReason {
+ Unknown = 0,
+ Shutdown = 1,
+ Process = 2,
+ User = 3,
+ Extension = 4
+}
+
export class TerminalLink implements vscode.TerminalLink {
constructor(
public startIndex: number,
@@ -1903,7 +1879,6 @@ export enum TaskPanelKind {
New = 3
}
-@es5ClassCompat
export class TaskGroup implements vscode.TaskGroup {
isDefault: boolean | undefined;
@@ -1955,7 +1930,6 @@ function computeTaskExecutionId(values: string[]): string {
return id;
}
-@es5ClassCompat
export class ProcessExecution implements vscode.ProcessExecution {
private _process: string;
@@ -2026,7 +2000,6 @@ export class ProcessExecution implements vscode.ProcessExecution {
}
}
-@es5ClassCompat
export class ShellExecution implements vscode.ShellExecution {
private _commandLine: string | undefined;
@@ -2141,7 +2114,6 @@ export class CustomExecution implements vscode.CustomExecution {
}
}
-@es5ClassCompat
export class Task implements vscode.Task {
private static ExtensionCallbackType: string = 'customExecution';
@@ -2398,16 +2370,64 @@ export enum ProgressLocation {
Notification = 15
}
-@es5ClassCompat
export class TreeItem {
label?: string | vscode.TreeItemLabel;
resourceUri?: URI;
- iconPath?: string | URI | { light: string | URI; dark: string | URI };
+ iconPath?: string | URI | { light: string | URI; dark: string | URI } | ThemeIcon;
command?: vscode.Command;
contextValue?: string;
tooltip?: string | vscode.MarkdownString;
+ static isTreeItem(thing: any): thing is TreeItem {
+ if (thing instanceof TreeItem) {
+ return true;
+ }
+ const treeItemThing = thing as vscode.TreeItem;
+ if (treeItemThing.label !== undefined && !isString(treeItemThing.label) && !(treeItemThing.label.label)) {
+ console.log('INVALID tree item, invalid label', treeItemThing.label);
+ return false;
+ }
+ if ((treeItemThing.id !== undefined) && !isString(treeItemThing.id)) {
+ console.log('INVALID tree item, invalid id', treeItemThing.id);
+ return false;
+ }
+ if ((treeItemThing.iconPath !== undefined) && !isString(treeItemThing.iconPath) && !URI.isUri(treeItemThing.iconPath) && !isString((treeItemThing.iconPath as vscode.ThemeIcon).id)) {
+ console.log('INVALID tree item, invalid iconPath', treeItemThing.iconPath);
+ return false;
+ }
+ if ((treeItemThing.description !== undefined) && !isString(treeItemThing.description) && (typeof treeItemThing.description !== 'boolean')) {
+ console.log('INVALID tree item, invalid description', treeItemThing.description);
+ return false;
+ }
+ if ((treeItemThing.resourceUri !== undefined) && !URI.isUri(treeItemThing.resourceUri)) {
+ console.log('INVALID tree item, invalid resourceUri', treeItemThing.resourceUri);
+ return false;
+ }
+ if ((treeItemThing.tooltip !== undefined) && !isString(treeItemThing.tooltip) && !(treeItemThing.tooltip instanceof MarkdownString)) {
+ console.log('INVALID tree item, invalid tooltip', treeItemThing.tooltip);
+ return false;
+ }
+ if ((treeItemThing.command !== undefined) && !treeItemThing.command.command) {
+ console.log('INVALID tree item, invalid command', treeItemThing.command);
+ return false;
+ }
+ if ((treeItemThing.collapsibleState !== undefined) && (treeItemThing.collapsibleState < TreeItemCollapsibleState.None) && (treeItemThing.collapsibleState > TreeItemCollapsibleState.Expanded)) {
+ console.log('INVALID tree item, invalid collapsibleState', treeItemThing.collapsibleState);
+ return false;
+ }
+ if ((treeItemThing.contextValue !== undefined) && !isString(treeItemThing.contextValue)) {
+ console.log('INVALID tree item, invalid contextValue', treeItemThing.contextValue);
+ return false;
+ }
+ if ((treeItemThing.accessibilityInformation !== undefined) && !treeItemThing.accessibilityInformation.label) {
+ console.log('INVALID tree item, invalid accessibilityInformation', treeItemThing.accessibilityInformation);
+ return false;
+ }
+
+ return true;
+ }
+
constructor(label: string | vscode.TreeItemLabel, collapsibleState?: vscode.TreeItemCollapsibleState);
constructor(resourceUri: URI, collapsibleState?: vscode.TreeItemCollapsibleState);
constructor(arg1: string | vscode.TreeItemLabel | URI, public collapsibleState: vscode.TreeItemCollapsibleState = TreeItemCollapsibleState.None) {
@@ -2426,7 +2446,6 @@ export enum TreeItemCollapsibleState {
Expanded = 2
}
-@es5ClassCompat
export class DataTransferItem {
async asString(): Promise<string> {
@@ -2440,7 +2459,6 @@ export class DataTransferItem {
constructor(public readonly value: any) { }
}
-@es5ClassCompat
export class DataTransfer implements vscode.DataTransfer {
#items = new Map<string, DataTransferItem[]>();
@@ -2482,7 +2500,6 @@ export class DataTransfer implements vscode.DataTransfer {
}
}
-@es5ClassCompat
export class DocumentDropEdit {
insertText: string | SnippetString;
@@ -2493,7 +2510,6 @@ export class DocumentDropEdit {
}
}
-@es5ClassCompat
export class DocumentPasteEdit {
insertText: string | SnippetString;
@@ -2504,7 +2520,6 @@ export class DocumentPasteEdit {
}
}
-@es5ClassCompat
export class ThemeIcon {
static File: ThemeIcon;
@@ -2522,7 +2537,6 @@ ThemeIcon.File = new ThemeIcon('file');
ThemeIcon.Folder = new ThemeIcon('folder');
-@es5ClassCompat
export class ThemeColor {
id: string;
constructor(id: string) {
@@ -2538,7 +2552,6 @@ export enum ConfigurationTarget {
WorkspaceFolder = 3
}
-@es5ClassCompat
export class RelativePattern implements IRelativePattern {
pattern: string;
@@ -2592,7 +2605,6 @@ export class RelativePattern implements IRelativePattern {
}
}
-@es5ClassCompat
export class Breakpoint {
private _id: string | undefined;
@@ -2623,7 +2635,6 @@ export class Breakpoint {
}
}
-@es5ClassCompat
export class SourceBreakpoint extends Breakpoint {
readonly location: Location;
@@ -2636,7 +2647,6 @@ export class SourceBreakpoint extends Breakpoint {
}
}
-@es5ClassCompat
export class FunctionBreakpoint extends Breakpoint {
readonly functionName: string;
@@ -2646,7 +2656,6 @@ export class FunctionBreakpoint extends Breakpoint {
}
}
-@es5ClassCompat
export class DataBreakpoint extends Breakpoint {
readonly label: string;
readonly dataId: string;
@@ -2664,7 +2673,6 @@ export class DataBreakpoint extends Breakpoint {
}
-@es5ClassCompat
export class DebugAdapterExecutable implements vscode.DebugAdapterExecutable {
readonly command: string;
readonly args: string[];
@@ -2677,7 +2685,6 @@ export class DebugAdapterExecutable implements vscode.DebugAdapterExecutable {
}
}
-@es5ClassCompat
export class DebugAdapterServer implements vscode.DebugAdapterServer {
readonly port: number;
readonly host?: string;
@@ -2688,13 +2695,11 @@ export class DebugAdapterServer implements vscode.DebugAdapterServer {
}
}
-@es5ClassCompat
export class DebugAdapterNamedPipeServer implements vscode.DebugAdapterNamedPipeServer {
constructor(public readonly path: string) {
}
}
-@es5ClassCompat
export class DebugAdapterInlineImplementation implements vscode.DebugAdapterInlineImplementation {
readonly implementation: vscode.DebugAdapter;
@@ -2703,7 +2708,6 @@ export class DebugAdapterInlineImplementation implements vscode.DebugAdapterInli
}
}
-@es5ClassCompat
export class EvaluatableExpression implements vscode.EvaluatableExpression {
readonly range: vscode.Range;
readonly expression?: string;
@@ -2724,7 +2728,6 @@ export enum InlineCompletionTriggerKindNew {
Automatic = 1,
}
-@es5ClassCompat
export class InlineValueText implements vscode.InlineValueText {
readonly range: Range;
readonly text: string;
@@ -2735,7 +2738,6 @@ export class InlineValueText implements vscode.InlineValueText {
}
}
-@es5ClassCompat
export class InlineValueVariableLookup implements vscode.InlineValueVariableLookup {
readonly range: Range;
readonly variableName?: string;
@@ -2748,7 +2750,6 @@ export class InlineValueVariableLookup implements vscode.InlineValueVariableLook
}
}
-@es5ClassCompat
export class InlineValueEvaluatableExpression implements vscode.InlineValueEvaluatableExpression {
readonly range: Range;
readonly expression?: string;
@@ -2759,7 +2760,6 @@ export class InlineValueEvaluatableExpression implements vscode.InlineValueEvalu
}
}
-@es5ClassCompat
export class InlineValueContext implements vscode.InlineValueContext {
readonly frameId: number;
@@ -2779,7 +2779,6 @@ export enum FileChangeType {
Deleted = 3,
}
-@es5ClassCompat
export class FileSystemError extends Error {
static FileExists(messageOrUri?: string | URI): FileSystemError {
@@ -2829,7 +2828,6 @@ export class FileSystemError extends Error {
//#region folding api
-@es5ClassCompat
export class FoldingRange {
start: number;
@@ -3119,7 +3117,6 @@ export enum DebugConsoleMode {
//#endregion
-@es5ClassCompat
export class QuickInputButtons {
static readonly Back: vscode.QuickInputButton = { iconPath: new ThemeIcon('arrow-left') };
@@ -3175,7 +3172,6 @@ export class FileDecoration {
//#region Theming
-@es5ClassCompat
export class ColorTheme implements vscode.ColorTheme {
constructor(public readonly kind: ColorThemeKind) {
}
@@ -3470,7 +3466,6 @@ export class NotebookRendererScript {
//#region Timeline
-@es5ClassCompat
export class TimelineItem implements vscode.TimelineItem {
constructor(public label: string, public timestamp: number) { }
}
@@ -3560,7 +3555,6 @@ export enum TestRunProfileKind {
Coverage = 3,
}
-@es5ClassCompat
export class TestRunRequest implements vscode.TestRunRequest {
constructor(
public readonly include: vscode.TestItem[] | undefined = undefined,
@@ -3569,7 +3563,6 @@ export class TestRunRequest implements vscode.TestRunRequest {
) { }
}
-@es5ClassCompat
export class TestMessage implements vscode.TestMessage {
public expectedOutput?: string;
public actualOutput?: string;
@@ -3585,7 +3578,6 @@ export class TestMessage implements vscode.TestMessage {
constructor(public message: string | vscode.MarkdownString) { }
}
-@es5ClassCompat
export class TestTag implements vscode.TestTag {
constructor(public readonly id: string) { }
}
@@ -3593,12 +3585,10 @@ export class TestTag implements vscode.TestTag {
//#endregion
//#region Test Coverage
-@es5ClassCompat
export class CoveredCount implements vscode.CoveredCount {
constructor(public covered: number, public total: number) { }
}
-@es5ClassCompat
export class FileCoverage implements vscode.FileCoverage {
public static fromDetails(uri: vscode.Uri, details: vscode.DetailedCoverage[]): vscode.FileCoverage {
const statements = new CoveredCount(0, 0);
@@ -3642,7 +3632,6 @@ export class FileCoverage implements vscode.FileCoverage {
) { }
}
-@es5ClassCompat
export class StatementCoverage implements vscode.StatementCoverage {
constructor(
public executionCount: number,
@@ -3651,7 +3640,6 @@ export class StatementCoverage implements vscode.StatementCoverage {
) { }
}
-@es5ClassCompat
export class BranchCoverage implements vscode.BranchCoverage {
constructor(
public executionCount: number,
@@ -3659,7 +3647,6 @@ export class BranchCoverage implements vscode.BranchCoverage {
) { }
}
-@es5ClassCompat
export class FunctionCoverage implements vscode.FunctionCoverage {
constructor(
public executionCount: number,
@@ -3741,4 +3728,7 @@ export class NotebookDiffEditorTabInput {
export class TerminalEditorTabInput {
constructor() { }
}
+export class InteractiveWindowInput {
+ constructor(readonly uri: URI, readonly inputBoxUri: URI) { }
+}
//#endregion
diff --git a/src/vs/workbench/api/common/shared/tasks.ts b/src/vs/workbench/api/common/shared/tasks.ts
index f7f206f0649..6f0445ab393 100644
--- a/src/vs/workbench/api/common/shared/tasks.ts
+++ b/src/vs/workbench/api/common/shared/tasks.ts
@@ -78,6 +78,7 @@ export interface ITaskSourceDTO {
scope?: number | UriComponents;
color?: string;
icon?: string;
+ hide?: boolean;
}
export interface ITaskHandleDTO {
diff --git a/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts b/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts
index b3fd49e72b2..2ceac7fde44 100644
--- a/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts
+++ b/src/vs/workbench/api/test/browser/extHostApiCommands.test.ts
@@ -100,7 +100,9 @@ suite('ExtHostLanguageFeatureCommands', function () {
override async activateByEvent() {
}
-
+ override activationEventIsDone(activationEvent: string): boolean {
+ return true;
+ }
});
services.set(ICommandService, new SyncDescriptor(class extends mock<ICommandService>() {
diff --git a/src/vs/workbench/api/test/browser/extHostBulkEdits.test.ts b/src/vs/workbench/api/test/browser/extHostBulkEdits.test.ts
index 3b393b35347..906405cd6b7 100644
--- a/src/vs/workbench/api/test/browser/extHostBulkEdits.test.ts
+++ b/src/vs/workbench/api/test/browser/extHostBulkEdits.test.ts
@@ -4,13 +4,12 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as extHostTypes from 'vs/workbench/api/common/extHostTypes';
-import { MainContext, IWorkspaceEditDto, WorkspaceEditType, MainThreadBulkEditsShape } from 'vs/workbench/api/common/extHost.protocol';
+import { MainContext, IWorkspaceEditDto, MainThreadBulkEditsShape, IWorkspaceTextEditDto } 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/api/test/common/testRPCProtocol';
import { NullLogService } from 'vs/platform/log/common/log';
-import { assertType } from 'vs/base/common/types';
import { ExtHostBulkEdits } from 'vs/workbench/api/common/extHostBulkEdits';
import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
@@ -50,8 +49,7 @@ suite('ExtHostBulkEdits.applyWorkspaceEdit', () => {
await bulkEdits.applyWorkspaceEdit(edit, nullExtensionDescription);
assert.strictEqual(workspaceResourceEdits.edits.length, 1);
const [first] = workspaceResourceEdits.edits;
- assertType(first._type === WorkspaceEditType.Text);
- assert.strictEqual(first.modelVersionId, 1337);
+ assert.strictEqual((<IWorkspaceTextEditDto>first).versionId, 1337);
});
test('does not use version id if document is not available', async () => {
@@ -60,8 +58,7 @@ suite('ExtHostBulkEdits.applyWorkspaceEdit', () => {
await bulkEdits.applyWorkspaceEdit(edit, nullExtensionDescription);
assert.strictEqual(workspaceResourceEdits.edits.length, 1);
const [first] = workspaceResourceEdits.edits;
- assertType(first._type === WorkspaceEditType.Text);
- assert.ok(typeof first.modelVersionId === 'undefined');
+ assert.ok(typeof (<IWorkspaceTextEditDto>first).versionId === 'undefined');
});
});
diff --git a/src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts b/src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts
index 70e338176d7..c50aabb812f 100644
--- a/src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts
+++ b/src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts
@@ -266,8 +266,8 @@ suite('ExtHostDocumentSaveParticipant', () => {
sub.dispose();
assert.strictEqual(dto.edits.length, 2);
- assert.ok((<IWorkspaceTextEditDto>dto.edits[0]).edit);
- assert.ok((<IWorkspaceTextEditDto>dto.edits[1]).edit);
+ assert.ok((<IWorkspaceTextEditDto>dto.edits[0]).textEdit);
+ assert.ok((<IWorkspaceTextEditDto>dto.edits[1]).textEdit);
});
});
@@ -317,7 +317,7 @@ suite('ExtHostDocumentSaveParticipant', () => {
for (const edit of dto.edits) {
const uri = URI.revive((<IWorkspaceTextEditDto>edit).resource);
- const { text, range } = (<IWorkspaceTextEditDto>edit).edit;
+ const { text, range } = (<IWorkspaceTextEditDto>edit).textEdit;
documents.$acceptModelChanged(uri, {
changes: [{
range,
diff --git a/src/vs/workbench/api/test/browser/extHostTypeConverter.test.ts b/src/vs/workbench/api/test/browser/extHostTypeConverter.test.ts
index a7a0b914279..650c28eb667 100644
--- a/src/vs/workbench/api/test/browser/extHostTypeConverter.test.ts
+++ b/src/vs/workbench/api/test/browser/extHostTypeConverter.test.ts
@@ -8,7 +8,6 @@ import * as assert from 'assert';
import * as extHostTypes from 'vs/workbench/api/common/extHostTypes';
import { MarkdownString, NotebookCellOutputItem, NotebookData, LanguageSelector } from 'vs/workbench/api/common/extHostTypeConverters';
import { isEmptyObject } from 'vs/base/common/types';
-import { forEach } from 'vs/base/common/collections';
import { LogLevel as _MainLogLevel } from 'vs/platform/log/common/log';
import { URI } from 'vs/base/common/uri';
@@ -74,13 +73,13 @@ suite('ExtHostTypeConverter', function () {
const data = MarkdownString.from('*hello* [click](command:npm.runScriptFromHover?%7B%22documentUri%22%3A%7B%22%24mid%22%3A1%2C%22external%22%3A%22file%3A%2F%2F%2Fc%253A%2Ffoo%2Fbaz.ex%22%2C%22path%22%3A%22%2Fc%3A%2Ffoo%2Fbaz.ex%22%2C%22scheme%22%3A%22file%22%7D%2C%22script%22%3A%22dev%22%7D)');
// assert that both uri get extracted but that the latter is only decoded once...
assert.strictEqual(size(data.uris!), 2);
- forEach(data.uris!, entry => {
- if (entry.value.scheme === 'file') {
- assert.ok(URI.revive(entry.value).toString().indexOf('file:///c%3A') === 0);
+ for (const value of Object.values(data.uris!)) {
+ if (value.scheme === 'file') {
+ assert.ok(URI.revive(value).toString().indexOf('file:///c%3A') === 0);
} else {
- assert.strictEqual(entry.value.scheme, 'command');
+ assert.strictEqual(value.scheme, 'command');
}
- });
+ }
});
test('Notebook metadata is ignored when using Notebook Serializer #125716', function () {
diff --git a/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts b/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts
index dac02ac748e..859c9c28ee8 100644
--- a/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts
+++ b/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts
@@ -9,7 +9,7 @@ import { TestConfigurationService } from 'vs/platform/configuration/test/common/
import { ModelService } from 'vs/editor/common/services/modelService';
import { TestCodeEditorService } from 'vs/editor/test/browser/editorTestServices';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
-import { IWorkspaceTextEditDto, WorkspaceEditType } from 'vs/workbench/api/common/extHost.protocol';
+import { IWorkspaceTextEditDto } from 'vs/workbench/api/common/extHost.protocol';
import { mock } from 'vs/base/test/common/mock';
import { Event } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
@@ -197,10 +197,9 @@ suite('MainThreadEditors', () => {
const model = modelService.createModel('something', null, resource);
const workspaceResourceEdit: IWorkspaceTextEditDto = {
- _type: WorkspaceEditType.Text,
resource: resource,
- modelVersionId: model.getVersionId(),
- edit: {
+ versionId: model.getVersionId(),
+ textEdit: {
text: 'asdfg',
range: new Range(1, 1, 1, 1)
}
@@ -219,19 +218,17 @@ suite('MainThreadEditors', () => {
const model = modelService.createModel('something', null, resource);
const workspaceResourceEdit1: IWorkspaceTextEditDto = {
- _type: WorkspaceEditType.Text,
resource: resource,
- modelVersionId: model.getVersionId(),
- edit: {
+ versionId: model.getVersionId(),
+ textEdit: {
text: 'asdfg',
range: new Range(1, 1, 1, 1)
}
};
const workspaceResourceEdit2: IWorkspaceTextEditDto = {
- _type: WorkspaceEditType.Text,
resource: resource,
- modelVersionId: model.getVersionId(),
- edit: {
+ versionId: model.getVersionId(),
+ textEdit: {
text: 'asdfg',
range: new Range(1, 1, 1, 1)
}
@@ -251,9 +248,9 @@ suite('MainThreadEditors', () => {
test(`applyWorkspaceEdit with only resource edit`, () => {
return bulkEdits.$tryApplyWorkspaceEdit({
edits: [
- { _type: WorkspaceEditType.File, oldUri: resource, newUri: resource, options: undefined },
- { _type: WorkspaceEditType.File, oldUri: undefined, newUri: resource, options: undefined },
- { _type: WorkspaceEditType.File, oldUri: resource, newUri: undefined, options: undefined }
+ { oldResource: resource, newResource: resource, options: undefined },
+ { oldResource: undefined, newResource: resource, options: undefined },
+ { oldResource: resource, newResource: undefined, options: undefined }
]
}).then((result) => {
assert.strictEqual(result, true);
diff --git a/src/vs/workbench/browser/actions.ts b/src/vs/workbench/browser/actions.ts
index 53eaf674196..02cf731caac 100644
--- a/src/vs/workbench/browser/actions.ts
+++ b/src/vs/workbench/browser/actions.ts
@@ -48,7 +48,7 @@ class MenuActions extends Disposable {
this._onDidChange.fire();
}
- private updateSubmenus(actions: readonly IAction[], submenus: { [id: number]: IMenu }): IDisposable {
+ private updateSubmenus(actions: readonly IAction[], submenus: Record<string, IMenu>): IDisposable {
const disposables = new DisposableStore();
for (const action of actions) {
diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts
index 4b4cc8b38f0..9bf977172a8 100644
--- a/src/vs/workbench/browser/actions/layoutActions.ts
+++ b/src/vs/workbench/browser/actions/layoutActions.ts
@@ -1182,7 +1182,7 @@ registerAction2(class CustomizeLayoutAction extends Action2 {
constructor() {
super({
id: 'workbench.action.customizeLayout',
- title: localize('customizeLayout', "Customize Layout..."),
+ title: { original: 'Customize Layout...', value: localize('customizeLayout', "Customize Layout...") },
f1: true,
icon: configureLayoutIcon,
menu: [
diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts
index 22791e7c00f..6703baae100 100644
--- a/src/vs/workbench/browser/parts/editor/editor.ts
+++ b/src/vs/workbench/browser/parts/editor/editor.ts
@@ -135,8 +135,6 @@ export interface IEditorGroupView extends IDisposable, ISerializableView, IEdito
readonly titleHeight: IEditorGroupTitleHeight;
- readonly isMinimized: boolean;
-
readonly disposed: boolean;
setActive(isActive: boolean): void;
diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts
index bf39a77a241..c93f52e60c6 100644
--- a/src/vs/workbench/browser/parts/editor/editorActions.ts
+++ b/src/vs/workbench/browser/parts/editor/editorActions.ts
@@ -573,24 +573,31 @@ abstract class AbstractCloseAllAction extends Action {
override async run(): Promise<void> {
// Depending on the editor and auto save configuration,
- // split dirty editors into buckets
+ // split editors into buckets for handling confirmation
const dirtyEditorsWithDefaultConfirm = new Set<IEditorIdentifier>();
const dirtyAutoSaveOnFocusChangeEditors = new Set<IEditorIdentifier>();
const dirtyAutoSaveOnWindowChangeEditors = new Set<IEditorIdentifier>();
- const dirtyEditorsWithCustomConfirm = new Map<string /* typeId */, Set<IEditorIdentifier>>();
+ const editorsWithCustomConfirm = new Map<string /* typeId */, Set<IEditorIdentifier>>();
for (const { editor, groupId } of this.editorService.getEditors(EditorsOrder.SEQUENTIAL, { excludeSticky: this.excludeSticky })) {
- if (!editor.isDirty() || editor.isSaving()) {
- continue; // only interested in dirty editors that are not in the process of saving
+ let confirmClose = false;
+ if (editor.closeHandler) {
+ confirmClose = editor.closeHandler.showConfirm(); // custom handling of confirmation on close
+ } else {
+ confirmClose = editor.isDirty() && !editor.isSaving(); // default confirm only when dirty and not saving
+ }
+
+ if (!confirmClose) {
+ continue;
}
// Editor has custom confirm implementation
- if (typeof editor.confirm === 'function') {
- let customEditorsToConfirm = dirtyEditorsWithCustomConfirm.get(editor.typeId);
+ if (typeof editor.closeHandler?.confirm === 'function') {
+ let customEditorsToConfirm = editorsWithCustomConfirm.get(editor.typeId);
if (!customEditorsToConfirm) {
customEditorsToConfirm = new Set();
- dirtyEditorsWithCustomConfirm.set(editor.typeId, customEditorsToConfirm);
+ editorsWithCustomConfirm.set(editor.typeId, customEditorsToConfirm);
}
customEditorsToConfirm.add({ editor, groupId });
@@ -619,7 +626,7 @@ abstract class AbstractCloseAllAction extends Action {
if (dirtyEditorsWithDefaultConfirm.size > 0) {
const editors = Array.from(dirtyEditorsWithDefaultConfirm.values());
- await this.revealDirtyEditors(editors); // help user make a decision by revealing editors
+ await this.revealEditorsToConfirm(editors); // help user make a decision by revealing editors
const confirmation = await this.fileDialogService.showSaveConfirm(editors.map(({ editor }) => {
if (editor instanceof SideBySideEditorInput) {
@@ -642,12 +649,12 @@ abstract class AbstractCloseAllAction extends Action {
}
// 2.) Show custom confirm based dialog
- for (const [, editorIdentifiers] of dirtyEditorsWithCustomConfirm) {
+ for (const [, editorIdentifiers] of editorsWithCustomConfirm) {
const editors = Array.from(editorIdentifiers.values());
- await this.revealDirtyEditors(editors); // help user make a decision by revealing editors
+ await this.revealEditorsToConfirm(editors); // help user make a decision by revealing editors
- const confirmation = await firstOrDefault(editors)?.editor.confirm?.(editors);
+ const confirmation = await firstOrDefault(editors)?.editor.closeHandler?.confirm?.(editors);
if (typeof confirmation === 'number') {
switch (confirmation) {
case ConfirmResult.CANCEL:
@@ -683,7 +690,7 @@ abstract class AbstractCloseAllAction extends Action {
return this.doCloseAll();
}
- private async revealDirtyEditors(editors: ReadonlyArray<IEditorIdentifier>): Promise<void> {
+ private async revealEditorsToConfirm(editors: ReadonlyArray<IEditorIdentifier>): Promise<void> {
try {
const handledGroups = new Set<GroupIdentifier>();
for (const { editor, groupId } of editors) {
@@ -1016,7 +1023,7 @@ export class MinimizeOtherGroupsAction extends Action {
}
override async run(): Promise<void> {
- this.editorGroupService.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS);
+ this.editorGroupService.arrangeGroups(GroupsArrangement.MAXIMIZE);
}
}
@@ -1067,7 +1074,7 @@ export class MaximizeGroupAction extends Action {
if (this.editorService.activeEditor) {
this.layoutService.setPartHidden(true, Parts.SIDEBAR_PART);
this.layoutService.setPartHidden(true, Parts.AUXILIARYBAR_PART);
- this.editorGroupService.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS);
+ this.editorGroupService.arrangeGroups(GroupsArrangement.MAXIMIZE);
}
}
}
diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts
index 717d7b138c1..6abeeef5019 100644
--- a/src/vs/workbench/browser/parts/editor/editorCommands.ts
+++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts
@@ -969,6 +969,7 @@ function registerCloseEditorCommands() {
type WorkbenchEditorReopenClassification = {
owner: 'rebornix';
+ comment: 'Identify how a document is reopened';
scheme: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
ext: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
from: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts
index 21b5d732107..d2df00e4d5e 100644
--- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts
+++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts
@@ -781,14 +781,6 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
return this.titleAreaControl.getHeight();
}
- get isMinimized(): boolean {
- if (!this.dimension) {
- return false;
- }
-
- return this.dimension.width === this.minimumWidth || this.dimension.height === this.minimumHeight;
- }
-
notifyIndexChanged(newIndex: number): void {
if (this._index !== newIndex) {
this._index = newIndex;
@@ -1322,16 +1314,16 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
//#region closeEditor()
async closeEditor(editor: EditorInput | undefined = this.activeEditor || undefined, options?: ICloseEditorOptions): Promise<boolean> {
- return this.doCloseEditorWithDirtyHandling(editor, options);
+ return this.doCloseEditorWithConfirmationHandling(editor, options);
}
- private async doCloseEditorWithDirtyHandling(editor: EditorInput | undefined = this.activeEditor || undefined, options?: ICloseEditorOptions, internalOptions?: IInternalEditorCloseOptions): Promise<boolean> {
+ private async doCloseEditorWithConfirmationHandling(editor: EditorInput | undefined = this.activeEditor || undefined, options?: ICloseEditorOptions, internalOptions?: IInternalEditorCloseOptions): Promise<boolean> {
if (!editor) {
return false;
}
- // Check for dirty and veto
- const veto = await this.handleDirtyClosing([editor]);
+ // Check for confirmation and veto
+ const veto = await this.handleCloseConfirmation([editor]);
if (veto) {
return false;
}
@@ -1461,7 +1453,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
return this.model.closeEditor(editor, internalOptions?.context)?.editorIndex;
}
- private async handleDirtyClosing(editors: EditorInput[]): Promise<boolean /* veto */> {
+ private async handleCloseConfirmation(editors: EditorInput[]): Promise<boolean /* veto */> {
if (!editors.length) {
return false; // no veto
}
@@ -1470,15 +1462,15 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// To prevent multiple confirmation dialogs from showing up one after the other
// we check if a pending confirmation is currently showing and if so, join that
- let handleDirtyClosingPromise = this.mapEditorToPendingConfirmation.get(editor);
- if (!handleDirtyClosingPromise) {
- handleDirtyClosingPromise = this.doHandleDirtyClosing(editor);
- this.mapEditorToPendingConfirmation.set(editor, handleDirtyClosingPromise);
+ let handleCloseConfirmationPromise = this.mapEditorToPendingConfirmation.get(editor);
+ if (!handleCloseConfirmationPromise) {
+ handleCloseConfirmationPromise = this.doHandleCloseConfirmation(editor);
+ this.mapEditorToPendingConfirmation.set(editor, handleCloseConfirmationPromise);
}
let veto: boolean;
try {
- veto = await handleDirtyClosingPromise;
+ veto = await handleCloseConfirmationPromise;
} finally {
this.mapEditorToPendingConfirmation.delete(editor);
}
@@ -1489,12 +1481,12 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
// Otherwise continue with the remainders
- return this.handleDirtyClosing(editors);
+ return this.handleCloseConfirmation(editors);
}
- private async doHandleDirtyClosing(editor: EditorInput, options?: { skipAutoSave: boolean }): Promise<boolean /* veto */> {
- if (!editor.isDirty() || editor.isSaving()) {
- return false; // editor must be dirty and not saving
+ private async doHandleCloseConfirmation(editor: EditorInput, options?: { skipAutoSave: boolean }): Promise<boolean /* veto */> {
+ if (!this.shouldConfirmClose(editor)) {
+ return false; // no veto
}
if (editor instanceof SideBySideEditorInput && this.model.contains(editor.primary)) {
@@ -1531,10 +1523,11 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// on auto-save configuration.
// However, make sure to respect `skipAutoSave` option in case the automated
// save fails which would result in the editor never closing.
+ // Also, we only do this if no custom confirmation handling is implemented.
let confirmation = ConfirmResult.CANCEL;
let saveReason = SaveReason.EXPLICIT;
let autoSave = false;
- if (!editor.hasCapability(EditorInputCapabilities.Untitled) && !options?.skipAutoSave) {
+ if (!editor.hasCapability(EditorInputCapabilities.Untitled) && !options?.skipAutoSave && !editor.closeHandler) {
// Auto-save on focus change: save, because a dialog would steal focus
// (see https://github.com/microsoft/vscode/issues/108752)
@@ -1554,15 +1547,15 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
}
- // No auto-save on focus change: ask user
+ // No auto-save on focus change or custom confirmation handler: ask user
if (!autoSave) {
- // Switch to editor that we want to handle and confirm to save/revert
+ // Switch to editor that we want to handle for confirmation
await this.doOpenEditor(editor);
// Let editor handle confirmation if implemented
- if (typeof editor.confirm === 'function') {
- confirmation = await editor.confirm();
+ if (typeof editor.closeHandler?.confirm === 'function') {
+ confirmation = await editor.closeHandler.confirm();
}
// Show a file specific confirmation
@@ -1578,11 +1571,14 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
}
- // It could be that the editor saved meanwhile or is saving, so we check
+ // It could be that the editor's choice of confirmation has changed
+ // given the check for confirmation is long running, so we check
// again to see if anything needs to happen before closing for good.
- // This can happen for example if autoSave: onFocusChange is configured
+ // This can happen for example if `autoSave: onFocusChange` is configured
// so that the save happens when the dialog opens.
- if (!editor.isDirty() || editor.isSaving()) {
+ // However, we only do this unless a custom confirm handler is installed
+ // that may not be fit to be asked a second time right after.
+ if (!editor.closeHandler && !this.shouldConfirmClose(editor)) {
return confirmation === ConfirmResult.CANCEL ? true : false;
}
@@ -1595,7 +1591,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// we handle the dirty editor again but this time ensuring to
// show the confirm dialog
// (see https://github.com/microsoft/vscode/issues/108752)
- return this.doHandleDirtyClosing(editor, { skipAutoSave: true });
+ return this.doHandleCloseConfirmation(editor, { skipAutoSave: true });
}
return editor.isDirty(); // veto if still dirty
@@ -1621,6 +1617,14 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
}
}
+ private shouldConfirmClose(editor: EditorInput): boolean {
+ if (editor.closeHandler) {
+ return editor.closeHandler.showConfirm(); // custom handling of confirmation on close
+ }
+
+ return editor.isDirty() && !editor.isSaving(); // editor must be dirty and not saving
+ }
+
//#endregion
//#region closeEditors()
@@ -1632,8 +1636,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
const editors = this.doGetEditorsToClose(args);
- // Check for dirty and veto
- const veto = await this.handleDirtyClosing(editors.slice(0));
+ // Check for confirmation and veto
+ const veto = await this.handleCloseConfirmation(editors.slice(0));
if (veto) {
return false;
}
@@ -1714,8 +1718,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
return true;
}
- // Check for dirty and veto
- const veto = await this.handleDirtyClosing(this.model.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE, options));
+ // Check for confirmation and veto
+ const veto = await this.handleCloseConfirmation(this.model.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE, options));
if (veto) {
return false;
}
@@ -1795,7 +1799,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
this.doCloseEditor(editor, false, { context: EditorCloseContext.REPLACE });
closed = true;
} else {
- closed = await this.doCloseEditorWithDirtyHandling(editor, { preserveFocus: true }, { context: EditorCloseContext.REPLACE });
+ closed = await this.doCloseEditorWithConfirmationHandling(editor, { preserveFocus: true }, { context: EditorCloseContext.REPLACE });
}
if (!closed) {
@@ -1815,7 +1819,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
if (activeReplacement.forceReplaceDirty) {
this.doCloseEditor(activeReplacement.editor, false, { context: EditorCloseContext.REPLACE });
} else {
- await this.doCloseEditorWithDirtyHandling(activeReplacement.editor, { preserveFocus: true }, { context: EditorCloseContext.REPLACE });
+ await this.doCloseEditorWithConfirmationHandling(activeReplacement.editor, { preserveFocus: true }, { context: EditorCloseContext.REPLACE });
}
}
diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts
index cf162aa9dae..da027276bd8 100644
--- a/src/vs/workbench/browser/parts/editor/editorPart.ts
+++ b/src/vs/workbench/browser/parts/editor/editorPart.ts
@@ -362,32 +362,22 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
case GroupsArrangement.EVEN:
this.gridWidget.distributeViewSizes();
break;
- case GroupsArrangement.MINIMIZE_OTHERS:
+ case GroupsArrangement.MAXIMIZE:
this.gridWidget.maximizeViewSize(target);
break;
case GroupsArrangement.TOGGLE:
if (this.isGroupMaximized(target)) {
this.arrangeGroups(GroupsArrangement.EVEN);
} else {
- this.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS);
+ this.arrangeGroups(GroupsArrangement.MAXIMIZE);
}
break;
}
}
- private isGroupMaximized(targetGroup: IEditorGroupView): boolean {
- for (const group of this.groups) {
- if (group === targetGroup) {
- continue; // ignore target group
- }
-
- if (!group.isMinimized) {
- return false; // target cannot be maximized if one group is not minimized
- }
- }
-
- return true;
+ isGroupMaximized(targetGroup: IEditorGroupView): boolean {
+ return this.gridWidget.isViewSizeMaximized(targetGroup);
}
setGroupOrientation(orientation: GroupOrientation): void {
@@ -609,7 +599,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
if (this.gridWidget) {
const viewSize = this.gridWidget.getViewSize(group);
if (viewSize.width === group.minimumWidth || viewSize.height === group.minimumHeight) {
- this.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS, group);
+ this.arrangeGroups(GroupsArrangement.MAXIMIZE, group);
}
}
}
diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts b/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts
index 9d9c7e3e23a..6b4685f4901 100644
--- a/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts
+++ b/src/vs/workbench/browser/parts/statusbar/statusbarItem.ts
@@ -12,7 +12,7 @@ import { IStatusbarEntry, ShowTooltipCommand } from 'vs/workbench/services/statu
import { WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions';
import { IThemeService, ThemeColor } from 'vs/platform/theme/common/themeService';
import { isThemeColor } from 'vs/editor/common/editorCommon';
-import { addDisposableListener, EventType, hide, show, append } from 'vs/base/browser/dom';
+import { addDisposableListener, EventType, hide, show, append, EventHelper } from 'vs/base/browser/dom';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { assertIsDefined } from 'vs/base/common/types';
import { Command } from 'vs/editor/common/languages';
@@ -129,6 +129,8 @@ export class StatusbarEntryItem extends Disposable {
this.commandKeyboardListener.value = addDisposableListener(this.labelContainer, EventType.KEY_DOWN, e => {
const event = new StandardKeyboardEvent(e);
if (event.equals(KeyCode.Space) || event.equals(KeyCode.Enter)) {
+ EventHelper.stop(e);
+
this.executeCommand(command);
}
});
diff --git a/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts
index de2f74a7b2f..2b383111c09 100644
--- a/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts
+++ b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts
@@ -4,21 +4,20 @@
*--------------------------------------------------------------------------------------------*/
import { reset } from 'vs/base/browser/dom';
-import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate';
import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels';
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
-import { Action, IAction } from 'vs/base/common/actions';
+import { IAction } from 'vs/base/common/actions';
import { Codicon } from 'vs/base/common/codicons';
import { Emitter, Event } from 'vs/base/common/event';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { assertType } from 'vs/base/common/types';
import { localize } from 'vs/nls';
import { createActionViewItem, createAndFillInContextMenuActions, MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
-import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';
+import { Action2, IMenuService, MenuId, MenuItemAction, registerAction2 } from 'vs/platform/actions/common/actions';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
-import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
+import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import * as colors from 'vs/platform/theme/common/colorRegistry';
@@ -57,7 +56,7 @@ export class CommandCenterControl {
override render(container: HTMLElement): void {
super.render(container);
- container.classList.add('quickopen');
+ container.classList.add('quickopen', 'left');
assertType(this.label);
this.label.classList.add('search');
@@ -68,7 +67,7 @@ export class CommandCenterControl {
this.workspaceTitle.classList.add('search-label');
this._updateFromWindowTitle();
reset(this.label, searchIcon, this.workspaceTitle);
- this._renderAllQuickPickItem(container);
+ // this._renderAllQuickPickItem(container);
this._store.add(windowTitle.onDidChange(this._updateFromWindowTitle, this));
}
@@ -96,24 +95,23 @@ export class CommandCenterControl {
: localize('title2', "Search {0} \u2014 {1}", windowTitle.workspaceName, windowTitle.value);
this._applyUpdateTooltip(title);
}
+ }
+ return instantiationService.createInstance(InputLikeViewItem, action, { hoverDelegate });
- private _renderAllQuickPickItem(parent: HTMLElement): void {
- const container = document.createElement('span');
- container.classList.add('all-options');
- parent.appendChild(container);
- const action = new Action('all', localize('all', "Show Search Modes..."), Codicon.chevronDown.classNames, true, () => {
- quickInputService.quickAccess.show('?');
- });
- const dropdown = new ActionViewItem(undefined, action, { icon: true, label: false, hoverDelegate });
- dropdown.render(container);
- this._store.add(dropdown);
- this._store.add(action);
+ } else if (action instanceof MenuItemAction && action.id === 'commandCenter.help') {
+
+ class ExtraClass extends MenuEntryActionViewItem {
+ override render(container: HTMLElement): void {
+ super.render(container);
+ container.classList.add('quickopen', 'right');
}
}
- return instantiationService.createInstance(InputLikeViewItem, action, { hoverDelegate });
- }
- return createActionViewItem(instantiationService, action, { hoverDelegate });
+ return instantiationService.createInstance(ExtraClass, action, { hoverDelegate });
+
+ } else {
+ return createActionViewItem(instantiationService, action, { hoverDelegate });
+ }
}
});
const menu = this._disposables.add(menuService.createMenu(MenuId.CommandCenter, contextKeyService));
@@ -143,6 +141,21 @@ export class CommandCenterControl {
}
}
+registerAction2(class extends Action2 {
+
+ constructor() {
+ super({
+ id: 'commandCenter.help',
+ title: localize('all', "Show Search Modes..."),
+ icon: Codicon.chevronDown,
+ menu: { id: MenuId.CommandCenter, order: 101 }
+ });
+ }
+ run(accessor: ServicesAccessor): void {
+ accessor.get(IQuickInputService).quickAccess.show('?');
+ }
+});
+
// --- theme colors
// foreground (inactive and active)
diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css
index b661db98d5c..29f1142364e 100644
--- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css
+++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css
@@ -101,13 +101,6 @@
color: var(--vscode-commandCenter-foreground);
background-color: var(--vscode-commandCenter-background);
border: 1px solid var(--vscode-commandCenter-border);
- border-radius: 5px;
- height: 20px;
- line-height: 18px;
- width: 38vw;
- max-width: 600px;
- min-width: 32px;
- margin: 0 4px;
flex-direction: row;
justify-content: center;
overflow: hidden;
@@ -116,47 +109,55 @@
.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen:HOVER {
color: var(--vscode-commandCenter-activeForeground);
background-color: var(--vscode-commandCenter-activeBackground);
- line-height: 18px;
}
-.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center:HOVER .quickopen .action-label {
- background-color: transparent !important;
- outline-color: transparent !important;
+.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen.left {
+ /* border,margin tricks */
+ margin-left: 6px;
+ border-top-left-radius: 6px;
+ border-bottom-left-radius: 6px;
+ border-right: none;
+
+ /* width */
+ width: 38vw;
+ max-width: 600px;
+ min-width: 32px;
+}
+
+.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen.right {
+ /* border,margin tricks */
+ margin-right: 6px;
+ border-top-right-radius: 6px;
+ border-bottom-right-radius: 6px;
+ border-left: none;
+
+ /* width */
+ width: 16px;
}
-.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen>.action-label.search {
+.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen>.action-label {
+ height: 22px;
+ line-height: 22px;
+ padding: 0;
+ background-color: transparent;
display: inline-flex;
text-align: center;
font-size: 12px;
- background-color: inherit;
justify-content: center;
- width: calc(100% - 19px);
+ width: 100%;
}
-.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen>.action-label.search>.search-icon {
+.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen.left>.action-label.search>.search-icon {
font-size: 14px;
opacity: .8;
margin: auto 3px;
}
-.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen>.action-label.search>.search-label {
+.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen.left>.action-label.search>.search-label {
overflow: hidden;
text-overflow: ellipsis;
}
-.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen>.all-options>.action-label {
- text-align: center;
- font-size: 12px;
- width: 16px;
- border-left: 1px solid transparent;
- border-radius: 0;
- padding-right: 0;
-}
-
-.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center:HOVER .action-item.quickopen>.all-options>.action-label {
- border-color: var(--vscode-commandCenter-border);
-}
-
/* Menubar */
.monaco-workbench .part.titlebar>.titlebar-container>.menubar {
/* move menubar above drag region as negative z-index on drag region cause greyscale AA */
diff --git a/src/vs/workbench/browser/parts/titlebar/windowTitle.ts b/src/vs/workbench/browser/parts/titlebar/windowTitle.ts
index 5f5e16ead26..9e9922b840a 100644
--- a/src/vs/workbench/browser/parts/titlebar/windowTitle.ts
+++ b/src/vs/workbench/browser/parts/titlebar/windowTitle.ts
@@ -27,7 +27,6 @@ import { getVirtualWorkspaceLocation } from 'vs/platform/workspace/common/virtua
export class WindowTitle extends Disposable {
- private static readonly NLS_UNSUPPORTED = localize('patchedWindowTitle', "[Unsupported]");
private static readonly NLS_USER_IS_ADMIN = isWindows ? localize('userIsAdmin', "[Administrator]") : localize('userIsSudo', "[Superuser]");
private static readonly NLS_EXTENSION_HOST = localize('devExtensionWindowTitlePrefix', "[Extension Development Host]");
private static readonly TITLE_DIRTY = '\u25cf ';
@@ -137,11 +136,6 @@ export class WindowTitle extends Disposable {
if (this.properties.isAdmin) {
suffix = WindowTitle.NLS_USER_IS_ADMIN;
}
- if (!this.properties.isPure) {
- suffix = !suffix
- ? WindowTitle.NLS_UNSUPPORTED
- : `${suffix} ${WindowTitle.NLS_UNSUPPORTED}`;
- }
return { prefix, suffix };
}
diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts
index e83eb19ce63..d0791572ccf 100644
--- a/src/vs/workbench/browser/web.main.ts
+++ b/src/vs/workbench/browser/web.main.ts
@@ -22,7 +22,7 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA
import { IWorkbenchFileService } from 'vs/workbench/services/files/common/files';
import { FileService } from 'vs/platform/files/common/fileService';
import { Schemas, connectionTokenCookieName } from 'vs/base/common/network';
-import { IAnyWorkspaceIdentifier, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
+import { IAnyWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { onUnexpectedError } from 'vs/base/common/errors';
import { setFullscreen } from 'vs/base/browser/browser';
@@ -73,13 +73,14 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IProgressService } from 'vs/platform/progress/common/progress';
import { DelayedLogChannel } from 'vs/workbench/services/output/common/delayedLogChannel';
import { dirname, joinPath } from 'vs/base/common/resources';
-import { IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
+import { IUserDataProfilesService, PROFILES_ENABLEMENT_CONFIG } from 'vs/platform/userDataProfile/common/userDataProfile';
import { NullPolicyService } from 'vs/platform/policy/common/policy';
import { IRemoteExplorerService, TunnelSource } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { DisposableTunnel } from 'vs/platform/tunnel/common/tunnel';
import { ILabelService } from 'vs/platform/label/common/label';
import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService';
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
+import { BrowserUserDataProfilesService } from 'vs/platform/userDataProfile/browser/userDataProfile';
export class BrowserMain extends Disposable {
@@ -259,17 +260,16 @@ export class BrowserMain extends Disposable {
serviceCollection.set(IWorkbenchFileService, fileService);
await this.registerFileSystemProviders(environmentService, fileService, remoteAgentService, logService, logsPath);
- // User Data Profiles
- const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, logService);
- serviceCollection.set(IUserDataProfilesService, userDataProfilesService);
-
- const userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile);
- serviceCollection.set(IUserDataProfileService, userDataProfileService);
-
// URI Identity
const uriIdentityService = new UriIdentityService(fileService);
serviceCollection.set(IUriIdentityService, uriIdentityService);
+ // User Data Profiles
+ const userDataProfilesService = new BrowserUserDataProfilesService(environmentService, fileService, uriIdentityService, logService);
+ serviceCollection.set(IUserDataProfilesService, userDataProfilesService);
+ const userDataProfileService = new UserDataProfileService(userDataProfilesService.getProfile(isWorkspaceIdentifier(payload) || isSingleFolderWorkspaceIdentifier(payload) ? payload : 'empty-window'), userDataProfilesService);
+ serviceCollection.set(IUserDataProfileService, userDataProfileService);
+
// Long running services (workspace, config, storage)
const [configurationService, storageService] = await Promise.all([
this.createWorkspaceService(payload, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, uriIdentityService, logService).then(service => {
@@ -292,6 +292,7 @@ export class BrowserMain extends Disposable {
})
]);
+ userDataProfilesService.setEnablement(!!configurationService.getValue(PROFILES_ENABLEMENT_CONFIG));
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts
index 9c75b526575..5d3a8a9d037 100644
--- a/src/vs/workbench/browser/workbench.contribution.ts
+++ b/src/vs/workbench/browser/workbench.contribution.ts
@@ -245,7 +245,7 @@ const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Con
},
'workbench.editor.restoreViewState': {
'type': 'boolean',
- 'markdownDescription': localize('restoreViewState', "Restores the last editor view state (e.g. scroll position) when re-opening editors after they have been closed. Editor view state is stored per editor group and discarded when a group closes. Use the `#workbench.editor.sharedViewState#` setting to use the last known view state across all editor groups in case no previous view state was found for a editor group."),
+ 'markdownDescription': localize('restoreViewState', "Restores the last editor view state (e.g. scroll position) when re-opening editors after they have been closed. Editor view state is stored per editor group and discarded when a group closes. Use the {0} setting to use the last known view state across all editor groups in case no previous view state was found for a editor group.", '`#workbench.editor.sharedViewState#`'),
'default': true,
'scope': ConfigurationScope.LANGUAGE_OVERRIDABLE
},
@@ -278,7 +278,7 @@ const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Con
'type': 'number',
'default': 10,
'exclusiveMinimum': 0,
- 'markdownDescription': localize('limitEditorsMaximum', "Controls the maximum number of opened editors. Use the `#workbench.editor.limit.perEditorGroup#` setting to control this limit per editor group or across all groups.")
+ 'markdownDescription': localize('limitEditorsMaximum', "Controls the maximum number of opened editors. Use the {0} setting to control this limit per editor group or across all groups.", '`#workbench.editor.limit.perEditorGroup#`')
},
'workbench.editor.limit.excludeDirty': {
'type': 'boolean',
@@ -540,12 +540,12 @@ const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Con
'window.titleSeparator': {
'type': 'string',
'default': isMacintosh ? ' \u2014 ' : ' - ',
- 'markdownDescription': localize("window.titleSeparator", "Separator used by `window.title`.")
+ 'markdownDescription': localize("window.titleSeparator", "Separator used by {0}.", '`#window.title#`')
},
'window.commandCenter': {
type: 'boolean',
default: false,
- markdownDescription: localize('window.commandCenter', "Show command launcher together with the window title. This setting only has an effect when `#window.titleBarStyle#` is set to `custom`.")
+ markdownDescription: localize('window.commandCenter', "Show command launcher together with the window title. This setting only has an effect when {0} is set to {1}.", '`#window.titleBarStyle#`', '`custom`')
},
'window.menuBarVisibility': {
'type': 'string',
@@ -557,7 +557,7 @@ const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Con
localize('window.menuBarVisibility.toggle.mac', "Menu is hidden but can be displayed at the top of the window by executing the `Focus Application Menu` command.") :
localize('window.menuBarVisibility.toggle', "Menu is hidden but can be displayed at the top of the window via the Alt key."),
localize('window.menuBarVisibility.hidden', "Menu is always hidden."),
- localize('window.menuBarVisibility.compact', "Menu is displayed as a compact button in the side bar. This value is ignored when `#window.titleBarStyle#` is `native`.")
+ localize('window.menuBarVisibility.compact', "Menu is displayed as a compact button in the side bar. This value is ignored when {0} is {1}.", '`#window.titleBarStyle#`', '`native`')
],
'default': isWeb ? 'compact' : 'classic',
'scope': ConfigurationScope.APPLICATION,
diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts
index 8094c87aca2..d505032b79a 100644
--- a/src/vs/workbench/browser/workbench.ts
+++ b/src/vs/workbench/browser/workbench.ts
@@ -188,11 +188,8 @@ export class Workbench extends Layout {
//
// NOTE: Please do NOT register services here. Use `registerSingleton()`
// from `workbench.common.main.ts` if the service is shared between
- // native and web or `workbench.sandbox.main.ts` if the service
- // is native only.
- //
- // DO NOT add services to `workbench.desktop.main.ts`, always add
- // to `workbench.sandbox.main.ts` to support our Electron sandbox
+ // desktop and web or `workbench.desktop.main.ts` if the service
+ // is desktop only.
//
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
diff --git a/src/vs/workbench/common/editor/editorInput.ts b/src/vs/workbench/common/editor/editorInput.ts
index 023e15af1af..86b53423c60 100644
--- a/src/vs/workbench/common/editor/editorInput.ts
+++ b/src/vs/workbench/common/editor/editorInput.ts
@@ -11,6 +11,31 @@ import { EditorInputCapabilities, Verbosity, GroupIdentifier, ISaveOptions, IRev
import { isEqual } from 'vs/base/common/resources';
import { ConfirmResult } from 'vs/platform/dialogs/common/dialogs';
+export interface IEditorCloseHandler {
+
+ /**
+ * If `true`, will call into the `confirm` method to ask for confirmation
+ * before closing the editor.
+ */
+ showConfirm(): boolean;
+
+ /**
+ * Allows an editor to control what should happen when the editor
+ * (or a list of editor of the same kind) is being closed.
+ *
+ * By default a file specific dialog will open if the editor is
+ * dirty and not in the process of saving.
+ *
+ * If the editor is not dealing with files or another condition
+ * should be used besides dirty state, this method should be
+ * implemented to show a different dialog.
+ *
+ * @param editors if more than one editor is closed, will pass in
+ * each editor of the same kind to be able to show a combined dialog.
+ */
+ confirm(editors?: ReadonlyArray<IEditorIdentifier>): Promise<ConfirmResult>;
+}
+
/**
* Editor inputs are lightweight objects that can be passed to the workbench API to open inside the editor part.
* Each editor input is mapped to an editor that is capable of opening it through the Platform facade.
@@ -46,6 +71,12 @@ export abstract class EditorInput extends AbstractEditorInput {
private disposed: boolean = false;
/**
+ * Optional: subclasses can override to implement
+ * custom confirmation on close behavior.
+ */
+ readonly closeHandler?: IEditorCloseHandler;
+
+ /**
* Unique type identifier for this input. Every editor input of the
* same class should share the same type identifier. The type identifier
* is used for example for serialising/deserialising editor inputs
@@ -169,20 +200,6 @@ export abstract class EditorInput extends AbstractEditorInput {
}
/**
- * Optional: if this method is implemented, allows an editor to
- * control what should happen when the editor (or a list of editors
- * of the same kind) is dirty and there is an intent to close it.
- *
- * By default a file specific dialog will open. If the editor is
- * not dealing with files, this method should be implemented to
- * show a different dialog.
- *
- * @param editors if more than one editor is closed, will pass in
- * each editor of the same kind to be able to show a combined dialog.
- */
- confirm?(editors?: ReadonlyArray<IEditorIdentifier>): Promise<ConfirmResult>;
-
- /**
* Saves the editor. The provided groupId helps implementors
* to e.g. preserve view state of the editor and re-open it
* in the correct group after saving.
diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts
index fc3727f804d..3b400d3fafc 100644
--- a/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts
+++ b/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts
@@ -4,9 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
+import { autorunWithStore } from 'vs/base/common/observable';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { AudioCue, IAudioCueService } from 'vs/workbench/contrib/audioCues/browser/audioCueService';
-import { autorunWithStore } from 'vs/workbench/contrib/audioCues/browser/observable';
import { IDebugService, IDebugSession } from 'vs/workbench/contrib/debug/common/debug';
export class AudioCueLineDebuggerContribution
diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts
index 3c348baa40a..490fab6c4c6 100644
--- a/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts
+++ b/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts
@@ -12,21 +12,11 @@ import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/edito
import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers';
import { FoldingController } from 'vs/editor/contrib/folding/browser/folding';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
-import {
- autorun,
- autorunDelta,
- constObservable,
- derivedObservable,
- observableFromEvent,
- observableFromPromise,
- IObservable,
- wasEventTriggeredRecently,
- debouncedObservable,
-} from 'vs/workbench/contrib/audioCues/browser/observable';
import { ITextModel } from 'vs/editor/common/model';
import { GhostTextController } from 'vs/editor/contrib/inlineCompletions/browser/ghostTextController';
import { AudioCue, IAudioCueService } from 'vs/workbench/contrib/audioCues/browser/audioCueService';
import { CursorChangeReason } from 'vs/editor/common/cursorEvents';
+import { autorun, autorunDelta, constObservable, debouncedObservable, derived, IObservable, observableFromEvent, observableFromPromise, wasEventTriggeredRecently } from 'vs/base/common/observable';
export class AudioCueLineFeatureContribution
extends Disposable
@@ -48,7 +38,7 @@ export class AudioCueLineFeatureContribution
) {
super();
- const someAudioCueFeatureIsEnabled = derivedObservable(
+ const someAudioCueFeatureIsEnabled = derived(
'someAudioCueFeatureIsEnabled',
(reader) =>
this.features.some((feature) =>
@@ -73,7 +63,7 @@ export class AudioCueLineFeatureContribution
);
this._register(
- autorun((reader) => {
+ autorun('updateAudioCuesEnabled', (reader) => {
this.store.clear();
if (!someAudioCueFeatureIsEnabled.read(reader)) {
@@ -84,7 +74,7 @@ export class AudioCueLineFeatureContribution
if (activeEditor) {
this.registerAudioCuesForEditor(activeEditor.editor, activeEditor.model, this.store);
}
- }, 'updateAudioCuesEnabled')
+ })
);
}
@@ -96,6 +86,7 @@ export class AudioCueLineFeatureContribution
const curLineNumber = observableFromEvent(
editor.onDidChangeCursorPosition,
(args) => {
+ /** @description editor.onDidChangeCursorPosition (caused by user) */
if (
args &&
args.reason !== CursorChangeReason.Explicit &&
@@ -117,7 +108,7 @@ export class AudioCueLineFeatureContribution
const featureStates = this.features.map((feature) => {
const lineFeatureState = feature.getObservableState(editor, editorModel);
- const isFeaturePresent = derivedObservable(
+ const isFeaturePresent = derived(
`isPresentInLine:${feature.audioCue.name}`,
(reader) => {
if (!this.audioCueService.isEnabled(feature.audioCue).read(reader)) {
@@ -129,7 +120,7 @@ export class AudioCueLineFeatureContribution
: lineFeatureState.read(reader).isPresent(lineNumber);
}
);
- return derivedObservable(
+ return derived(
`typingDebouncedFeatureState:\n${feature.audioCue.name}`,
(reader) =>
feature.debounceWhileTyping && isTyping.read(reader)
@@ -138,7 +129,7 @@ export class AudioCueLineFeatureContribution
);
});
- const state = derivedObservable(
+ const state = derived(
'states',
(reader) => ({
lineNumber: debouncedLineNumber.read(reader),
@@ -193,7 +184,7 @@ class MarkerLineFeature implements LineFeature {
Event.filter(this.markerService.onMarkerChanged, (changedUris) =>
changedUris.some((u) => u.toString() === model.uri.toString())
),
- () => ({
+ () => /** @description this.markerService.onMarkerChanged */({
isPresent: (lineNumber) => {
const hasMarker = this.markerService
.read({ resource: model.uri })
@@ -245,7 +236,7 @@ class BreakpointLineFeature implements LineFeature {
getObservableState(editor: ICodeEditor, model: ITextModel): IObservable<LineFeatureState> {
return observableFromEvent<LineFeatureState>(
this.debugService.getModel().onDidChangeBreakpoints,
- () => ({
+ () => /** @description debugService.getModel().onDidChangeBreakpoints */({
isPresent: (lineNumber) => {
const breakpoints = this.debugService
.getModel()
@@ -271,17 +262,17 @@ class InlineCompletionLineFeature implements LineFeature {
const activeGhostText = observableFromEvent(
ghostTextController.onActiveModelDidChange,
- () => ghostTextController.activeModel
+ () => /** @description ghostTextController.onActiveModelDidChange */ ghostTextController.activeModel
).map((activeModel) => (
activeModel
? observableFromEvent(
activeModel.inlineCompletionsModel.onDidChange,
- () => activeModel.inlineCompletionsModel.ghostText
+ () => /** @description activeModel.inlineCompletionsModel.onDidChange */ activeModel.inlineCompletionsModel.ghostText
)
: undefined
));
- return derivedObservable<LineFeatureState>('ghostText', reader => {
+ return derived<LineFeatureState>('ghostText', reader => {
const ghostText = activeGhostText.read(reader)?.read(reader);
return {
isPresent(lineNumber) {
diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueService.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueService.ts
index dcac8bf037b..4e491840f5a 100644
--- a/src/vs/workbench/contrib/audioCues/browser/audioCueService.ts
+++ b/src/vs/workbench/contrib/audioCues/browser/audioCueService.ts
@@ -9,9 +9,9 @@ import { FileAccess } from 'vs/base/common/network';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
-import { observableFromEvent, IObservable, LazyDerived } from 'vs/workbench/contrib/audioCues/browser/observable';
import { Event } from 'vs/base/common/event';
import { localize } from 'vs/nls';
+import { IObservable, observableFromEvent, derived } from 'vs/base/common/observable';
export const IAudioCueService = createDecorator<IAudioCueService>('audioCue');
@@ -29,7 +29,7 @@ export class AudioCueService extends Disposable implements IAudioCueService {
private readonly screenReaderAttached = observableFromEvent(
this.accessibilityService.onDidChangeScreenReaderOptimized,
- () => this.accessibilityService.isScreenReaderOptimized()
+ () => /** @description accessibilityService.onDidChangeScreenReaderOptimized */ this.accessibilityService.isScreenReaderOptimized()
);
constructor(
@@ -85,7 +85,7 @@ export class AudioCueService extends Disposable implements IAudioCueService {
Event.filter(this.configurationService.onDidChangeConfiguration, (e) =>
e.affectsConfiguration('audioCues.enabled')
),
- () => this.configurationService.getValue<'on' | 'off' | 'auto'>('audioCues.enabled')
+ () => /** @description config: audioCues.enabled */ this.configurationService.getValue<'on' | 'off' | 'auto'>('audioCues.enabled')
);
private readonly isEnabledCache = new Cache((cue: AudioCue) => {
@@ -95,7 +95,7 @@ export class AudioCueService extends Disposable implements IAudioCueService {
),
() => this.configurationService.getValue<'on' | 'off' | 'auto'>(cue.settingsKey)
);
- return new LazyDerived(reader => {
+ return derived('audio cue enabled', reader => {
const setting = settingObservable.read(reader);
if (
setting === 'on' ||
@@ -113,7 +113,7 @@ export class AudioCueService extends Disposable implements IAudioCueService {
}
return false;
- }, 'audio cue enabled');
+ });
});
public isEnabled(cue: AudioCue): IObservable<boolean> {
diff --git a/src/vs/workbench/contrib/audioCues/browser/observable.ts b/src/vs/workbench/contrib/audioCues/browser/observable.ts
deleted file mode 100644
index 2ad62412411..00000000000
--- a/src/vs/workbench/contrib/audioCues/browser/observable.ts
+++ /dev/null
@@ -1,690 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-import { Event } from 'vs/base/common/event';
-import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
-
-export interface IObservable<T, TChange = void> {
- _change: TChange;
-
- /**
- * Reads the current value.
- *
- * This causes a recomputation if needed.
- * Calling this method forces changes to propagate to observers during update operations.
- * Must not be called from {@link IObserver.handleChange}.
- */
- get(): T;
-
- /**
- * Registers an observer.
- *
- * Calls {@link IObserver.handleChange} immediately after a change is noticed.
- * Might happen while someone calls {@link IObservable.get} or {@link IObservable.read}.
- */
- subscribe(observer: IObserver): void;
- unsubscribe(observer: IObserver): void;
-
- /**
- * Calls {@link IObservable.get} and then {@link IReader.handleBeforeReadObservable}.
- */
- read(reader: IReader): T;
-
- map<TNew>(fn: (value: T) => TNew): IObservable<TNew>;
-}
-
-export interface IReader {
- /**
- * Reports an observable that was read.
- *
- * Is called by `Observable.read`.
- */
- handleBeforeReadObservable<T>(observable: IObservable<T, any>): void;
-}
-
-export interface IObserver {
- /**
- * Indicates that an update operation is about to begin.
- *
- * During an update, invariants might not hold for subscribed observables and
- * change events might be delayed.
- * However, all changes must be reported before all update operations are over.
- */
- beginUpdate<T>(observable: IObservable<T>): void;
-
- /**
- * Is called by a subscribed observable immediately after it notices a change.
- *
- * When {@link IObservable.get} returns and no change has been reported,
- * there has been no change for that observable.
- *
- * Implementations must not call into other observables!
- * The change should be processed when {@link IObserver.endUpdate} is called.
- */
- handleChange<T, TChange>(observable: IObservable<T, TChange>, change: TChange): void;
-
- /**
- * Indicates that an update operation has completed.
- */
- endUpdate<T>(observable: IObservable<T>): void;
-}
-
-export interface ISettable<T, TChange = void> {
- set(value: T, transaction: ITransaction | undefined, change: TChange): void;
-}
-
-export interface ITransaction {
- /**
- * Calls `Observer.beginUpdate` immediately
- * and `Observer.endUpdate` when the transaction is complete.
- */
- updateObserver(
- observer: IObserver,
- observable: IObservable<any, any>
- ): void;
-}
-
-// === Base ===
-export abstract class ConvenientObservable<T, TChange> implements IObservable<T, TChange> {
- get _change(): TChange { return null!; }
-
- public abstract get(): T;
- public abstract subscribe(observer: IObserver): void;
- public abstract unsubscribe(observer: IObserver): void;
-
- public read(reader: IReader): T {
- reader.handleBeforeReadObservable(this);
- return this.get();
- }
-
- public map<TNew>(fn: (value: T) => TNew): IObservable<TNew> {
- return new LazyDerived((reader) => fn(this.read(reader)), '(mapped)');
- }
-}
-
-export abstract class BaseObservable<T, TChange = void> extends ConvenientObservable<T, TChange> {
- protected readonly observers = new Set<IObserver>();
-
- public subscribe(observer: IObserver): void {
- const len = this.observers.size;
- this.observers.add(observer);
- if (len === 0) {
- this.onFirstObserverSubscribed();
- }
- }
-
- public unsubscribe(observer: IObserver): void {
- const deleted = this.observers.delete(observer);
- if (deleted && this.observers.size === 0) {
- this.onLastObserverUnsubscribed();
- }
- }
-
- protected onFirstObserverSubscribed(): void { }
- protected onLastObserverUnsubscribed(): void { }
-}
-
-export function transaction(fn: (tx: ITransaction) => void) {
- const tx = new TransactionImpl();
- try {
- fn(tx);
- } finally {
- tx.finish();
- }
-}
-
-class TransactionImpl implements ITransaction {
- private readonly finishActions = new Array<() => void>();
-
- public updateObserver(
- observer: IObserver,
- observable: IObservable<any>
- ): void {
- this.finishActions.push(function () {
- observer.endUpdate(observable);
- });
- observer.beginUpdate(observable);
- }
-
- public finish(): void {
- for (const action of this.finishActions) {
- action();
- }
- }
-}
-
-export class ObservableValue<T, TChange = void>
- extends BaseObservable<T, TChange>
- implements ISettable<T, TChange>
-{
- private value: T;
-
- constructor(initialValue: T, public readonly name: string) {
- super();
- this.value = initialValue;
- }
-
- public get(): T {
- return this.value;
- }
-
- public set(value: T, tx: ITransaction | undefined, change: TChange): void {
- if (this.value === value) {
- return;
- }
-
- if (!tx) {
- transaction((tx) => {
- this.set(value, tx, change);
- });
- return;
- }
-
- this.value = value;
-
- for (const observer of this.observers) {
- tx.updateObserver(observer, this);
- observer.handleChange(this, change);
- }
- }
-}
-
-export function constObservable<T>(value: T): IObservable<T> {
- return new ConstObservable(value);
-}
-
-class ConstObservable<T> extends ConvenientObservable<T, void> {
- constructor(private readonly value: T) {
- super();
- }
-
- public get(): T {
- return this.value;
- }
- public subscribe(observer: IObserver): void {
- // NO OP
- }
- public unsubscribe(observer: IObserver): void {
- // NO OP
- }
-}
-
-// == autorun ==
-export function autorun(fn: (reader: IReader) => void, name: string): IDisposable {
- return new AutorunObserver(fn, name, undefined);
-}
-
-interface IChangeContext {
- readonly changedObservable: IObservable<any, any>;
- readonly change: unknown;
-
- didChange<T, TChange>(observable: IObservable<T, TChange>): this is { change: TChange };
-}
-
-export function autorunHandleChanges(
- name: string,
- options: {
- /**
- * Returns if this change should cause a re-run of the autorun.
- */
- handleChange: (context: IChangeContext) => boolean;
- },
- fn: (reader: IReader) => void
-): IDisposable {
- return new AutorunObserver(fn, name, options.handleChange);
-}
-
-export function autorunWithStore(
- fn: (reader: IReader, store: DisposableStore) => void,
- name: string
-): IDisposable {
- const store = new DisposableStore();
- const disposable = autorun(
- reader => {
- store.clear();
- fn(reader, store);
- },
- name
- );
- return toDisposable(() => {
- disposable.dispose();
- store.dispose();
- });
-}
-
-export class AutorunObserver implements IObserver, IReader, IDisposable {
- public needsToRun = true;
- private updateCount = 0;
-
- /**
- * The actual dependencies.
- */
- private _dependencies = new Set<IObservable<any>>();
- public get dependencies() {
- return this._dependencies;
- }
-
- /**
- * Dependencies that have to be removed when {@link runFn} ran through.
- */
- private staleDependencies = new Set<IObservable<any>>();
-
- constructor(
- private readonly runFn: (reader: IReader) => void,
- public readonly name: string,
- private readonly _handleChange: ((context: IChangeContext) => boolean) | undefined
- ) {
- this.runIfNeeded();
- }
-
- public handleBeforeReadObservable<T>(observable: IObservable<T>) {
- this._dependencies.add(observable);
- if (!this.staleDependencies.delete(observable)) {
- observable.subscribe(this);
- }
- }
-
- public handleChange<T, TChange>(observable: IObservable<T, TChange>, change: TChange): void {
- const shouldReact = this._handleChange ? this._handleChange({
- changedObservable: observable,
- change,
- didChange: o => o === observable as any,
- }) : true;
- this.needsToRun = this.needsToRun || shouldReact;
-
- if (this.updateCount === 0) {
- this.runIfNeeded();
- }
- }
-
- public beginUpdate() {
- this.updateCount++;
- }
-
- public endUpdate() {
- this.updateCount--;
- if (this.updateCount === 0) {
- this.runIfNeeded();
- }
- }
-
- private runIfNeeded(): void {
- if (!this.needsToRun) {
- return;
- }
- // Assert: this.staleDependencies is an empty set.
- const emptySet = this.staleDependencies;
- this.staleDependencies = this._dependencies;
- this._dependencies = emptySet;
-
- this.needsToRun = false;
-
- try {
- this.runFn(this);
- } finally {
- // We don't want our observed observables to think that they are (not even temporarily) not being observed.
- // Thus, we only unsubscribe from observables that are definitely not read anymore.
- for (const o of this.staleDependencies) {
- o.unsubscribe(this);
- }
- this.staleDependencies.clear();
- }
- }
-
- public dispose() {
- for (const o of this._dependencies) {
- o.unsubscribe(this);
- }
- this._dependencies.clear();
- }
-}
-
-export namespace autorun {
- export const Observer = AutorunObserver;
-}
-export function autorunDelta<T>(
- name: string,
- observable: IObservable<T>,
- handler: (args: { lastValue: T | undefined; newValue: T }) => void
-): IDisposable {
- let _lastValue: T | undefined;
- return autorun((reader) => {
- const newValue = observable.read(reader);
- const lastValue = _lastValue;
- _lastValue = newValue;
- handler({ lastValue, newValue });
- }, name);
-}
-
-
-// == Lazy Derived ==
-
-export function derivedObservable<T>(name: string, computeFn: (reader: IReader) => T): IObservable<T> {
- return new LazyDerived(computeFn, name);
-}
-export class LazyDerived<T> extends ConvenientObservable<T, void> {
- private readonly observer: LazyDerivedObserver<T>;
-
- constructor(computeFn: (reader: IReader) => T, name: string) {
- super();
- this.observer = new LazyDerivedObserver(computeFn, name, this);
- }
-
- public subscribe(observer: IObserver): void {
- this.observer.subscribe(observer);
- }
-
- public unsubscribe(observer: IObserver): void {
- this.observer.unsubscribe(observer);
- }
-
- public override read(reader: IReader): T {
- return this.observer.read(reader);
- }
-
- public get(): T {
- return this.observer.get();
- }
-}
-
-/**
- * @internal
- */
-class LazyDerivedObserver<T>
- extends BaseObservable<T, void>
- implements IReader, IObserver {
- private hadValue = false;
- private hasValue = false;
- private value: T | undefined = undefined;
- private updateCount = 0;
-
- private _dependencies = new Set<IObservable<any>>();
- public get dependencies(): ReadonlySet<IObservable<any>> {
- return this._dependencies;
- }
-
- /**
- * Dependencies that have to be removed when {@link runFn} ran through.
- */
- private staleDependencies = new Set<IObservable<any>>();
-
- constructor(
- private readonly computeFn: (reader: IReader) => T,
- public readonly name: string,
- private readonly actualObservable: LazyDerived<T>,
- ) {
- super();
- }
-
- protected override onLastObserverUnsubscribed(): void {
- /**
- * We are not tracking changes anymore, thus we have to assume
- * that our cache is invalid.
- */
- this.hasValue = false;
- this.hadValue = false;
- this.value = undefined;
- for (const d of this._dependencies) {
- d.unsubscribe(this);
- }
- this._dependencies.clear();
- }
-
- public handleBeforeReadObservable<T>(observable: IObservable<T>) {
- this._dependencies.add(observable);
- if (!this.staleDependencies.delete(observable)) {
- observable.subscribe(this);
- }
- }
-
- public handleChange() {
- if (this.hasValue) {
- this.hadValue = true;
- this.hasValue = false;
- }
-
- // Not in transaction: Recompute & inform observers immediately
- if (this.updateCount === 0 && this.observers.size > 0) {
- this.get();
- }
-
- // Otherwise, recompute in `endUpdate` or on demand.
- }
-
- public beginUpdate() {
- if (this.updateCount === 0) {
- for (const r of this.observers) {
- r.beginUpdate(this);
- }
- }
- this.updateCount++;
- }
-
- public endUpdate() {
- this.updateCount--;
- if (this.updateCount === 0) {
- if (this.observers.size > 0) {
- // Propagate invalidation
- this.get();
- }
-
- for (const r of this.observers) {
- r.endUpdate(this);
- }
- }
- }
-
- public get(): T {
- if (this.observers.size === 0) {
- // Cache is not valid and don't refresh the cache.
- // Observables should not be read in non-reactive contexts.
- return this.computeFn(this);
- }
-
- if (this.updateCount > 0 && this.hasValue) {
- // Refresh dependencies
- for (const d of this._dependencies) {
- // Maybe `.get()` triggers `handleChange`?
- d.get();
- if (!this.hasValue) {
- // The other dependencies will refresh on demand
- break;
- }
- }
- }
-
- if (!this.hasValue) {
- const emptySet = this.staleDependencies;
- this.staleDependencies = this._dependencies;
- this._dependencies = emptySet;
-
- const oldValue = this.value;
- try {
- this.value = this.computeFn(this);
- } finally {
- // We don't want our observed observables to think that they are (not even temporarily) not being observed.
- // Thus, we only unsubscribe from observables that are definitely not read anymore.
- for (const o of this.staleDependencies) {
- o.unsubscribe(this);
- }
- this.staleDependencies.clear();
- }
-
- this.hasValue = true;
- if (this.hadValue && oldValue !== this.value) {
- for (const r of this.observers) {
- r.handleChange(this.actualObservable, undefined);
- }
- }
- }
- return this.value!;
- }
-}
-
-export namespace LazyDerived {
- export const Observer = LazyDerivedObserver;
-}
-
-export function observableFromPromise<T>(promise: Promise<T>): IObservable<{ value?: T }> {
- const observable = new ObservableValue<{ value?: T }>({}, 'promiseValue');
- promise.then((value) => {
- observable.set({ value }, undefined);
- });
- return observable;
-}
-
-export function waitForState<T, TState extends T>(observable: IObservable<T>, predicate: (state: T) => state is TState): Promise<TState>;
-export function waitForState<T>(observable: IObservable<T>, predicate: (state: T) => boolean): Promise<T>;
-export function waitForState<T>(observable: IObservable<T>, predicate: (state: T) => boolean): Promise<T> {
- return new Promise(resolve => {
- const d = autorun(reader => {
- const currentState = observable.read(reader);
- if (predicate(currentState)) {
- d.dispose();
- resolve(currentState);
- }
- }, 'waitForState');
- });
-}
-
-export function observableFromEvent<T, TArgs = unknown>(
- event: Event<TArgs>,
- getValue: (args: TArgs | undefined) => T
-): IObservable<T> {
- return new FromEventObservable(event, getValue);
-}
-
-class FromEventObservable<TArgs, T> extends BaseObservable<T> {
- private value: T | undefined;
- private hasValue = false;
- private subscription: IDisposable | undefined;
-
- constructor(
- private readonly event: Event<TArgs>,
- private readonly getValue: (args: TArgs | undefined) => T
- ) {
- super();
- }
-
- protected override onFirstObserverSubscribed(): void {
- this.subscription = this.event(this.handleEvent);
- }
-
- private readonly handleEvent = (args: TArgs | undefined) => {
- const newValue = this.getValue(args);
- if (this.value !== newValue) {
- this.value = newValue;
-
- if (this.hasValue) {
- transaction(tx => {
- for (const o of this.observers) {
- tx.updateObserver(o, this);
- o.handleChange(this, undefined);
- }
- });
- }
- this.hasValue = true;
- }
- };
-
- protected override onLastObserverUnsubscribed(): void {
- this.subscription!.dispose();
- this.subscription = undefined;
- this.hasValue = false;
- this.value = undefined;
- }
-
- public get(): T {
- if (this.subscription) {
- if (!this.hasValue) {
- this.handleEvent(undefined);
- }
- return this.value!;
- } else {
- // no cache, as there are no subscribers to clean it up
- return this.getValue(undefined);
- }
- }
-}
-
-export namespace observableFromEvent {
- export const Observer = FromEventObservable;
-}
-
-export function debouncedObservable<T>(observable: IObservable<T>, debounceMs: number, disposableStore: DisposableStore): IObservable<T | undefined> {
- const debouncedObservable = new ObservableValue<T | undefined>(undefined, 'debounced');
-
- let timeout: any = undefined;
-
- disposableStore.add(autorun(reader => {
- const value = observable.read(reader);
-
- if (timeout) {
- clearTimeout(timeout);
- }
- timeout = setTimeout(() => {
- transaction(tx => {
- debouncedObservable.set(value, tx);
- });
- }, debounceMs);
-
- }, 'debounce'));
-
- return debouncedObservable;
-}
-
-export function wasEventTriggeredRecently(event: Event<any>, timeoutMs: number, disposableStore: DisposableStore): IObservable<boolean> {
- const observable = new ObservableValue(false, 'triggeredRecently');
-
- let timeout: any = undefined;
-
- disposableStore.add(event(() => {
- observable.set(true, undefined);
-
- if (timeout) {
- clearTimeout(timeout);
- }
- timeout = setTimeout(() => {
- observable.set(false, undefined);
- }, timeoutMs);
- }));
-
- return observable;
-}
-
-/**
- * This ensures the observable is kept up-to-date.
- * This is useful when the observables `get` method is used.
-*/
-export function keepAlive(observable: IObservable<any>): IDisposable {
- return autorun(reader => {
- observable.read(reader);
- }, 'keep-alive');
-}
-
-export function derivedObservableWithCache<T>(name: string, computeFn: (reader: IReader, lastValue: T | undefined) => T): IObservable<T> {
- let lastValue: T | undefined = undefined;
- const observable = derivedObservable(name, reader => {
- lastValue = computeFn(reader, lastValue);
- return lastValue;
- });
- return observable;
-}
-
-export function derivedObservableWithWritableCache<T>(name: string, computeFn: (reader: IReader, lastValue: T | undefined) => T): IObservable<T> & { clearCache(transaction: ITransaction): void } {
- let lastValue: T | undefined = undefined;
- const counter = new ObservableValue(0, 'counter');
- const observable = derivedObservable(name, reader => {
- counter.read(reader);
- lastValue = computeFn(reader, lastValue);
- return lastValue;
- });
- return Object.assign(observable, {
- clearCache: (transaction: ITransaction) => {
- lastValue = undefined;
- counter.set(counter.get() + 1, transaction);
- },
- });
-}
diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts
index 1f533a82ccc..0e870b7b639 100644
--- a/src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts
+++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts
@@ -6,20 +6,36 @@
import { groupBy } from 'vs/base/common/arrays';
import { CancellationToken } from 'vs/base/common/cancellation';
import { compare } from 'vs/base/common/strings';
+import { isObject } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import { ResourceEdit } from 'vs/editor/browser/services/bulkEditService';
import { WorkspaceEditMetadata } from 'vs/editor/common/languages';
import { IProgress } from 'vs/platform/progress/common/progress';
import { UndoRedoGroup, UndoRedoSource } from 'vs/platform/undoRedo/common/undoRedo';
-import { ICellEditOperation } from 'vs/workbench/contrib/notebook/common/notebookCommon';
+import { ICellPartialMetadataEdit, ICellReplaceEdit, IDocumentMetadataEdit, IWorkspaceNotebookCellEdit } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService';
-export class ResourceNotebookCellEdit extends ResourceEdit {
+export class ResourceNotebookCellEdit extends ResourceEdit implements IWorkspaceNotebookCellEdit {
+
+ static is(candidate: any): candidate is IWorkspaceNotebookCellEdit {
+ if (candidate instanceof ResourceNotebookCellEdit) {
+ return true;
+ }
+ return URI.isUri((<IWorkspaceNotebookCellEdit>candidate).resource)
+ && isObject((<IWorkspaceNotebookCellEdit>candidate).cellEdit);
+ }
+
+ static lift(edit: IWorkspaceNotebookCellEdit): ResourceNotebookCellEdit {
+ if (edit instanceof ResourceNotebookCellEdit) {
+ return edit;
+ }
+ return new ResourceNotebookCellEdit(edit.resource, edit.cellEdit, edit.notebookVersionId, edit.metadata);
+ }
constructor(
readonly resource: URI,
- readonly cellEdit: ICellEditOperation,
- readonly versionId?: number,
+ readonly cellEdit: ICellPartialMetadataEdit | IDocumentMetadataEdit | ICellReplaceEdit,
+ readonly notebookVersionId: number | undefined = undefined,
metadata?: WorkspaceEditMetadata
) {
super(metadata);
@@ -49,7 +65,7 @@ export class BulkCellEdits {
const ref = await this._notebookModelService.resolve(first.resource);
// check state
- if (typeof first.versionId === 'number' && ref.object.notebook.versionId !== first.versionId) {
+ if (typeof first.notebookVersionId === 'number' && ref.object.notebook.versionId !== first.notebookVersionId) {
ref.dispose();
throw new Error(`Notebook '${first.resource}' has changed in the meantime`);
}
diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts
index f69e60201f2..6906a0b4fb7 100644
--- a/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts
+++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts
@@ -19,8 +19,9 @@ import { ResourceMap } from 'vs/base/common/map';
import { IModelService } from 'vs/editor/common/services/model';
import { ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService';
import { CancellationToken } from 'vs/base/common/cancellation';
-import { performSnippetEdits } from 'vs/editor/contrib/snippet/browser/snippetController2';
+import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2';
import { SnippetParser } from 'vs/editor/contrib/snippet/browser/snippetParser';
+import { ISnippetEdit } from 'vs/editor/contrib/snippet/browser/snippetSession';
type ValidationResult = { canApply: true } | { canApply: false; reason: URI };
@@ -115,7 +116,7 @@ class ModelEditTask implements IDisposable {
if (!edit.text) {
return edit;
}
- const text = new SnippetParser().parse(edit.text, false, false).toString();
+ const text = new SnippetParser().text(edit.text);
return { ...edit, insertAsSnippet: false, text };
}
}
@@ -141,14 +142,24 @@ class EditorEditTask extends ModelEditTask {
super.apply();
return;
}
- if (this._edits.length > 0) {
- const insertAsSnippet = this._edits.every(edit => edit.insertAsSnippet);
- if (insertAsSnippet) {
- // todo@jrieken what ABOUT EOL?
- performSnippetEdits(this._editor, this._edits.map(edit => ({ range: Range.lift(edit.range!), snippet: edit.text! })));
+ if (this._edits.length > 0) {
+ const snippetCtrl = SnippetController2.get(this._editor);
+ if (snippetCtrl && this._edits.some(edit => edit.insertAsSnippet)) {
+ // some edit is a snippet edit -> use snippet controller and ISnippetEdits
+ const snippetEdits: ISnippetEdit[] = [];
+ for (const edit of this._edits) {
+ if (edit.range && edit.text !== null) {
+ snippetEdits.push({
+ range: Range.lift(edit.range),
+ template: edit.insertAsSnippet ? edit.text : SnippetParser.escape(edit.text)
+ });
+ }
+ }
+ snippetCtrl.apply(snippetEdits);
} else {
+ // normal edit
this._edits = this._edits
.map(this._transformSnippetStringToInsertText, this) // mixed edits (snippet and normal) -> no snippet mode
.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range));
@@ -222,13 +233,13 @@ export class BulkTextEdits {
let makeMinimal = false;
if (this._editor?.getModel()?.uri.toString() === ref.object.textEditorModel.uri.toString()) {
task = new EditorEditTask(ref, this._editor);
- makeMinimal = true && false; // todo@jrieken HACK
+ makeMinimal = true;
} else {
task = new ModelEditTask(ref);
}
for (const edit of value) {
- if (makeMinimal) {
+ if (makeMinimal && !edit.textEdit.insertAsSnippet) {
const newEdits = await this._editorWorker.computeMoreMinimalEdits(edit.resource, [edit.textEdit]);
if (!newEdits) {
task.addEdit(edit);
diff --git a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts
index 922bf591bf7..5f44dc67e26 100644
--- a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts
+++ b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts
@@ -403,8 +403,7 @@ class DocumentSymbolsOutlineCreator implements IOutlineCreator<IEditorPane, Docu
readonly dispose: () => void;
constructor(
- @IOutlineService outlineService: IOutlineService,
- @IInstantiationService private readonly _instantiationService: IInstantiationService,
+ @IOutlineService outlineService: IOutlineService
) {
const reg = outlineService.registerOutlineCreator(this);
this.dispose = () => reg.dispose();
@@ -427,7 +426,7 @@ class DocumentSymbolsOutlineCreator implements IOutlineCreator<IEditorPane, Docu
return undefined;
}
const firstLoadBarrier = new Barrier();
- const result = this._instantiationService.createInstance(DocumentSymbolsOutline, editor, target, firstLoadBarrier);
+ const result = editor.invokeWithinContext(accessor => accessor.get(IInstantiationService).createInstance(DocumentSymbolsOutline, editor!, target, firstLoadBarrier));
await firstLoadBarrier.wait();
return result;
}
diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts
index d1d118f5c15..450c76ae8ad 100644
--- a/src/vs/workbench/contrib/comments/browser/commentNode.ts
+++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts
@@ -382,7 +382,7 @@ export class CommentNode<T extends IRange | ICellRange> extends Disposable {
private createCommentEditor(editContainer: HTMLElement): void {
const container = dom.append(editContainer, dom.$('.edit-textarea'));
- this._commentEditor = this.instantiationService.createInstance(SimpleCommentEditor, container, SimpleCommentEditor.getEditorOptions(), this.parentThread);
+ this._commentEditor = this.instantiationService.createInstance(SimpleCommentEditor, container, SimpleCommentEditor.getEditorOptions(this.configurationService), this.parentThread);
const resource = URI.parse(`comment:commentinput-${this.comment.uniqueIdInThread}-${Date.now()}.md`);
this._commentEditorModel = this.modelService.createModel('', this.languageService.createByFilepathOrFirstLine(resource), resource, false);
diff --git a/src/vs/workbench/contrib/comments/browser/commentReply.ts b/src/vs/workbench/contrib/comments/browser/commentReply.ts
index 0905db307c3..68eda2e6e39 100644
--- a/src/vs/workbench/contrib/comments/browser/commentReply.ts
+++ b/src/vs/workbench/contrib/comments/browser/commentReply.ts
@@ -17,6 +17,7 @@ import { ILanguageService } from 'vs/editor/common/languages/language';
import { ITextModel } from 'vs/editor/common/model';
import { IModelService } from 'vs/editor/common/services/model';
import * as nls from 'vs/nls';
+import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { editorForeground, resolveColorValue } from 'vs/platform/theme/common/colorRegistry';
@@ -58,11 +59,12 @@ export class CommentReply<T extends IRange | ICellRange> extends Disposable {
@ILanguageService private languageService: ILanguageService,
@IModelService private modelService: IModelService,
@IThemeService private themeService: IThemeService,
+ @IConfigurationService configurationService: IConfigurationService
) {
super();
this.form = dom.append(container, dom.$('.comment-form'));
- this.commentEditor = this._register(this._scopedInstatiationService.createInstance(SimpleCommentEditor, this.form, SimpleCommentEditor.getEditorOptions(), this._parentThread));
+ this.commentEditor = this._register(this._scopedInstatiationService.createInstance(SimpleCommentEditor, this.form, SimpleCommentEditor.getEditorOptions(configurationService), this._parentThread));
this.commentEditorIsEmpty = CommentContextKeys.commentIsEmpty.bindTo(this._contextKeyService);
this.commentEditorIsEmpty.set(!this._pendingComment);
@@ -216,8 +218,8 @@ export class CommentReply<T extends IRange | ICellRange> extends Disposable {
this._commentThreadDisposables.push(this._commentThread.onDidChangeInput(input => {
const thread = this._commentThread;
-
- if (thread.input && thread.input.uri !== commentEditor.getModel()!.uri) {
+ const model = commentEditor.getModel();
+ if (thread.input && model && (thread.input.uri !== model.uri)) {
return;
}
if (!input) {
diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts
index a0e155d0bdd..41ed64d6d55 100644
--- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts
+++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts
@@ -382,6 +382,7 @@ export class CommentController implements IEditorContribution {
this._commentingRangeDecorator.update(this.editor, []);
this._commentThreadRangeDecorator.update(this.editor, []);
dispose(this._commentWidgets);
+ this._commentWidgets = [];
}
}));
@@ -859,7 +860,7 @@ export class CommentController implements IEditorContribution {
}
const options = this.editor.getOptions();
- if (options.get(EditorOption.folding)) {
+ if (options.get(EditorOption.folding) && options.get(EditorOption.showFoldingControls) !== 'never') {
lineDecorationsWidth -= 16;
}
lineDecorationsWidth += 9;
diff --git a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts
index 3166fd47195..020ef7eb4d1 100644
--- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts
+++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts
@@ -188,9 +188,21 @@ export class CommentNodeRenderer implements IListRenderer<ITreeNode<CommentNode>
return renderedComment;
}
+ private getIcon(commentCount: number, threadState?: CommentThreadState): Codicon {
+ if (threadState === CommentThreadState.Unresolved) {
+ return Codicon.commentUnresolved;
+ } else if (commentCount === 1) {
+ return Codicon.comment;
+ } else {
+ return Codicon.commentDiscussion;
+ }
+ }
+
renderElement(node: ITreeNode<CommentNode>, index: number, templateData: ICommentThreadTemplateData, height: number | undefined): void {
const commentCount = node.element.replies.length + 1;
- templateData.threadMetadata.icon?.classList.add(...ThemeIcon.asClassNameArray((commentCount === 1) ? Codicon.comment : Codicon.commentDiscussion));
+ templateData.threadMetadata.icon.classList.remove(...Array.from(templateData.threadMetadata.icon.classList.values())
+ .filter(value => value.startsWith('codicon')));
+ templateData.threadMetadata.icon.classList.add(...ThemeIcon.asClassNameArray(this.getIcon(commentCount, node.element.threadState)));
if (node.element.threadState !== undefined) {
const color = this.getCommentThreadWidgetStateColor(node.element.threadState, this.themeService.getColorTheme());
templateData.threadMetadata.icon.style.setProperty(commentViewThreadStateColorVar, `${color}`);
diff --git a/src/vs/workbench/contrib/comments/browser/commentsView.ts b/src/vs/workbench/contrib/comments/browser/commentsView.ts
index d6883919fa6..4c38db0a895 100644
--- a/src/vs/workbench/contrib/comments/browser/commentsView.ts
+++ b/src/vs/workbench/contrib/comments/browser/commentsView.ts
@@ -234,7 +234,7 @@ export class CommentsPanel extends ViewPane {
const commentToReveal = element instanceof ResourceWithCommentThreads ? element.commentThreads[0].comment.uniqueIdInThread : element.comment.uniqueIdInThread;
if (threadToReveal && isCodeEditor(editor)) {
const controller = CommentController.get(editor);
- controller?.revealCommentThread(threadToReveal, commentToReveal, false);
+ controller?.revealCommentThread(threadToReveal, commentToReveal, true);
}
return true;
diff --git a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts
index 4a69955fd4a..c4ec1bbf5e3 100644
--- a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts
+++ b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts
@@ -24,6 +24,7 @@ import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/comme
import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys';
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
+import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
export const ctxCommentEditorFocused = new RawContextKey<boolean>('commentEditorFocused', false);
@@ -79,7 +80,7 @@ export class SimpleCommentEditor extends CodeEditorWidget {
return EditorExtensionsRegistry.getEditorActions();
}
- public static getEditorOptions(): IEditorOptions {
+ public static getEditorOptions(configurationService: IConfigurationService): IEditorOptions {
return {
wordWrap: 'on',
glyphMargin: false,
@@ -103,6 +104,7 @@ export class SimpleCommentEditor extends CodeEditorWidget {
minimap: {
enabled: false
},
+ autoClosingBrackets: configurationService.getValue('editor.autoClosingBrackets'),
quickSuggestions: false
};
}
diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts
index 2eb3d65a818..b97d040674f 100644
--- a/src/vs/workbench/contrib/debug/browser/callStackView.ts
+++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts
@@ -21,7 +21,7 @@ import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'
import { posix } from 'vs/base/common/path';
import { commonSuffixLength } from 'vs/base/common/strings';
import { localize } from 'vs/nls';
-import { Icon } from 'vs/platform/action/common/action';
+import { ICommandActionTitle, Icon } from 'vs/platform/action/common/action';
import { createAndFillInActionBarActions, createAndFillInContextMenuActions, MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { IMenuService, MenuId, MenuItemAction, MenuRegistry, registerAction2, SubmenuItemAction } from 'vs/platform/actions/common/actions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@@ -1120,7 +1120,7 @@ registerAction2(class Collapse extends ViewAction<CallStackView> {
}
});
-function registerCallStackInlineMenuItem(id: string, title: string, icon: Icon, when: ContextKeyExpression, order: number, precondition?: ContextKeyExpression): void {
+function registerCallStackInlineMenuItem(id: string, title: string | ICommandActionTitle, icon: Icon, when: ContextKeyExpression, order: number, precondition?: ContextKeyExpression): void {
MenuRegistry.appendMenuItem(MenuId.DebugCallStackContext, {
group: 'inline',
order,
diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts
index 11584519c47..54d0f0a0e7c 100644
--- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts
+++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts
@@ -20,7 +20,7 @@ import {
} from 'vs/workbench/contrib/debug/common/debug';
import { DebugToolBar } from 'vs/workbench/contrib/debug/browser/debugToolBar';
import { DebugService } from 'vs/workbench/contrib/debug/browser/debugService';
-import { ADD_CONFIGURATION_ID, TOGGLE_INLINE_BREAKPOINT_ID, COPY_STACK_TRACE_ID, RESTART_SESSION_ID, TERMINATE_THREAD_ID, STEP_OVER_ID, STEP_INTO_ID, STEP_OUT_ID, PAUSE_ID, DISCONNECT_ID, STOP_ID, RESTART_FRAME_ID, CONTINUE_ID, FOCUS_REPL_ID, JUMP_TO_CURSOR_ID, RESTART_LABEL, STEP_INTO_LABEL, STEP_OVER_LABEL, STEP_OUT_LABEL, PAUSE_LABEL, DISCONNECT_LABEL, STOP_LABEL, CONTINUE_LABEL, DEBUG_START_LABEL, DEBUG_START_COMMAND_ID, DEBUG_RUN_LABEL, DEBUG_RUN_COMMAND_ID, EDIT_EXPRESSION_COMMAND_ID, REMOVE_EXPRESSION_COMMAND_ID, SELECT_AND_START_ID, SELECT_AND_START_LABEL, SET_EXPRESSION_COMMAND_ID, DISCONNECT_AND_SUSPEND_ID, DISCONNECT_AND_SUSPEND_LABEL, NEXT_DEBUG_CONSOLE_ID, NEXT_DEBUG_CONSOLE_LABEL, PREV_DEBUG_CONSOLE_ID, PREV_DEBUG_CONSOLE_LABEL, OPEN_LOADED_SCRIPTS_LABEL, SHOW_LOADED_SCRIPTS_ID, DEBUG_QUICK_ACCESS_PREFIX, DEBUG_CONSOLE_QUICK_ACCESS_PREFIX, SELECT_DEBUG_CONSOLE_ID, SELECT_DEBUG_CONSOLE_LABEL, STEP_INTO_TARGET_LABEL, STEP_INTO_TARGET_ID } from 'vs/workbench/contrib/debug/browser/debugCommands';
+import { ADD_CONFIGURATION_ID, TOGGLE_INLINE_BREAKPOINT_ID, COPY_STACK_TRACE_ID, RESTART_SESSION_ID, TERMINATE_THREAD_ID, STEP_OVER_ID, STEP_INTO_ID, STEP_OUT_ID, PAUSE_ID, DISCONNECT_ID, STOP_ID, RESTART_FRAME_ID, CONTINUE_ID, FOCUS_REPL_ID, JUMP_TO_CURSOR_ID, RESTART_LABEL, STEP_INTO_LABEL, STEP_OVER_LABEL, STEP_OUT_LABEL, PAUSE_LABEL, DISCONNECT_LABEL, STOP_LABEL, CONTINUE_LABEL, DEBUG_START_LABEL, DEBUG_START_COMMAND_ID, DEBUG_RUN_LABEL, DEBUG_RUN_COMMAND_ID, EDIT_EXPRESSION_COMMAND_ID, REMOVE_EXPRESSION_COMMAND_ID, SELECT_AND_START_ID, SELECT_AND_START_LABEL, SET_EXPRESSION_COMMAND_ID, DISCONNECT_AND_SUSPEND_ID, DISCONNECT_AND_SUSPEND_LABEL, NEXT_DEBUG_CONSOLE_ID, NEXT_DEBUG_CONSOLE_LABEL, PREV_DEBUG_CONSOLE_ID, PREV_DEBUG_CONSOLE_LABEL, OPEN_LOADED_SCRIPTS_LABEL, SHOW_LOADED_SCRIPTS_ID, DEBUG_QUICK_ACCESS_PREFIX, DEBUG_CONSOLE_QUICK_ACCESS_PREFIX, SELECT_DEBUG_CONSOLE_ID, SELECT_DEBUG_CONSOLE_LABEL, STEP_INTO_TARGET_LABEL, STEP_INTO_TARGET_ID, CALLSTACK_TOP_ID, CALLSTACK_TOP_LABEL, CALLSTACK_BOTTOM_LABEL, CALLSTACK_UP_LABEL, CALLSTACK_BOTTOM_ID, CALLSTACK_UP_ID, CALLSTACK_DOWN_ID, CALLSTACK_DOWN_LABEL, DEBUG_COMMAND_CATEGORY } from 'vs/workbench/contrib/debug/browser/debugCommands';
import { StatusBarColorProvider } from 'vs/workbench/contrib/debug/browser/statusbarColorProvider';
import { IViewsRegistry, Extensions as ViewExtensions, IViewContainersRegistry, ViewContainerLocation, ViewContainer } from 'vs/workbench/common/views';
import { isMacintosh, isWeb } from 'vs/base/common/platform';
@@ -55,7 +55,7 @@ import { DisassemblyView, DisassemblyViewContribution } from 'vs/workbench/contr
import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor';
import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassemblyViewInput';
import { DebugLifecycle } from 'vs/workbench/contrib/debug/common/debugLifecycle';
-import { Icon } from 'vs/platform/action/common/action';
+import { ICommandActionTitle, Icon } from 'vs/platform/action/common/action';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { DebugConsoleQuickAccess } from 'vs/workbench/contrib/debug/browser/debugConsoleQuickAccess';
@@ -98,20 +98,21 @@ registerEditorContribution('editor.contrib.callStack', CallStackEditorContributi
registerEditorContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID, BreakpointEditorContribution);
registerEditorContribution(EDITOR_CONTRIBUTION_ID, DebugEditorContribution);
-const registerDebugCommandPaletteItem = (id: string, title: string, when?: ContextKeyExpression, precondition?: ContextKeyExpression) => {
+const registerDebugCommandPaletteItem = (id: string, title: ICommandActionTitle, when?: ContextKeyExpression, precondition?: ContextKeyExpression) => {
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
when: ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, when),
group: debugCategory,
command: {
id,
- title: `Debug: ${title}`,
+ title,
+ category: DEBUG_COMMAND_CATEGORY,
precondition
}
});
};
registerDebugCommandPaletteItem(RESTART_SESSION_ID, RESTART_LABEL);
-registerDebugCommandPaletteItem(TERMINATE_THREAD_ID, nls.localize('terminateThread', "Terminate Thread"), CONTEXT_IN_DEBUG_MODE);
+registerDebugCommandPaletteItem(TERMINATE_THREAD_ID, { value: nls.localize('terminateThread', "Terminate Thread"), original: 'Terminate Thread' }, CONTEXT_IN_DEBUG_MODE);
registerDebugCommandPaletteItem(STEP_OVER_ID, STEP_OVER_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
registerDebugCommandPaletteItem(STEP_INTO_ID, STEP_INTO_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
registerDebugCommandPaletteItem(STEP_INTO_TARGET_ID, STEP_INTO_TARGET_LABEL, CONTEXT_IN_DEBUG_MODE, ContextKeyExpr.and(CONTEXT_STEP_INTO_TARGETS_SUPPORTED, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')));
@@ -121,13 +122,13 @@ registerDebugCommandPaletteItem(DISCONNECT_ID, DISCONNECT_LABEL, CONTEXT_IN_DEBU
registerDebugCommandPaletteItem(DISCONNECT_AND_SUSPEND_ID, DISCONNECT_AND_SUSPEND_LABEL, CONTEXT_IN_DEBUG_MODE, ContextKeyExpr.or(CONTEXT_FOCUSED_SESSION_IS_ATTACH, ContextKeyExpr.and(CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED)));
registerDebugCommandPaletteItem(STOP_ID, STOP_LABEL, CONTEXT_IN_DEBUG_MODE, ContextKeyExpr.or(CONTEXT_FOCUSED_SESSION_IS_ATTACH.toNegated(), CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED));
registerDebugCommandPaletteItem(CONTINUE_ID, CONTINUE_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
-registerDebugCommandPaletteItem(FOCUS_REPL_ID, nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugFocusConsole' }, 'Focus on Debug Console View'));
-registerDebugCommandPaletteItem(JUMP_TO_CURSOR_ID, nls.localize('jumpToCursor', "Jump to Cursor"), CONTEXT_JUMP_TO_CURSOR_SUPPORTED);
-registerDebugCommandPaletteItem(JUMP_TO_CURSOR_ID, nls.localize('SetNextStatement', "Set Next Statement"), CONTEXT_JUMP_TO_CURSOR_SUPPORTED);
-registerDebugCommandPaletteItem(RunToCursorAction.ID, RunToCursorAction.LABEL, ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')));
-registerDebugCommandPaletteItem(SelectionToReplAction.ID, SelectionToReplAction.LABEL, ContextKeyExpr.and(EditorContextKeys.hasNonEmptySelection, CONTEXT_IN_DEBUG_MODE));
-registerDebugCommandPaletteItem(SelectionToWatchExpressionsAction.ID, SelectionToWatchExpressionsAction.LABEL, ContextKeyExpr.and(EditorContextKeys.hasNonEmptySelection, CONTEXT_IN_DEBUG_MODE));
-registerDebugCommandPaletteItem(TOGGLE_INLINE_BREAKPOINT_ID, nls.localize('inlineBreakpoint', "Inline Breakpoint"));
+registerDebugCommandPaletteItem(FOCUS_REPL_ID, { value: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugFocusConsole' }, 'Focus on Debug Console View'), original: 'Focus on Debug Console View' });
+registerDebugCommandPaletteItem(JUMP_TO_CURSOR_ID, { value: nls.localize('jumpToCursor', "Jump to Cursor"), original: 'Jump to Cursor' }, CONTEXT_JUMP_TO_CURSOR_SUPPORTED);
+registerDebugCommandPaletteItem(JUMP_TO_CURSOR_ID, { value: nls.localize('SetNextStatement', "Set Next Statement"), original: 'Set Next Statement' }, CONTEXT_JUMP_TO_CURSOR_SUPPORTED);
+registerDebugCommandPaletteItem(RunToCursorAction.ID, { value: RunToCursorAction.LABEL, original: 'Run to Cursor' }, ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')));
+registerDebugCommandPaletteItem(SelectionToReplAction.ID, { value: SelectionToReplAction.LABEL, original: 'Evaluate in Debug Console' }, ContextKeyExpr.and(EditorContextKeys.hasNonEmptySelection, CONTEXT_IN_DEBUG_MODE));
+registerDebugCommandPaletteItem(SelectionToWatchExpressionsAction.ID, { value: SelectionToWatchExpressionsAction.LABEL, original: 'Add to Watch' }, ContextKeyExpr.and(EditorContextKeys.hasNonEmptySelection, CONTEXT_IN_DEBUG_MODE));
+registerDebugCommandPaletteItem(TOGGLE_INLINE_BREAKPOINT_ID, { value: nls.localize('inlineBreakpoint', "Inline Breakpoint"), original: 'Inline Breakpoint' });
registerDebugCommandPaletteItem(DEBUG_START_COMMAND_ID, DEBUG_START_LABEL, ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE.notEqualsTo(getStateLabel(State.Initializing))));
registerDebugCommandPaletteItem(DEBUG_RUN_COMMAND_ID, DEBUG_RUN_LABEL, ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE.notEqualsTo(getStateLabel(State.Initializing))));
registerDebugCommandPaletteItem(SELECT_AND_START_ID, SELECT_AND_START_LABEL, ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE.notEqualsTo(getStateLabel(State.Initializing))));
@@ -135,10 +136,13 @@ registerDebugCommandPaletteItem(NEXT_DEBUG_CONSOLE_ID, NEXT_DEBUG_CONSOLE_LABEL)
registerDebugCommandPaletteItem(PREV_DEBUG_CONSOLE_ID, PREV_DEBUG_CONSOLE_LABEL);
registerDebugCommandPaletteItem(SHOW_LOADED_SCRIPTS_ID, OPEN_LOADED_SCRIPTS_LABEL, CONTEXT_IN_DEBUG_MODE);
registerDebugCommandPaletteItem(SELECT_DEBUG_CONSOLE_ID, SELECT_DEBUG_CONSOLE_LABEL);
-
+registerDebugCommandPaletteItem(CALLSTACK_TOP_ID, CALLSTACK_TOP_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
+registerDebugCommandPaletteItem(CALLSTACK_BOTTOM_ID, CALLSTACK_BOTTOM_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
+registerDebugCommandPaletteItem(CALLSTACK_UP_ID, CALLSTACK_UP_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
+registerDebugCommandPaletteItem(CALLSTACK_DOWN_ID, CALLSTACK_DOWN_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
// Debug callstack context menu
-const registerDebugViewMenuItem = (menuId: MenuId, id: string, title: string, order: number, when?: ContextKeyExpression, precondition?: ContextKeyExpression, group = 'navigation', icon?: Icon) => {
+const registerDebugViewMenuItem = (menuId: MenuId, id: string, title: string | ICommandActionTitle, order: number, when?: ContextKeyExpression, precondition?: ContextKeyExpression, group = 'navigation', icon?: Icon) => {
MenuRegistry.appendMenuItem(menuId, {
group,
when,
@@ -186,7 +190,7 @@ registerDebugViewMenuItem(MenuId.DebugWatchContext, REMOVE_WATCH_EXPRESSIONS_COM
// Touch Bar
if (isMacintosh) {
- const registerTouchBarEntry = (id: string, title: string, order: number, when: ContextKeyExpression | undefined, iconUri: URI) => {
+ const registerTouchBarEntry = (id: string, title: string | ICommandActionTitle, order: number, when: ContextKeyExpression | undefined, iconUri: URI) => {
MenuRegistry.appendMenuItem(MenuId.TouchBarContext, {
command: {
id,
diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts
index 49eb8197d7a..596bbd9c32d 100644
--- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts
+++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts
@@ -9,7 +9,7 @@ import { List } from 'vs/base/browser/ui/list/listWidget';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IListService } from 'vs/platform/list/browser/listService';
import { IDebugService, IEnablement, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_VARIABLES_FOCUSED, EDITOR_CONTRIBUTION_ID, IDebugEditorContribution, CONTEXT_IN_DEBUG_MODE, CONTEXT_EXPRESSION_SELECTED, IConfig, IStackFrame, IThread, IDebugSession, CONTEXT_DEBUG_STATE, IDebugConfiguration, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, REPL_VIEW_ID, CONTEXT_DEBUGGERS_AVAILABLE, State, getStateLabel, CONTEXT_BREAKPOINT_INPUT_FOCUSED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, VIEWLET_ID, CONTEXT_DISASSEMBLY_VIEW_FOCUS, CONTEXT_IN_DEBUG_REPL, CONTEXT_STEP_INTO_TARGETS_SUPPORTED } from 'vs/workbench/contrib/debug/common/debug';
-import { Expression, Variable, Breakpoint, FunctionBreakpoint, DataBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel';
+import { Expression, Variable, Breakpoint, FunctionBreakpoint, DataBreakpoint, Thread } from 'vs/workbench/contrib/debug/common/debugModel';
import { IExtensionsViewPaneContainer, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions';
import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
@@ -64,27 +64,36 @@ export const REMOVE_EXPRESSION_COMMAND_ID = 'debug.removeWatchExpression';
export const NEXT_DEBUG_CONSOLE_ID = 'workbench.action.debug.nextConsole';
export const PREV_DEBUG_CONSOLE_ID = 'workbench.action.debug.prevConsole';
export const SHOW_LOADED_SCRIPTS_ID = 'workbench.action.debug.showLoadedScripts';
-
-export const RESTART_LABEL = nls.localize('restartDebug', "Restart");
-export const STEP_OVER_LABEL = nls.localize('stepOverDebug', "Step Over");
-export const STEP_INTO_LABEL = nls.localize('stepIntoDebug', "Step Into");
-export const STEP_INTO_TARGET_LABEL = nls.localize('stepIntoTargetDebug', "Step Into Target");
-export const STEP_OUT_LABEL = nls.localize('stepOutDebug', "Step Out");
-export const PAUSE_LABEL = nls.localize('pauseDebug', "Pause");
-export const DISCONNECT_LABEL = nls.localize('disconnect', "Disconnect");
-export const DISCONNECT_AND_SUSPEND_LABEL = nls.localize('disconnectSuspend', "Disconnect and Suspend");
-export const STOP_LABEL = nls.localize('stop', "Stop");
-export const CONTINUE_LABEL = nls.localize('continueDebug', "Continue");
-export const FOCUS_SESSION_LABEL = nls.localize('focusSession', "Focus Session");
-export const SELECT_AND_START_LABEL = nls.localize('selectAndStartDebugging', "Select and Start Debugging");
+export const CALLSTACK_TOP_ID = 'workbench.action.debug.callStackTop';
+export const CALLSTACK_BOTTOM_ID = 'workbench.action.debug.callStackBottom';
+export const CALLSTACK_UP_ID = 'workbench.action.debug.callStackUp';
+export const CALLSTACK_DOWN_ID = 'workbench.action.debug.callStackDown';
+
+export const DEBUG_COMMAND_CATEGORY = 'Debug';
+export const RESTART_LABEL = { value: nls.localize('restartDebug', "Restart"), original: 'Restart' };
+export const STEP_OVER_LABEL = { value: nls.localize('stepOverDebug', "Step Over"), original: 'Step Over' };
+export const STEP_INTO_LABEL = { value: nls.localize('stepIntoDebug', "Step Into"), original: 'Step Into' };
+export const STEP_INTO_TARGET_LABEL = { value: nls.localize('stepIntoTargetDebug', "Step Into Target"), original: 'Step Into Target' };
+export const STEP_OUT_LABEL = { value: nls.localize('stepOutDebug', "Step Out"), original: 'Step Out' };
+export const PAUSE_LABEL = { value: nls.localize('pauseDebug', "Pause"), original: 'Pause' };
+export const DISCONNECT_LABEL = { value: nls.localize('disconnect', "Disconnect"), original: 'Disconnect' };
+export const DISCONNECT_AND_SUSPEND_LABEL = { value: nls.localize('disconnectSuspend', "Disconnect and Suspend"), original: 'Disconnect and Suspend' };
+export const STOP_LABEL = { value: nls.localize('stop', "Stop"), original: 'Stop' };
+export const CONTINUE_LABEL = { value: nls.localize('continueDebug', "Continue"), original: 'Continue' };
+export const FOCUS_SESSION_LABEL = { value: nls.localize('focusSession', "Focus Session"), original: 'Focus Session' };
+export const SELECT_AND_START_LABEL = { value: nls.localize('selectAndStartDebugging', "Select and Start Debugging"), original: 'Select and Start Debugging' };
export const DEBUG_CONFIGURE_LABEL = nls.localize('openLaunchJson', "Open '{0}'", 'launch.json');
-export const DEBUG_START_LABEL = nls.localize('startDebug', "Start Debugging");
-export const DEBUG_RUN_LABEL = nls.localize('startWithoutDebugging', "Start Without Debugging");
-export const NEXT_DEBUG_CONSOLE_LABEL = nls.localize('nextDebugConsole', "Focus Next Debug Console");
-export const PREV_DEBUG_CONSOLE_LABEL = nls.localize('prevDebugConsole', "Focus Previous Debug Console");
-export const OPEN_LOADED_SCRIPTS_LABEL = nls.localize('openLoadedScript', "Open Loaded Script...");
-
-export const SELECT_DEBUG_CONSOLE_LABEL = nls.localize('selectDebugConsole', "Select Debug Console");
+export const DEBUG_START_LABEL = { value: nls.localize('startDebug', "Start Debugging"), original: 'Start Debugging' };
+export const DEBUG_RUN_LABEL = { value: nls.localize('startWithoutDebugging', "Start Without Debugging"), original: 'Start Without Debugging' };
+export const NEXT_DEBUG_CONSOLE_LABEL = { value: nls.localize('nextDebugConsole', "Focus Next Debug Console"), original: 'Focus Next Debug Console' };
+export const PREV_DEBUG_CONSOLE_LABEL = { value: nls.localize('prevDebugConsole', "Focus Previous Debug Console"), original: 'Focus Previous Debug Console' };
+export const OPEN_LOADED_SCRIPTS_LABEL = { value: nls.localize('openLoadedScript', "Open Loaded Script..."), original: 'Open Loaded Script...' };
+export const CALLSTACK_TOP_LABEL = { value: nls.localize('callStackTop', "Navigate to Top of Call Stack"), original: 'Navigate to Top of Call Stack' };
+export const CALLSTACK_BOTTOM_LABEL = { value: nls.localize('callStackBottom', "Navigate to Bottom of Call Stack"), original: 'Navigate to Bottom of Call Stack' };
+export const CALLSTACK_UP_LABEL = { value: nls.localize('callStackUp', "Navigate Up Call Stack"), original: 'Navigate Up Call Stack' };
+export const CALLSTACK_DOWN_LABEL = { value: nls.localize('callStackDown', "Navigate Down Call Stack"), original: 'Navigate Down Call Stack' };
+
+export const SELECT_DEBUG_CONSOLE_LABEL = { value: nls.localize('selectDebugConsole', "Select Debug Console"), original: 'Select Debug Console' };
export const DEBUG_QUICK_ACCESS_PREFIX = 'debug ';
export const DEBUG_CONSOLE_QUICK_ACCESS_PREFIX = 'debug consoles ';
@@ -179,6 +188,103 @@ async function changeDebugConsoleFocus(accessor: ServicesAccessor, next: boolean
}
}
+async function navigateCallStack(debugService: IDebugService, down: boolean) {
+ const frame = debugService.getViewModel().focusedStackFrame;
+ if (frame) {
+
+ let callStack = frame.thread.getCallStack();
+ let index = callStack.findIndex(elem => elem.frameId === frame.frameId);
+ let nextVisibleFrame;
+ if (down) {
+ if (index >= callStack.length - 1) {
+ if ((<Thread>frame.thread).reachedEndOfCallStack) {
+ goToTopOfCallStack(debugService);
+ return;
+ } else {
+ await debugService.getModel().fetchCallstack(frame.thread, 20);
+ callStack = frame.thread.getCallStack();
+ index = callStack.findIndex(elem => elem.frameId === frame.frameId);
+ }
+ }
+ nextVisibleFrame = findNextVisibleFrame(true, callStack, index);
+ } else {
+ if (index <= 0) {
+ goToBottomOfCallStack(debugService);
+ return;
+ }
+ nextVisibleFrame = findNextVisibleFrame(false, callStack, index);
+ }
+
+ if (nextVisibleFrame) {
+ debugService.focusStackFrame(nextVisibleFrame);
+ }
+ }
+}
+
+async function goToBottomOfCallStack(debugService: IDebugService) {
+ const thread = debugService.getViewModel().focusedThread;
+ if (thread) {
+ await debugService.getModel().fetchCallstack(thread);
+ const callStack = thread.getCallStack();
+ if (callStack.length > 0) {
+ const nextVisibleFrame = findNextVisibleFrame(false, callStack, 0); // must consider the next frame up first, which will be the last frame
+ if (nextVisibleFrame) {
+ debugService.focusStackFrame(nextVisibleFrame);
+ }
+ }
+ }
+}
+
+function goToTopOfCallStack(debugService: IDebugService) {
+ const thread = debugService.getViewModel().focusedThread;
+
+ if (thread) {
+ debugService.focusStackFrame(thread.getTopStackFrame());
+ }
+}
+
+/**
+ * Finds next frame that is not skipped by SkipFiles. Skips frame at index and starts searching at next.
+ * Must satisfy `0 <= startIndex <= callStack - 1`
+ * @param down specifies whether to search downwards if the current file is skipped.
+ * @param callStack the call stack to search
+ * @param startIndex the index to start the search at
+ */
+function findNextVisibleFrame(down: boolean, callStack: readonly IStackFrame[], startIndex: number) {
+
+ if (startIndex >= callStack.length) {
+ startIndex = callStack.length - 1;
+ } else if (startIndex < 0) {
+ startIndex = 0;
+ }
+
+ let index = startIndex;
+
+ let currFrame;
+ do {
+ if (down) {
+ if (index === callStack.length - 1) {
+ index = 0;
+ } else {
+ index++;
+ }
+ } else {
+ if (index === 0) {
+ index = callStack.length - 1;
+ } else {
+ index--;
+ }
+ }
+
+ currFrame = callStack[index];
+ if (!(currFrame.source.presentationHint === 'deemphasize' || currFrame.presentationHint === 'deemphasize')) {
+ return currFrame;
+ }
+ } while (index !== startIndex); // end loop when we've just checked the start index, since that should be the last one checked
+
+ return undefined;
+}
+
// These commands are used in call stack context menu, call stack inline actions, command palette, debug toolbar, mac native touch bar
// When the command is exectued in the context of a thread(context menu on a thread, inline call stack action) we pass the thread id
// Otherwise when it is executed "globaly"(using the touch bar, debug toolbar, command palette) we do not pass any id and just take whatever is the focussed thread
@@ -260,6 +366,39 @@ CommandsRegistry.registerCommand({
}
});
+
+CommandsRegistry.registerCommand({
+ id: CALLSTACK_TOP_ID,
+ handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
+ const debugService = accessor.get(IDebugService);
+ goToTopOfCallStack(debugService);
+ }
+});
+
+CommandsRegistry.registerCommand({
+ id: CALLSTACK_BOTTOM_ID,
+ handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
+ const debugService = accessor.get(IDebugService);
+ await goToBottomOfCallStack(debugService);
+ }
+});
+
+CommandsRegistry.registerCommand({
+ id: CALLSTACK_UP_ID,
+ handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
+ const debugService = accessor.get(IDebugService);
+ navigateCallStack(debugService, false);
+ }
+});
+
+CommandsRegistry.registerCommand({
+ id: CALLSTACK_DOWN_ID,
+ handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
+ const debugService = accessor.get(IDebugService);
+ navigateCallStack(debugService, true);
+ }
+});
+
MenuRegistry.appendMenuItem(MenuId.EditorContext, {
command: {
id: JUMP_TO_CURSOR_ID,
@@ -584,7 +723,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
const { launch, name, getConfig } = debugService.getConfigurationManager().selectedConfiguration;
const config = await getConfig();
const configOrName = config ? Object.assign(deepClone(config), debugStartOptions?.config) : name;
- await debugService.startDebugging(launch, configOrName, { noDebug: debugStartOptions?.noDebug, startedByUser: true }, false);
+ await debugService.startDebugging(launch, configOrName, { noDebug: debugStartOptions?.noDebug, startedByUser: true, saveBeforeStart: false });
}
});
diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts
index b308716a8bb..2474922a5ad 100644
--- a/src/vs/workbench/contrib/debug/browser/debugService.ts
+++ b/src/vs/workbench/contrib/debug/browser/debugService.ts
@@ -312,7 +312,10 @@ export class DebugService implements IDebugService {
* main entry point
* properly manages compounds, checks for errors and handles the initializing state.
*/
- async startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, options?: IDebugSessionOptions, saveBeforeStart = !options?.parentSession): Promise<boolean> {
+ async startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, options?: IDebugSessionOptions): Promise<boolean> {
+
+ const saveBeforeStart = options?.saveBeforeStart ?? !options?.parentSession;
+
const message = options && options.noDebug ? nls.localize('runTrust', "Running executes build tasks and program code from your workspace.") : nls.localize('debugTrust', "Debugging executes build tasks and program code from your workspace.");
const trust = await this.workspaceTrustRequestService.requestWorkspaceTrust({ message });
if (!trust) {
@@ -701,7 +704,10 @@ export class DebugService implements IDebugService {
}
async restartSession(session: IDebugSession, restartData?: any): Promise<any> {
- await this.editorService.saveAll();
+ if (session.saveBeforeStart) {
+ await saveAllBeforeDebugStart(this.configurationService, this.editorService);
+ }
+
const isAutoRestart = !!restartData;
const runTasks: () => Promise<TaskRunResult> = async () => {
diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts
index 4a53a4e0c93..29a568d478b 100644
--- a/src/vs/workbench/contrib/debug/browser/debugSession.ts
+++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts
@@ -164,6 +164,10 @@ export class DebugSession implements IDebugSession {
return !!this._options.compact;
}
+ get saveBeforeStart(): boolean {
+ return this._options.saveBeforeStart ?? !this._options?.parentSession;
+ }
+
get compoundRoot(): DebugCompoundRoot | undefined {
return this._options.compoundRoot;
}
@@ -955,7 +959,7 @@ export class DebugSession implements IDebugSession {
if (thread) {
// Call fetch call stack twice, the first only return the top stack frame.
// Second retrieves the rest of the call stack. For performance reasons #25605
- const promises = this.model.fetchCallStack(<Thread>thread);
+ const promises = this.model.refreshTopOfCallstack(<Thread>thread);
const focus = async () => {
if (focusedThreadDoesNotExist || (!event.body.preserveFocusHint && thread.getCallStack().length)) {
const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame;
diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts
index 1f7614bfee7..3fd1c0238b8 100644
--- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts
+++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts
@@ -16,7 +16,7 @@ import { URI } from 'vs/base/common/uri';
import 'vs/css!./media/debugToolBar';
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { localize } from 'vs/nls';
-import { ICommandAction } from 'vs/platform/action/common/action';
+import { ICommandAction, ICommandActionTitle } from 'vs/platform/action/common/action';
import { DropdownWithPrimaryActionViewItem } from 'vs/platform/actions/browser/dropdownWithPrimaryActionViewItem';
import { createActionViewItem, createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { IMenu, IMenuService, MenuId, MenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions';
@@ -296,7 +296,7 @@ export function createDisconnectMenuItemAction(action: MenuItemAction, disposabl
// Debug toolbar
const debugViewTitleItems: IDisposable[] = [];
-const registerDebugToolBarItem = (id: string, title: string, order: number, icon?: { light?: URI; dark?: URI } | ThemeIcon, when?: ContextKeyExpression, precondition?: ContextKeyExpression, alt?: ICommandAction) => {
+const registerDebugToolBarItem = (id: string, title: string | ICommandActionTitle, order: number, icon?: { light?: URI; dark?: URI } | ThemeIcon, when?: ContextKeyExpression, precondition?: ContextKeyExpression, alt?: ICommandAction) => {
MenuRegistry.appendMenuItem(MenuId.DebugToolBar, {
group: 'navigation',
when,
diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts
index baac9206484..21dc92d475c 100644
--- a/src/vs/workbench/contrib/debug/common/debug.ts
+++ b/src/vs/workbench/contrib/debug/common/debug.ts
@@ -206,6 +206,7 @@ export interface IDebugSessionOptions {
simple?: boolean;
};
startedByUser?: boolean;
+ saveBeforeStart?: boolean;
}
export interface IDataBreakpointInfoResponse {
@@ -296,6 +297,7 @@ export interface IDebugSession extends ITreeElement {
readonly subId: string | undefined;
readonly compact: boolean;
readonly compoundRoot: DebugCompoundRoot | undefined;
+ readonly saveBeforeStart: boolean;
readonly name: string;
readonly isSimpleUI: boolean;
readonly autoExpandLazyVariables: boolean;
@@ -602,6 +604,8 @@ export interface IDebugModel extends ITreeElement {
onDidChangeBreakpoints: Event<IBreakpointsChangeEvent | undefined>;
onDidChangeCallStack: Event<void>;
onDidChangeWatchExpressions: Event<IExpression | undefined>;
+
+ fetchCallstack(thread: IThread, levels?: number): Promise<void>;
}
/**
@@ -1086,7 +1090,7 @@ export interface IDebugService {
* Returns true if the start debugging was successful. For compound launches, all configurations have to start successfully for it to return success.
* On errors the startDebugging will throw an error, however some error and cancelations are handled and in that case will simply return false.
*/
- startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, options?: IDebugSessionOptions, saveBeforeStart?: boolean): Promise<boolean>;
+ startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, options?: IDebugSessionOptions): Promise<boolean>;
/**
* Restarts a session or creates a new one if there is no active session.
diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts
index 4b20e069cd2..1f50eb36368 100644
--- a/src/vs/workbench/contrib/debug/common/debugModel.ts
+++ b/src/vs/workbench/contrib/debug/common/debugModel.ts
@@ -1244,7 +1244,31 @@ export class DebugModel implements IDebugModel {
}
}
- fetchCallStack(thread: Thread): { topCallStack: Promise<void>; wholeCallStack: Promise<void> } {
+ /**
+ * Update the call stack and notify the call stack view that changes have occurred.
+ */
+ async fetchCallstack(thread: IThread, levels?: number): Promise<void> {
+
+ if ((<Thread>thread).reachedEndOfCallStack) {
+ return;
+ }
+
+ const totalFrames = thread.stoppedDetails?.totalFrames;
+ const remainingFrames = (typeof totalFrames === 'number') ? (totalFrames - thread.getCallStack().length) : undefined;
+
+ if (!levels || (remainingFrames && levels > remainingFrames)) {
+ levels = remainingFrames;
+ }
+
+ if (levels && levels > 0) {
+ await (<Thread>thread).fetchCallStack(levels);
+ this._onDidChangeCallStack.fire();
+ }
+
+ return;
+ }
+
+ refreshTopOfCallstack(thread: Thread): { topCallStack: Promise<void>; wholeCallStack: Promise<void> } {
if (thread.session.capabilities.supportsDelayedStackTraceLoading) {
// For improved performance load the first stack frame and then load the rest async.
let topCallStack = Promise.resolve();
diff --git a/src/vs/workbench/contrib/debug/node/telemetryApp.ts b/src/vs/workbench/contrib/debug/node/telemetryApp.ts
index ab6d37993ca..601ff4f9b1e 100644
--- a/src/vs/workbench/contrib/debug/node/telemetryApp.ts
+++ b/src/vs/workbench/contrib/debug/node/telemetryApp.ts
@@ -4,10 +4,10 @@
*--------------------------------------------------------------------------------------------*/
import { Server } from 'vs/base/parts/ipc/node/ipc.cp';
-import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender';
import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetryIpc';
+import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender';
-const appender = new AppInsightsAppender(process.argv[2], JSON.parse(process.argv[3]), process.argv[4]);
+const appender = new OneDataSystemAppender(undefined, process.argv[2], JSON.parse(process.argv[3]), process.argv[4]);
process.once('exit', () => appender.flush());
const channel = new TelemetryAppenderChannel([appender]);
diff --git a/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts b/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts
index 1bebaa31adb..1596e346e31 100644
--- a/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts
+++ b/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts
@@ -190,6 +190,10 @@ export class MockSession implements IDebugSession {
return undefined;
}
+ get saveBeforeStart(): boolean {
+ return true;
+ }
+
get isSimpleUI(): boolean {
return false;
}
diff --git a/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts
index d075977cb45..12f620df779 100644
--- a/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts
+++ b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts
@@ -7,10 +7,10 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { Registry } from 'vs/platform/registry/common/platform';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
-import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
+import { Action2, IAction2Options, registerAction2 } from 'vs/platform/actions/common/actions';
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { localize } from 'vs/nls';
-import { ISessionSyncWorkbenchService, Change, ChangeType, Folder, EditSession, FileType, EDIT_SESSION_SYNC_TITLE, EditSessionSchemaVersion } from 'vs/workbench/services/sessionSync/common/sessionSync';
+import { IEditSessionsWorkbenchService, Change, ChangeType, Folder, EditSession, FileType, EDIT_SESSION_SYNC_CATEGORY, EditSessionSchemaVersion, IEditSessionsLogService } from 'vs/workbench/contrib/editSessions/common/editSessions';
import { ISCMRepository, ISCMService } from 'vs/workbench/contrib/scm/common/scm';
import { IFileService } from 'vs/platform/files/common/files';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
@@ -19,13 +19,12 @@ import { joinPath, relativePath } from 'vs/base/common/resources';
import { VSBuffer } from 'vs/base/common/buffer';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
-import { SessionSyncWorkbenchService } from 'vs/workbench/services/sessionSync/browser/sessionSyncWorkbenchService';
+import { EditSessionsWorkbenchService } from 'vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { UserDataSyncErrorCode, UserDataSyncStoreError } from 'vs/platform/userDataSync/common/userDataSync';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
-import { ILogService } from 'vs/platform/log/common/log';
import { IProductService } from 'vs/platform/product/common/productService';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
@@ -39,34 +38,33 @@ import { getVirtualWorkspaceLocation } from 'vs/platform/workspace/common/virtua
import { Schemas } from 'vs/base/common/network';
import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys';
import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
+import { EditSessionsLogService } from 'vs/workbench/contrib/editSessions/common/editSessionsLogService';
-registerSingleton(ISessionSyncWorkbenchService, SessionSyncWorkbenchService);
+registerSingleton(IEditSessionsLogService, EditSessionsLogService);
+registerSingleton(IEditSessionsWorkbenchService, EditSessionsWorkbenchService);
-const resumeLatestCommand = {
- id: 'workbench.experimental.editSessions.actions.resumeLatest',
- title: { value: localize('resume latest', "{0}: Resume Latest Edit Session", EDIT_SESSION_SYNC_TITLE), original: 'Edit Sessions' },
-};
-const storeCurrentCommand = {
- id: 'workbench.experimental.editSessions.actions.storeCurrent',
- title: { value: localize('store current', "{0}: Store Current Edit Session", EDIT_SESSION_SYNC_TITLE), original: 'Edit Sessions' },
-};
-const continueEditSessionCommand = {
+const continueEditSessionCommand: IAction2Options = {
id: '_workbench.experimental.editSessions.actions.continueEditSession',
title: { value: localize('continue edit session', "Continue Edit Session..."), original: 'Continue Edit Session...' },
+ category: EDIT_SESSION_SYNC_CATEGORY,
+ f1: true
};
-const openLocalFolderCommand = {
+const openLocalFolderCommand: IAction2Options = {
id: '_workbench.experimental.editSessions.actions.continueEditSession.openLocalFolder',
- title: localize('continue edit session in local folder', "Open In Local Folder"),
+ title: { value: localize('continue edit session in local folder', "Open In Local Folder"), original: 'Open In Local Folder' },
+ category: EDIT_SESSION_SYNC_CATEGORY,
+ precondition: IsWebContext
};
const queryParamName = 'editSessionId';
+const experimentalSettingName = 'workbench.experimental.editSessions.enabled';
-export class SessionSyncContribution extends Disposable implements IWorkbenchContribution {
+export class EditSessionsContribution extends Disposable implements IWorkbenchContribution {
private registered = false;
private continueEditSessionOptions: ContinueEditSessionItem[] = [];
constructor(
- @ISessionSyncWorkbenchService private readonly sessionSyncWorkbenchService: ISessionSyncWorkbenchService,
+ @IEditSessionsWorkbenchService private readonly editSessionsWorkbenchService: IEditSessionsWorkbenchService,
@IFileService private readonly fileService: IFileService,
@IProgressService private readonly progressService: IProgressService,
@IOpenerService private readonly openerService: IOpenerService,
@@ -74,7 +72,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
@ISCMService private readonly scmService: ISCMService,
@INotificationService private readonly notificationService: INotificationService,
@IDialogService private readonly dialogService: IDialogService,
- @ILogService private readonly logService: ILogService,
+ @IEditSessionsLogService private readonly logService: IEditSessionsLogService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IProductService private readonly productService: IProductService,
@IConfigurationService private configurationService: IConfigurationService,
@@ -87,11 +85,11 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
super();
if (this.environmentService.editSessionId !== undefined) {
- void this.applyEditSession(this.environmentService.editSessionId).finally(() => this.environmentService.editSessionId = undefined);
+ void this.resumeEditSession(this.environmentService.editSessionId).finally(() => this.environmentService.editSessionId = undefined);
}
this.configurationService.onDidChangeConfiguration((e) => {
- if (e.affectsConfiguration('workbench.experimental.editSessions.enabled')) {
+ if (e.affectsConfiguration(experimentalSettingName)) {
this.registerActions();
}
});
@@ -127,13 +125,14 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
}
private registerActions() {
- if (this.registered || this.configurationService.getValue('workbench.experimental.editSessions.enabled') !== true) {
+ if (this.registered || this.configurationService.getValue(experimentalSettingName) !== true) {
+ this.logService.info(`Skipping registering edit sessions actions as edit sessions are currently disabled. Set ${experimentalSettingName} to enable edit sessions.`);
return;
}
this.registerContinueEditSessionAction();
- this.registerApplyLatestEditSessionAction();
+ this.registerResumeLatestEditSessionAction();
this.registerStoreLatestEditSessionAction();
this.registerContinueInLocalFolderAction();
@@ -145,11 +144,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
const that = this;
this._register(registerAction2(class ContinueEditSessionAction extends Action2 {
constructor() {
- super({
- id: continueEditSessionCommand.id,
- title: continueEditSessionCommand.title,
- f1: true
- });
+ super(continueEditSessionCommand);
}
async run(accessor: ServicesAccessor, workspaceUri: URI | undefined): Promise<void> {
@@ -166,34 +161,33 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
query: uri.query.length > 0 ? (uri + `&${queryParamName}=${encodedRef}`) : `${queryParamName}=${encodedRef}`
});
} else {
- that.logService.warn(`Edit Sessions: Failed to store edit session when invoking ${continueEditSessionCommand.id}.`);
+ that.logService.warn(`Failed to store edit session when invoking ${continueEditSessionCommand.id}.`);
}
// Open the URI
- that.logService.info(`Edit Sessions: opening ${uri.toString()}`);
+ that.logService.info(`Opening ${uri.toString()}`);
await that.openerService.open(uri, { openExternal: true });
}
}));
}
- private registerApplyLatestEditSessionAction(): void {
+ private registerResumeLatestEditSessionAction(): void {
const that = this;
- this._register(registerAction2(class ApplyLatestEditSessionAction extends Action2 {
+ this._register(registerAction2(class ResumeLatestEditSessionAction extends Action2 {
constructor() {
super({
- id: resumeLatestCommand.id,
- title: resumeLatestCommand.title,
- menu: {
- id: MenuId.CommandPalette,
- }
+ id: 'workbench.experimental.editSessions.actions.resumeLatest',
+ title: { value: localize('resume latest.v2', "Resume Latest Edit Session"), original: 'Resume Latest Edit Session' },
+ category: EDIT_SESSION_SYNC_CATEGORY,
+ f1: true,
});
}
async run(accessor: ServicesAccessor): Promise<void> {
await that.progressService.withProgress({
location: ProgressLocation.Notification,
- title: localize('applying edit session', 'Applying edit session...')
- }, async () => await that.applyEditSession());
+ title: localize('resuming edit session', 'Resuming edit session...')
+ }, async () => await that.resumeEditSession());
}
}));
}
@@ -203,11 +197,10 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
this._register(registerAction2(class StoreLatestEditSessionAction extends Action2 {
constructor() {
super({
- id: storeCurrentCommand.id,
- title: storeCurrentCommand.title,
- menu: {
- id: MenuId.CommandPalette,
- }
+ id: 'workbench.experimental.editSessions.actions.storeCurrent',
+ title: { value: localize('store current.v2', "Store Current Edit Session"), original: 'Store Current Edit Session' },
+ category: EDIT_SESSION_SYNC_CATEGORY,
+ f1: true,
});
}
@@ -220,26 +213,24 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
}));
}
- async applyEditSession(ref?: string): Promise<void> {
- if (ref !== undefined) {
- this.logService.info(`Edit Sessions: Applying edit session with ref ${ref}.`);
- }
+ async resumeEditSession(ref?: string): Promise<void> {
+ this.logService.info(ref !== undefined ? `Resuming edit session with ref ${ref}...` : 'Resuming edit session...');
- const data = await this.sessionSyncWorkbenchService.read(ref);
+ const data = await this.editSessionsWorkbenchService.read(ref);
if (!data) {
if (ref === undefined) {
- this.notificationService.info(localize('no edit session', 'There are no edit sessions to apply.'));
+ this.notificationService.info(localize('no edit session', 'There are no edit sessions to resume.'));
} else {
- this.notificationService.warn(localize('no edit session content for ref', 'Could not apply edit session contents for ID {0}.', ref));
+ this.notificationService.warn(localize('no edit session content for ref', 'Could not resume edit session contents for ID {0}.', ref));
}
- this.logService.info(`Edit Sessions: Aborting applying edit session as no edit session content is available to be applied from ref ${ref}.`);
+ this.logService.info(`Aborting resuming edit session as no edit session content is available to be applied from ref ${ref}.`);
return;
}
const editSession = data.editSession;
ref = data.ref;
if (editSession.version > EditSessionSchemaVersion) {
- this.notificationService.error(localize('client too old', "Please upgrade to a newer version of {0} to apply this edit session.", this.productService.nameLong));
+ this.notificationService.error(localize('client too old', "Please upgrade to a newer version of {0} to resume this edit session.", this.productService.nameLong));
return;
}
@@ -250,7 +241,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
for (const folder of editSession.folders) {
const folderRoot = this.contextService.getWorkspace().folders.find((f) => f.name === folder.name);
if (!folderRoot) {
- this.logService.info(`Edit Sessions: Skipping applying ${folder.workingChanges.length} changes from edit session with ref ${ref} as no corresponding workspace folder named ${folder.name} is currently open.`);
+ this.logService.info(`Skipping applying ${folder.workingChanges.length} changes from edit session with ref ${ref} as no corresponding workspace folder named ${folder.name} is currently open.`);
continue;
}
@@ -273,9 +264,9 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
if (hasLocalUncommittedChanges) {
// TODO@joyceerhl Provide the option to diff files which would be overwritten by edit session contents
const result = await this.dialogService.confirm({
- message: localize('apply edit session warning', 'Applying your edit session may overwrite your existing uncommitted changes. Do you want to proceed?'),
+ message: localize('resume edit session warning', 'Resuming your edit session may overwrite your existing uncommitted changes. Do you want to proceed?'),
type: 'warning',
- title: EDIT_SESSION_SYNC_TITLE
+ title: EDIT_SESSION_SYNC_CATEGORY.value
});
if (!result.confirmed) {
return;
@@ -290,12 +281,12 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
}
}
- this.logService.info(`Edit Sessions: Deleting edit session with ref ${ref} after successfully applying it to current workspace...`);
- await this.sessionSyncWorkbenchService.delete(ref);
- this.logService.info(`Edit Sessions: Deleted edit session with ref ${ref}.`);
+ this.logService.info(`Deleting edit session with ref ${ref} after successfully applying it to current workspace...`);
+ await this.editSessionsWorkbenchService.delete(ref);
+ this.logService.info(`Deleted edit session with ref ${ref}.`);
} catch (ex) {
- this.logService.error('Edit Sessions: Failed to apply edit session, reason: ', (ex as Error).toString());
- this.notificationService.error(localize('apply failed', "Failed to apply your edit session."));
+ this.logService.error('Failed to resume edit session, reason: ', (ex as Error).toString());
+ this.notificationService.error(localize('resume failed', "Failed to resume your edit session."));
}
}
@@ -313,7 +304,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
for (const uri of trackedUris) {
const workspaceFolder = this.contextService.getWorkspaceFolder(uri);
if (!workspaceFolder) {
- this.logService.info(`Edit Sessions: Skipping working change ${uri.toString()} as no associated workspace folder was found.`);
+ this.logService.info(`Skipping working change ${uri.toString()} as no associated workspace folder was found.`);
continue;
}
@@ -342,7 +333,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
}
if (!hasEdits) {
- this.logService.info('Edit Sessions: Skipping storing edit session as there are no edits to store.');
+ this.logService.info('Skipping storing edit session as there are no edits to store.');
if (fromStoreCommand) {
this.notificationService.info(localize('no edits to store', 'Skipped storing edit session as there are no edits to store.'));
}
@@ -352,12 +343,12 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
const data: EditSession = { folders, version: 1 };
try {
- this.logService.info(`Edit Sessions: Storing edit session...`);
- const ref = await this.sessionSyncWorkbenchService.write(data);
- this.logService.info(`Edit Sessions: Stored edit session with ref ${ref}.`);
+ this.logService.info(`Storing edit session...`);
+ const ref = await this.editSessionsWorkbenchService.write(data);
+ this.logService.info(`Stored edit session with ref ${ref}.`);
return ref;
} catch (ex) {
- this.logService.error(`Edit Sessions: Failed to store edit session, reason: `, (ex as Error).toString());
+ this.logService.error(`Failed to store edit session, reason: `, (ex as Error).toString());
type UploadFailedEvent = { reason: string };
type UploadFailedClassification = {
@@ -369,11 +360,11 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
switch (ex.code) {
case UserDataSyncErrorCode.TooLarge:
// Uploading a payload can fail due to server size limits
- this.telemetryService.publicLog2<UploadFailedEvent, UploadFailedClassification>('sessionSync.upload.failed', { reason: 'TooLarge' });
+ this.telemetryService.publicLog2<UploadFailedEvent, UploadFailedClassification>('editSessions.upload.failed', { reason: 'TooLarge' });
this.notificationService.error(localize('payload too large', 'Your edit session exceeds the size limit and cannot be stored.'));
break;
default:
- this.telemetryService.publicLog2<UploadFailedEvent, UploadFailedClassification>('sessionSync.upload.failed', { reason: 'unknown' });
+ this.telemetryService.publicLog2<UploadFailedEvent, UploadFailedClassification>('editSessions.upload.failed', { reason: 'unknown' });
this.notificationService.error(localize('payload failed', 'Your edit session cannot be stored.'));
break;
}
@@ -398,11 +389,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
const that = this;
this._register(registerAction2(class ContinueInLocalFolderAction extends Action2 {
constructor() {
- super({
- id: openLocalFolderCommand.id,
- title: openLocalFolderCommand.title,
- precondition: IsWebContext
- });
+ super(openLocalFolderCommand);
}
async run(accessor: ServicesAccessor): Promise<URI | undefined> {
@@ -513,7 +500,7 @@ const continueEditSessionExtPoint = ExtensionsRegistry.registerExtensionPoint<IC
//#endregion
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
-workbenchRegistry.registerWorkbenchContribution(SessionSyncContribution, LifecyclePhase.Restored);
+workbenchRegistry.registerWorkbenchContribution(EditSessionsContribution, LifecyclePhase.Restored);
Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfiguration({
...workbenchConfigurationNodeBase,
diff --git a/src/vs/workbench/services/sessionSync/browser/sessionSyncWorkbenchService.ts b/src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts
index 2c0a8bb167e..b63b550ab35 100644
--- a/src/vs/workbench/services/sessionSync/browser/sessionSyncWorkbenchService.ts
+++ b/src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts
@@ -10,29 +10,30 @@ import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/act
import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
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 { IProductService } from 'vs/platform/product/common/productService';
import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
import { IRequestService } from 'vs/platform/request/common/request';
import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
-import { IAuthenticationProvider } from 'vs/platform/userDataSync/common/userDataSync';
+import { createSyncHeaders, IAuthenticationProvider } from 'vs/platform/userDataSync/common/userDataSync';
import { UserDataSyncStoreClient } from 'vs/platform/userDataSync/common/userDataSyncStoreService';
import { AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
-import { EDIT_SESSIONS_SIGNED_IN, EditSession, EDIT_SESSION_SYNC_TITLE, ISessionSyncWorkbenchService, EDIT_SESSIONS_SIGNED_IN_KEY } from 'vs/workbench/services/sessionSync/common/sessionSync';
+import { EDIT_SESSIONS_SIGNED_IN, EditSession, EDIT_SESSION_SYNC_CATEGORY, IEditSessionsWorkbenchService, EDIT_SESSIONS_SIGNED_IN_KEY, IEditSessionsLogService } from 'vs/workbench/contrib/editSessions/common/editSessions';
+import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
+import { generateUuid } from 'vs/base/common/uuid';
type ExistingSession = IQuickPickItem & { session: AuthenticationSession & { providerId: string } };
type AuthenticationProviderOption = IQuickPickItem & { provider: IAuthenticationProvider };
-export class SessionSyncWorkbenchService extends Disposable implements ISessionSyncWorkbenchService {
+export class EditSessionsWorkbenchService extends Disposable implements IEditSessionsWorkbenchService {
_serviceBrand = undefined;
- private serverConfiguration = this.productService['sessionSync.store'];
+ private serverConfiguration = this.productService['editSessions.store'];
private storeClient: UserDataSyncStoreClient | undefined;
#authenticationInfo: { sessionId: string; token: string; providerId: string } | undefined;
- private static CACHED_SESSION_STORAGE_KEY = 'editSessionSyncAccountPreference';
+ private static CACHED_SESSION_STORAGE_KEY = 'editSessionAccountPreference';
private initialized = false;
private readonly signedInContext: IContextKey<boolean>;
@@ -44,10 +45,11 @@ export class SessionSyncWorkbenchService extends Disposable implements ISessionS
@IAuthenticationService private readonly authenticationService: IAuthenticationService,
@IExtensionService private readonly extensionService: IExtensionService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
- @ILogService private readonly logService: ILogService,
+ @IEditSessionsLogService private readonly logService: IEditSessionsLogService,
@IProductService private readonly productService: IProductService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IRequestService private readonly requestService: IRequestService,
+ @IDialogService private readonly dialogService: IDialogService,
) {
super();
@@ -74,7 +76,7 @@ export class SessionSyncWorkbenchService extends Disposable implements ISessionS
throw new Error('Please sign in to store your edit session.');
}
- return this.storeClient!.write('editSessions', JSON.stringify(editSession), null);
+ return this.storeClient!.write('editSessions', JSON.stringify(editSession), null, createSyncHeaders(generateUuid()));
}
/**
@@ -90,11 +92,12 @@ export class SessionSyncWorkbenchService extends Disposable implements ISessionS
}
let content: string | undefined | null;
+ const headers = createSyncHeaders(generateUuid());
try {
if (ref !== undefined) {
- content = await this.storeClient?.resolveContent('editSessions', ref);
+ content = await this.storeClient?.resolveContent('editSessions', ref, headers);
} else {
- const result = await this.storeClient?.read('editSessions', null);
+ const result = await this.storeClient?.read('editSessions', null, headers);
content = result?.content;
ref = result?.ref;
}
@@ -144,7 +147,7 @@ export class SessionSyncWorkbenchService extends Disposable implements ISessionS
if (!this.storeClient) {
this.storeClient = new UserDataSyncStoreClient(URI.parse(this.serverConfiguration.url), this.productService, this.requestService, this.logService, this.environmentService, this.fileService, this.storageService);
this._register(this.storeClient.onTokenFailed(() => {
- this.logService.info('Edit Sessions: clearing edit sessions authentication preference because of successive token failures.');
+ this.logService.info('Clearing edit sessions authentication preference because of successive token failures.');
this.clearAuthenticationPreference();
}));
}
@@ -157,11 +160,11 @@ export class SessionSyncWorkbenchService extends Disposable implements ISessionS
// If the user signed in previously and the session is still available, reuse that without prompting the user again
const existingSessionId = this.existingSessionId;
if (existingSessionId) {
- this.logService.trace(`Edit Sessions: Searching for existing authentication session with ID ${existingSessionId}`);
+ this.logService.trace(`Searching for existing authentication session with ID ${existingSessionId}`);
const existing = await this.getExistingSession();
if (existing !== undefined) {
- this.logService.trace(`Edit Sessions: Found existing authentication session with ID ${existingSessionId}`);
- this.#authenticationInfo = { sessionId: existing.session.id, token: existing.session.accessToken, providerId: existing.session.providerId };
+ this.logService.trace(`Found existing authentication session with ID ${existingSessionId}`);
+ this.#authenticationInfo = { sessionId: existing.session.id, token: existing.session.idToken ?? existing.session.accessToken, providerId: existing.session.providerId };
this.storeClient.setAuthToken(this.#authenticationInfo.token, this.#authenticationInfo.providerId);
return true;
}
@@ -170,10 +173,10 @@ export class SessionSyncWorkbenchService extends Disposable implements ISessionS
// Ask the user to pick a preferred account
const session = await this.getAccountPreference();
if (session !== undefined) {
- this.#authenticationInfo = { sessionId: session.id, token: session.accessToken, providerId: session.providerId };
+ this.#authenticationInfo = { sessionId: session.id, token: session.idToken ?? session.accessToken, providerId: session.providerId };
this.storeClient.setAuthToken(this.#authenticationInfo.token, this.#authenticationInfo.providerId);
this.existingSessionId = session.id;
- this.logService.trace(`Edit Sessions: Saving authentication session preference for ID ${session.id}.`);
+ this.logService.trace(`Saving authentication session preference for ID ${session.id}.`);
return true;
}
@@ -287,14 +290,14 @@ export class SessionSyncWorkbenchService extends Disposable implements ISessionS
}
private get existingSessionId() {
- return this.storageService.get(SessionSyncWorkbenchService.CACHED_SESSION_STORAGE_KEY, StorageScope.APPLICATION);
+ return this.storageService.get(EditSessionsWorkbenchService.CACHED_SESSION_STORAGE_KEY, StorageScope.APPLICATION);
}
private set existingSessionId(sessionId: string | undefined) {
if (sessionId === undefined) {
- this.storageService.remove(SessionSyncWorkbenchService.CACHED_SESSION_STORAGE_KEY, StorageScope.APPLICATION);
+ this.storageService.remove(EditSessionsWorkbenchService.CACHED_SESSION_STORAGE_KEY, StorageScope.APPLICATION);
} else {
- this.storageService.store(SessionSyncWorkbenchService.CACHED_SESSION_STORAGE_KEY, sessionId, StorageScope.APPLICATION, StorageTarget.MACHINE);
+ this.storageService.store(EditSessionsWorkbenchService.CACHED_SESSION_STORAGE_KEY, sessionId, StorageScope.APPLICATION, StorageTarget.MACHINE);
}
}
@@ -304,14 +307,14 @@ export class SessionSyncWorkbenchService extends Disposable implements ISessionS
}
private async onDidChangeStorage(e: IStorageValueChangeEvent): Promise<void> {
- if (e.key === SessionSyncWorkbenchService.CACHED_SESSION_STORAGE_KEY
+ if (e.key === EditSessionsWorkbenchService.CACHED_SESSION_STORAGE_KEY
&& e.scope === StorageScope.APPLICATION
) {
const newSessionId = this.existingSessionId;
const previousSessionId = this.#authenticationInfo?.sessionId;
if (previousSessionId !== newSessionId) {
- this.logService.trace(`Edit Sessions: resetting authentication state because authentication session ID preference changed from ${previousSessionId} to ${newSessionId}.`);
+ this.logService.trace(`Resetting authentication state because authentication session ID preference changed from ${previousSessionId} to ${newSessionId}.`);
this.#authenticationInfo = undefined;
this.initialized = false;
}
@@ -336,8 +339,9 @@ export class SessionSyncWorkbenchService extends Disposable implements ISessionS
this._register(registerAction2(class ResetEditSessionAuthenticationAction extends Action2 {
constructor() {
super({
- id: 'workbench.sessionSync.actions.resetAuth',
- title: localize('reset auth', '{0}: Sign Out', EDIT_SESSION_SYNC_TITLE),
+ id: 'workbench.editSessions.actions.resetAuth',
+ title: localize('reset auth', 'Sign Out'),
+ category: EDIT_SESSION_SYNC_CATEGORY,
precondition: ContextKeyExpr.equals(EDIT_SESSIONS_SIGNED_IN_KEY, true),
menu: [{
id: MenuId.CommandPalette,
@@ -350,8 +354,19 @@ export class SessionSyncWorkbenchService extends Disposable implements ISessionS
});
}
- run() {
- that.clearAuthenticationPreference();
+ async run() {
+ const result = await that.dialogService.confirm({
+ type: 'info',
+ message: localize('sign out of edit sessions clear data prompt', 'Do you want to sign out of edit sessions?'),
+ checkbox: { label: localize('delete all edit sessions', 'Delete all stored edit sessions from the cloud.') },
+ primaryButton: localize('clear data confirm', 'Yes'),
+ });
+ if (result.confirmed) {
+ if (result.checkboxChecked) {
+ that.storeClient?.delete('editSessions', null);
+ }
+ that.clearAuthenticationPreference();
+ }
}
}));
}
diff --git a/src/vs/workbench/services/sessionSync/common/sessionSync.ts b/src/vs/workbench/contrib/editSessions/common/editSessions.ts
index 6eafdb65490..789976a50e5 100644
--- a/src/vs/workbench/services/sessionSync/common/sessionSync.ts
+++ b/src/vs/workbench/contrib/editSessions/common/editSessions.ts
@@ -4,13 +4,18 @@
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
+import { ILocalizedString } from 'vs/platform/action/common/action';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
+import { ILogService } from 'vs/platform/log/common/log';
-export const EDIT_SESSION_SYNC_TITLE = localize('session sync', 'Edit Sessions');
+export const EDIT_SESSION_SYNC_CATEGORY: ILocalizedString = {
+ original: 'Edit Sessions',
+ value: localize('session sync', 'Edit Sessions')
+};
-export const ISessionSyncWorkbenchService = createDecorator<ISessionSyncWorkbenchService>('ISessionSyncWorkbenchService');
-export interface ISessionSyncWorkbenchService {
+export const IEditSessionsWorkbenchService = createDecorator<IEditSessionsWorkbenchService>('IEditSessionsWorkbenchService');
+export interface IEditSessionsWorkbenchService {
_serviceBrand: undefined;
read(ref: string | undefined): Promise<{ ref: string; editSession: EditSession } | undefined>;
@@ -18,6 +23,9 @@ export interface ISessionSyncWorkbenchService {
delete(ref: string): Promise<void>;
}
+export const IEditSessionsLogService = createDecorator<IEditSessionsLogService>('IEditSessionsLogService');
+export interface IEditSessionsLogService extends ILogService { }
+
export enum ChangeType {
Addition = 1,
Deletion = 2,
diff --git a/src/vs/workbench/contrib/editSessions/common/editSessionsLogService.ts b/src/vs/workbench/contrib/editSessions/common/editSessionsLogService.ts
new file mode 100644
index 00000000000..a18c9e29304
--- /dev/null
+++ b/src/vs/workbench/contrib/editSessions/common/editSessionsLogService.ts
@@ -0,0 +1,50 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { IEnvironmentService } from 'vs/platform/environment/common/environment';
+import { AbstractLogger, ILogger, ILoggerService } from 'vs/platform/log/common/log';
+import { IEditSessionsLogService } from 'vs/workbench/contrib/editSessions/common/editSessions';
+
+export class EditSessionsLogService extends AbstractLogger implements IEditSessionsLogService {
+
+ declare readonly _serviceBrand: undefined;
+ private readonly logger: ILogger;
+
+ constructor(
+ @ILoggerService loggerService: ILoggerService,
+ @IEnvironmentService environmentService: IEnvironmentService
+ ) {
+ super();
+ this.logger = this._register(loggerService.createLogger(environmentService.editSessionsLogResource, { name: 'editsessions' }));
+ }
+
+ trace(message: string, ...args: any[]): void {
+ this.logger.trace(message, ...args);
+ }
+
+ debug(message: string, ...args: any[]): void {
+ this.logger.debug(message, ...args);
+ }
+
+ info(message: string, ...args: any[]): void {
+ this.logger.info(message, ...args);
+ }
+
+ warn(message: string, ...args: any[]): void {
+ this.logger.warn(message, ...args);
+ }
+
+ error(message: string | Error, ...args: any[]): void {
+ this.logger.error(message, ...args);
+ }
+
+ critical(message: string | Error, ...args: any[]): void {
+ this.logger.critical(message, ...args);
+ }
+
+ flush(): void {
+ this.logger.flush();
+ }
+}
diff --git a/src/vs/workbench/contrib/sessionSync/test/browser/sessionSync.test.ts b/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts
index 5c29394116b..07917f285e5 100644
--- a/src/vs/workbench/contrib/sessionSync/test/browser/sessionSync.test.ts
+++ b/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts
@@ -9,8 +9,8 @@ import { FileService } from 'vs/platform/files/common/fileService';
import { Schemas } from 'vs/base/common/network';
import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
-import { NullLogService, ILogService } from 'vs/platform/log/common/log';
-import { SessionSyncContribution } from 'vs/workbench/contrib/sessionSync/browser/sessionSync.contribution';
+import { NullLogService } from 'vs/platform/log/common/log';
+import { EditSessionsContribution } from 'vs/workbench/contrib/editSessions/browser/editSessions.contribution';
import { ProgressService } from 'vs/workbench/services/progress/browser/progressService';
import { IProgressService } from 'vs/platform/progress/common/progress';
import { ISCMService } from 'vs/workbench/contrib/scm/common/scm';
@@ -21,7 +21,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace
import { mock } from 'vs/base/test/common/mock';
import * as sinon from 'sinon';
import * as assert from 'assert';
-import { ChangeType, FileType, ISessionSyncWorkbenchService } from 'vs/workbench/services/sessionSync/common/sessionSync';
+import { ChangeType, FileType, IEditSessionsLogService, IEditSessionsWorkbenchService } from 'vs/workbench/contrib/editSessions/common/editSessions';
import { URI } from 'vs/base/common/uri';
import { joinPath } from 'vs/base/common/resources';
import { INotificationService } from 'vs/platform/notification/common/notification';
@@ -34,7 +34,7 @@ const folderUri = URI.file(`/${folderName}`);
suite('Edit session sync', () => {
let instantiationService: TestInstantiationService;
- let sessionSyncContribution: SessionSyncContribution;
+ let editSessionsContribution: EditSessionsContribution;
let fileService: FileService;
let sandbox: sinon.SinonSandbox;
@@ -52,14 +52,14 @@ suite('Edit session sync', () => {
fileService.registerProvider(Schemas.file, fileSystemProvider);
// Stub out all services
- instantiationService.stub(ILogService, logService);
+ instantiationService.stub(IEditSessionsLogService, logService);
instantiationService.stub(IFileService, fileService);
instantiationService.stub(INotificationService, new TestNotificationService());
- instantiationService.stub(ISessionSyncWorkbenchService, new class extends mock<ISessionSyncWorkbenchService>() { });
+ instantiationService.stub(IEditSessionsWorkbenchService, new class extends mock<IEditSessionsWorkbenchService>() { });
instantiationService.stub(IProgressService, ProgressService);
instantiationService.stub(ISCMService, SCMService);
instantiationService.stub(IEnvironmentService, TestEnvironmentService);
- instantiationService.stub(IConfigurationService, new TestConfigurationService({ workbench: { experimental: { sessionSync: { enabled: true } } } }));
+ instantiationService.stub(IConfigurationService, new TestConfigurationService({ workbench: { experimental: { editSessions: { enabled: true } } } }));
instantiationService.stub(IWorkspaceContextService, new class extends mock<IWorkspaceContextService>() {
override getWorkspace() {
return {
@@ -77,7 +77,7 @@ suite('Edit session sync', () => {
// Stub repositories
instantiationService.stub(ISCMService, '_repositories', new Map());
- sessionSyncContribution = instantiationService.createInstance(SessionSyncContribution);
+ editSessionsContribution = instantiationService.createInstance(EditSessionsContribution);
});
teardown(() => {
@@ -107,13 +107,13 @@ suite('Edit session sync', () => {
// Stub sync service to return edit session data
const readStub = sandbox.stub().returns({ editSession, ref: '0' });
- instantiationService.stub(ISessionSyncWorkbenchService, 'read', readStub);
+ instantiationService.stub(IEditSessionsWorkbenchService, 'read', readStub);
// Create root folder
await fileService.createFolder(folderUri);
- // Apply edit session
- await sessionSyncContribution.applyEditSession();
+ // Resume edit session
+ await editSessionsContribution.resumeEditSession();
// Verify edit session was correctly applied
assert.equal((await fileService.readFile(fileUri)).value.toString(), fileContents);
@@ -121,12 +121,12 @@ suite('Edit session sync', () => {
test('Edit session not stored if there are no edits', async function () {
const writeStub = sandbox.stub();
- instantiationService.stub(ISessionSyncWorkbenchService, 'write', writeStub);
+ instantiationService.stub(IEditSessionsWorkbenchService, 'write', writeStub);
// Create root folder
await fileService.createFolder(folderUri);
- await sessionSyncContribution.storeEditSession(true);
+ await editSessionsContribution.storeEditSession(true);
// Verify that we did not attempt to write the edit session
assert.equal(writeStub.called, false);
diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts
index 381096c914f..c8632a19ecb 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts
@@ -29,7 +29,7 @@ import {
UpdateAction, ReloadAction, EnableDropDownAction, DisableDropDownAction, ExtensionStatusLabelAction, SetFileIconThemeAction, SetColorThemeAction,
RemoteInstallAction, ExtensionStatusAction, LocalInstallAction, ToggleSyncExtensionAction, SetProductIconThemeAction,
ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, UninstallAction, ExtensionActionWithDropdownActionViewItem, ExtensionDropDownAction,
- InstallAnotherVersionAction, ExtensionEditorManageExtensionAction, WebInstallAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction, MigrateDeprecatedExtensionAction
+ InstallAnotherVersionAction, ExtensionEditorManageExtensionAction, WebInstallAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction, MigrateDeprecatedExtensionAction, SetLanguageAction
} from 'vs/workbench/contrib/extensions/browser/extensionsActions';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
@@ -329,6 +329,7 @@ export class ExtensionEditor extends EditorPane {
this.instantiationService.createInstance(EnableDropDownAction),
this.instantiationService.createInstance(DisableDropDownAction),
+ this.instantiationService.createInstance(SetLanguageAction),
this.instantiationService.createInstance(RemoteInstallAction, false),
this.instantiationService.createInstance(LocalInstallAction),
this.instantiationService.createInstance(WebInstallAction),
diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts
index f64e267746c..f53f6a7de8a 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts
@@ -77,6 +77,8 @@ import { UnsupportedExtensionsMigrationContrib } from 'vs/workbench/contrib/exte
import { isWeb } from 'vs/base/common/platform';
import { ExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage';
import { IStorageService } from 'vs/platform/storage/common/storage';
+import product from 'vs/platform/product/common/product';
+import { IStringDictionary } from 'vs/base/common/collections';
// Singletons
registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService);
@@ -228,7 +230,7 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
'extensions.experimental.useUtilityProcess': {
type: 'boolean',
description: localize('extensionsUseUtilityProcess', "When enabled, the extension host will be launched using the new UtilityProcess Electron API."),
- default: false
+ default: product.quality === 'stable' ? false : true // disabled by default in stable for now
},
[WORKSPACE_TRUST_EXTENSION_SUPPORT]: {
type: 'object',
@@ -315,13 +317,17 @@ CommandsRegistry.registerCommand({
'type': 'boolean',
'description': localize('workbench.extensions.installExtension.option.donotSync', "When enabled, VS Code do not sync this extension when Settings Sync is on."),
default: false
+ },
+ 'context': {
+ 'type': 'object',
+ 'description': localize('workbench.extensions.installExtension.option.context', "Context for the installation. This is a JSON object that can be used to pass any information to the installation handlers. i.e. `{skipWalkthrough: true}` will skip opening the walkthrough upon install."),
}
}
}
}
]
},
- handler: async (accessor, arg: string | UriComponents, options?: { installOnlyNewlyAddedFromExtensionPackVSIX?: boolean; installPreReleaseVersion?: boolean; donotSync?: boolean }) => {
+ handler: async (accessor, arg: string | UriComponents, options?: { installOnlyNewlyAddedFromExtensionPackVSIX?: boolean; installPreReleaseVersion?: boolean; donotSync?: boolean; context?: IStringDictionary<any> }) => {
const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService);
try {
if (typeof arg === 'string') {
@@ -331,7 +337,8 @@ CommandsRegistry.registerCommand({
const installOptions: InstallOptions = {
isMachineScoped: options?.donotSync ? true : undefined, /* do not allow syncing extensions automatically while installing through the command */
installPreReleaseVersion: options?.installPreReleaseVersion,
- installGivenVersion: !!version
+ installGivenVersion: !!version,
+ context: options?.context
};
if (version) {
await extensionsWorkbenchService.installVersion(extension, version, installOptions);
@@ -445,7 +452,7 @@ async function runAction(action: IAction): Promise<void> {
}
interface IExtensionActionOptions extends IAction2Options {
- menuTitles?: { [id: number]: string };
+ menuTitles?: { [id: string]: string };
run(accessor: ServicesAccessor, ...args: any[]): Promise<any>;
}
diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts
index 710e811a4f7..23ddb1a771c 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts
@@ -14,7 +14,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
import { dispose } from 'vs/base/common/lifecycle';
import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, IExtensionContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, THEME_ACTIONS_GROUP, INSTALL_ACTIONS_GROUP } from 'vs/workbench/contrib/extensions/common/extensions';
import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate';
-import { IGalleryExtension, IExtensionGalleryService, ILocalExtension, InstallOptions, InstallOperation, TargetPlatformToString, ExtensionManagementErrorCode } from 'vs/platform/extensionManagement/common/extensionManagement';
+import { IGalleryExtension, IExtensionGalleryService, ILocalExtension, InstallOptions, InstallOperation, TargetPlatformToString, ExtensionManagementErrorCode, isTargetPlatformCompatible } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { ExtensionRecommendationReason, IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
import { areSameExtensions, getExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
@@ -56,7 +56,7 @@ import { IContextMenuProvider } from 'vs/base/browser/contextmenu';
import { ILogService } from 'vs/platform/log/common/log';
import * as Constants from 'vs/workbench/contrib/logs/common/logConstants';
import { errorIcon, infoIcon, manageExtensionIcon, preReleaseIcon, syncEnabledIcon, syncIgnoredIcon, trustIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons';
-import { isIOS, isWeb } from 'vs/base/common/platform';
+import { isIOS, isWeb, language } from 'vs/base/common/platform';
import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService';
import { IWorkspaceTrustEnablementService, IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust';
import { isVirtualWorkspace } from 'vs/platform/workspace/common/virtualWorkspace';
@@ -66,6 +66,7 @@ import { ViewContainerLocation } from 'vs/workbench/common/views';
import { flatten } from 'vs/base/common/arrays';
import { fromNow } from 'vs/base/common/date';
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
+import { ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks';
export class PromptExtensionInstallFailureAction extends Action {
@@ -264,11 +265,18 @@ export abstract class AbstractInstallAction extends ExtensionAction {
protected async computeAndUpdateEnablement(): Promise<void> {
this.enabled = false;
- if (this.extension && !this.extension.isBuiltin) {
- if (this.extension.state === ExtensionState.Uninstalled && await this.extensionsWorkbenchService.canInstall(this.extension)) {
- this.enabled = this.installPreReleaseVersion ? this.extension.hasPreReleaseVersion : this.extension.hasReleaseVersion;
- this.updateLabel();
- }
+ if (!this.extension) {
+ return;
+ }
+ if (this.extension.isBuiltin) {
+ return;
+ }
+ if (this.extensionsWorkbenchService.canSetLanguage(this.extension)) {
+ return;
+ }
+ if (this.extension.state === ExtensionState.Uninstalled && await this.extensionsWorkbenchService.canInstall(this.extension)) {
+ this.enabled = this.installPreReleaseVersion ? this.extension.hasPreReleaseVersion : this.extension.hasReleaseVersion;
+ this.updateLabel();
}
}
@@ -561,6 +569,7 @@ export abstract class InstallInOtherServerAction extends ExtensionAction {
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
@IExtensionManagementServerService protected readonly extensionManagementServerService: IExtensionManagementServerService,
@IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService,
+ @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
) {
super(id, InstallInOtherServerAction.INSTALL_LABEL, InstallInOtherServerAction.Class, false);
this.update();
@@ -635,19 +644,29 @@ export abstract class InstallInOtherServerAction extends ExtensionAction {
}
override async run(): Promise<void> {
- if (!this.extension) {
+ if (!this.extension?.local) {
return;
}
- if (this.server) {
- this.extensionsWorkbenchService.open(this.extension);
- alert(localize('installExtensionStart', "Installing extension {0} started. An editor is now open with more details on this extension", this.extension.displayName));
- if (this.extension.gallery) {
- await this.server.extensionManagementService.installFromGallery(this.extension.gallery, { installPreReleaseVersion: this.extension.local?.preRelease });
- } else {
- const vsix = await this.extension.server!.extensionManagementService.zip(this.extension.local!);
- await this.server.extensionManagementService.install(vsix);
- }
+ if (!this.extension?.server) {
+ return;
+ }
+ if (!this.server) {
+ return;
+ }
+ this.extensionsWorkbenchService.open(this.extension);
+ alert(localize('installExtensionStart', "Installing extension {0} started. An editor is now open with more details on this extension", this.extension.displayName));
+
+ const gallery = this.extension.gallery ?? (this.extensionGalleryService.isEnabled() && (await this.extensionGalleryService.getExtensions([this.extension.identifier], CancellationToken.None))[0]);
+ if (gallery) {
+ await this.server.extensionManagementService.installFromGallery(gallery, { installPreReleaseVersion: this.extension.local.preRelease });
+ return;
+ }
+ const targetPlatform = await this.server.extensionManagementService.getTargetPlatform();
+ if (!isTargetPlatformCompatible(this.extension.local.targetPlatform, [this.extension.local.targetPlatform], targetPlatform)) {
+ throw new Error(localize('incompatible', "Can't install '{0}' extension because it is not compatible.", this.extension.identifier.id));
}
+ const vsix = await this.extension.server.extensionManagementService.zip(this.extension.local);
+ await this.server.extensionManagementService.install(vsix);
}
protected abstract getInstallLabel(): string;
@@ -660,8 +679,9 @@ export class RemoteInstallAction extends InstallInOtherServerAction {
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService,
@IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService,
@IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService,
+ @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService,
) {
- super(`extensions.remoteinstall`, extensionManagementServerService.remoteExtensionManagementServer, canInstallAnyWhere, extensionsWorkbenchService, extensionManagementServerService, extensionManifestPropertiesService);
+ super(`extensions.remoteinstall`, extensionManagementServerService.remoteExtensionManagementServer, canInstallAnyWhere, extensionsWorkbenchService, extensionManagementServerService, extensionManifestPropertiesService, extensionGalleryService);
}
protected getInstallLabel(): string {
@@ -678,8 +698,9 @@ export class LocalInstallAction extends InstallInOtherServerAction {
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService,
@IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService,
@IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService,
+ @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService,
) {
- super(`extensions.localinstall`, extensionManagementServerService.localExtensionManagementServer, false, extensionsWorkbenchService, extensionManagementServerService, extensionManifestPropertiesService);
+ super(`extensions.localinstall`, extensionManagementServerService.localExtensionManagementServer, false, extensionsWorkbenchService, extensionManagementServerService, extensionManifestPropertiesService, extensionGalleryService);
}
protected getInstallLabel(): string {
@@ -694,8 +715,9 @@ export class WebInstallAction extends InstallInOtherServerAction {
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService,
@IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService,
@IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService,
+ @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService,
) {
- super(`extensions.webInstall`, extensionManagementServerService.webExtensionManagementServer, false, extensionsWorkbenchService, extensionManagementServerService, extensionManifestPropertiesService);
+ super(`extensions.webInstall`, extensionManagementServerService.webExtensionManagementServer, false, extensionsWorkbenchService, extensionManagementServerService, extensionManifestPropertiesService, extensionGalleryService);
}
protected getInstallLabel(): string {
@@ -1623,38 +1645,38 @@ export class SetColorThemeAction extends ExtensionAction {
private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`;
private static readonly DisabledClass = `${SetColorThemeAction.EnabledClass} disabled`;
- private colorThemes: IWorkbenchColorTheme[] = [];
-
constructor(
@IExtensionService extensionService: IExtensionService,
@IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService,
@IQuickInputService private readonly quickInputService: IQuickInputService,
+ @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,
) {
super(SetColorThemeAction.ID, SetColorThemeAction.TITLE.value, SetColorThemeAction.DisabledClass, false);
this._register(Event.any<any>(extensionService.onDidChangeExtensions, workbenchThemeService.onDidColorThemeChange)(() => this.update(), this));
- workbenchThemeService.getColorThemes().then(colorThemes => {
- this.colorThemes = colorThemes;
- this.update();
- });
this.update();
}
update(): void {
- this.enabled = !!this.extension && (this.extension.state === ExtensionState.Installed) && this.colorThemes.some(th => isThemeFromExtension(th, this.extension));
- this.class = this.enabled ? SetColorThemeAction.EnabledClass : SetColorThemeAction.DisabledClass;
+ this.workbenchThemeService.getColorThemes().then(colorThemes => {
+ this.enabled = this.computeEnablement(colorThemes);
+ this.class = this.enabled ? SetColorThemeAction.EnabledClass : SetColorThemeAction.DisabledClass;
+ });
+ }
+
+ private computeEnablement(colorThemes: IWorkbenchColorTheme[]): boolean {
+ return !!this.extension && this.extension.state === ExtensionState.Installed && this.extensionEnablementService.isEnabledEnablementState(this.extension.enablementState) && colorThemes.some(th => isThemeFromExtension(th, this.extension));
}
override async run({ showCurrentTheme, ignoreFocusLost }: { showCurrentTheme: boolean; ignoreFocusLost: boolean } = { showCurrentTheme: false, ignoreFocusLost: false }): Promise<any> {
- this.colorThemes = await this.workbenchThemeService.getColorThemes();
+ const colorThemes = await this.workbenchThemeService.getColorThemes();
- this.update();
- if (!this.enabled) {
+ if (!this.computeEnablement(colorThemes)) {
return;
}
const currentTheme = this.workbenchThemeService.getColorTheme();
const delayer = new Delayer<any>(100);
- const picks = getQuickPickEntries(this.colorThemes, currentTheme, this.extension, showCurrentTheme);
+ const picks = getQuickPickEntries(colorThemes, currentTheme, this.extension, showCurrentTheme);
const pickedTheme = await this.quickInputService.pick(
picks,
{
@@ -1674,37 +1696,37 @@ export class SetFileIconThemeAction extends ExtensionAction {
private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`;
private static readonly DisabledClass = `${SetFileIconThemeAction.EnabledClass} disabled`;
- private fileIconThemes: IWorkbenchFileIconTheme[] = [];
-
constructor(
@IExtensionService extensionService: IExtensionService,
@IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService,
- @IQuickInputService private readonly quickInputService: IQuickInputService
+ @IQuickInputService private readonly quickInputService: IQuickInputService,
+ @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,
) {
super(SetFileIconThemeAction.ID, SetFileIconThemeAction.TITLE.value, SetFileIconThemeAction.DisabledClass, false);
this._register(Event.any<any>(extensionService.onDidChangeExtensions, workbenchThemeService.onDidFileIconThemeChange)(() => this.update(), this));
- workbenchThemeService.getFileIconThemes().then(fileIconThemes => {
- this.fileIconThemes = fileIconThemes;
- this.update();
- });
this.update();
}
update(): void {
- this.enabled = !!this.extension && (this.extension.state === ExtensionState.Installed) && this.fileIconThemes.some(th => isThemeFromExtension(th, this.extension));
- this.class = this.enabled ? SetFileIconThemeAction.EnabledClass : SetFileIconThemeAction.DisabledClass;
+ this.workbenchThemeService.getFileIconThemes().then(fileIconThemes => {
+ this.enabled = this.computeEnablement(fileIconThemes);
+ this.class = this.enabled ? SetFileIconThemeAction.EnabledClass : SetFileIconThemeAction.DisabledClass;
+ });
+ }
+
+ private computeEnablement(colorThemfileIconThemess: IWorkbenchFileIconTheme[]): boolean {
+ return !!this.extension && this.extension.state === ExtensionState.Installed && this.extensionEnablementService.isEnabledEnablementState(this.extension.enablementState) && colorThemfileIconThemess.some(th => isThemeFromExtension(th, this.extension));
}
override async run({ showCurrentTheme, ignoreFocusLost }: { showCurrentTheme: boolean; ignoreFocusLost: boolean } = { showCurrentTheme: false, ignoreFocusLost: false }): Promise<any> {
- this.fileIconThemes = await this.workbenchThemeService.getFileIconThemes();
- this.update();
- if (!this.enabled) {
+ const fileIconThemes = await this.workbenchThemeService.getFileIconThemes();
+ if (!this.computeEnablement(fileIconThemes)) {
return;
}
const currentTheme = this.workbenchThemeService.getFileIconTheme();
const delayer = new Delayer<any>(100);
- const picks = getQuickPickEntries(this.fileIconThemes, currentTheme, this.extension, showCurrentTheme);
+ const picks = getQuickPickEntries(fileIconThemes, currentTheme, this.extension, showCurrentTheme);
const pickedTheme = await this.quickInputService.pick(
picks,
{
@@ -1724,38 +1746,38 @@ export class SetProductIconThemeAction extends ExtensionAction {
private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`;
private static readonly DisabledClass = `${SetProductIconThemeAction.EnabledClass} disabled`;
- private productIconThemes: IWorkbenchProductIconTheme[] = [];
-
constructor(
@IExtensionService extensionService: IExtensionService,
@IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService,
- @IQuickInputService private readonly quickInputService: IQuickInputService
+ @IQuickInputService private readonly quickInputService: IQuickInputService,
+ @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,
) {
super(SetProductIconThemeAction.ID, SetProductIconThemeAction.TITLE.value, SetProductIconThemeAction.DisabledClass, false);
this._register(Event.any<any>(extensionService.onDidChangeExtensions, workbenchThemeService.onDidProductIconThemeChange)(() => this.update(), this));
- workbenchThemeService.getProductIconThemes().then(productIconThemes => {
- this.productIconThemes = productIconThemes;
- this.update();
- });
this.update();
}
update(): void {
- this.enabled = !!this.extension && (this.extension.state === ExtensionState.Installed) && this.productIconThemes.some(th => isThemeFromExtension(th, this.extension));
- this.class = this.enabled ? SetProductIconThemeAction.EnabledClass : SetProductIconThemeAction.DisabledClass;
+ this.workbenchThemeService.getProductIconThemes().then(productIconThemes => {
+ this.enabled = this.computeEnablement(productIconThemes);
+ this.class = this.enabled ? SetProductIconThemeAction.EnabledClass : SetProductIconThemeAction.DisabledClass;
+ });
+ }
+
+ private computeEnablement(productIconThemes: IWorkbenchProductIconTheme[]): boolean {
+ return !!this.extension && this.extension.state === ExtensionState.Installed && this.extensionEnablementService.isEnabledEnablementState(this.extension.enablementState) && productIconThemes.some(th => isThemeFromExtension(th, this.extension));
}
override async run({ showCurrentTheme, ignoreFocusLost }: { showCurrentTheme: boolean; ignoreFocusLost: boolean } = { showCurrentTheme: false, ignoreFocusLost: false }): Promise<any> {
- this.productIconThemes = await this.workbenchThemeService.getProductIconThemes();
- this.update();
- if (!this.enabled) {
+ const productIconThemes = await this.workbenchThemeService.getProductIconThemes();
+ if (!this.computeEnablement(productIconThemes)) {
return;
}
const currentTheme = this.workbenchThemeService.getProductIconTheme();
const delayer = new Delayer<any>(100);
- const picks = getQuickPickEntries(this.productIconThemes, currentTheme, this.extension, showCurrentTheme);
+ const picks = getQuickPickEntries(productIconThemes, currentTheme, this.extension, showCurrentTheme);
const pickedTheme = await this.quickInputService.pick(
picks,
{
@@ -1767,6 +1789,43 @@ export class SetProductIconThemeAction extends ExtensionAction {
}
}
+export class SetLanguageAction extends ExtensionAction {
+
+ static readonly ID = 'workbench.extensions.action.setLanguageTheme';
+ static readonly TITLE = { value: localize('workbench.extensions.action.setLanguageTheme', "Set Display Language"), original: 'Set Display Language' };
+
+ private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`;
+ private static readonly DisabledClass = `${SetLanguageAction.EnabledClass} disabled`;
+
+ constructor(
+ @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
+ @ILanguagePackService private readonly languagePackService: ILanguagePackService,
+ ) {
+ super(SetLanguageAction.ID, SetLanguageAction.TITLE.value, SetLanguageAction.DisabledClass, false);
+ this.update();
+ }
+
+ update(): void {
+ this.enabled = false;
+ this.class = SetLanguageAction.DisabledClass;
+ if (!this.extension) {
+ return;
+ }
+ if (!this.extensionsWorkbenchService.canSetLanguage(this.extension)) {
+ return;
+ }
+ if (this.extension.gallery && language === this.languagePackService.getLocale(this.extension.gallery)) {
+ return;
+ }
+ this.enabled = true;
+ this.class = SetLanguageAction.EnabledClass;
+ }
+
+ override async run(): Promise<any> {
+ return this.extension && this.extensionsWorkbenchService.setLanguage(this.extension);
+ }
+}
+
export class ShowRecommendedExtensionAction extends Action {
static readonly ID = 'workbench.extensions.action.showRecommendedExtension';
@@ -2259,6 +2318,10 @@ export class ExtensionStatusAction extends ExtensionAction {
return;
}
+ if (this.extensionsWorkbenchService.canSetLanguage(this.extension)) {
+ return;
+ }
+
if (this.extension.gallery && this.extension.state === ExtensionState.Uninstalled && !await this.extensionsWorkbenchService.canInstall(this.extension)) {
if (this.extensionManagementServerService.localExtensionManagementServer || this.extensionManagementServerService.remoteExtensionManagementServer) {
const targetPlatform = await (this.extensionManagementServerService.localExtensionManagementServer ? this.extensionManagementServerService.localExtensionManagementServer!.extensionManagementService.getTargetPlatform() : this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.getTargetPlatform());
diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts
index 1754e2233ab..84eaa15be18 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts
@@ -13,7 +13,7 @@ import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { IPagedRenderer } from 'vs/base/browser/ui/list/listPaging';
import { Event } from 'vs/base/common/event';
import { IExtension, ExtensionContainers, ExtensionState, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
-import { UpdateAction, ManageExtensionAction, ReloadAction, ExtensionStatusLabelAction, RemoteInstallAction, ExtensionStatusAction, LocalInstallAction, ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, ExtensionActionWithDropdownActionViewItem, ExtensionDropDownAction, WebInstallAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction, MigrateDeprecatedExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
+import { UpdateAction, ManageExtensionAction, ReloadAction, ExtensionStatusLabelAction, RemoteInstallAction, ExtensionStatusAction, LocalInstallAction, ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, ExtensionActionWithDropdownActionViewItem, ExtensionDropDownAction, WebInstallAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction, MigrateDeprecatedExtensionAction, SetLanguageAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { RatingsWidget, InstallCountWidget, RecommendationWidget, RemoteBadgeWidget, ExtensionPackCountWidget as ExtensionPackBadgeWidget, SyncIgnoredWidget, ExtensionHoverWidget, ExtensionActivationStatusWidget, PreReleaseBookmarkWidget, extensionVerifiedPublisherIconColor } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets';
import { IExtensionService, toExtension } from 'vs/workbench/services/extensions/common/extensions';
@@ -123,6 +123,7 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
reloadAction,
this.instantiationService.createInstance(InstallDropdownAction),
this.instantiationService.createInstance(InstallingLabelAction),
+ this.instantiationService.createInstance(SetLanguageAction),
this.instantiationService.createInstance(RemoteInstallAction, false),
this.instantiationService.createInstance(LocalInstallAction),
this.instantiationService.createInstance(WebInstallAction),
@@ -186,16 +187,25 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
data.extensionDisposables = dispose(data.extensionDisposables);
- const updateEnablement = async () => {
- let disabled = false;
- const deprecated = !!extension.deprecationInfo;
+ const computeEnablement = async () => {
if (extension.state === ExtensionState.Uninstalled) {
- disabled = deprecated || !(await this.extensionsWorkbenchService.canInstall(extension));
+ if (!!extension.deprecationInfo) {
+ return true;
+ }
+ if (this.extensionsWorkbenchService.canSetLanguage(extension)) {
+ return false;
+ }
+ return !(await this.extensionsWorkbenchService.canInstall(extension));
} else if (extension.local && !isLanguagePackExtension(extension.local.manifest)) {
const runningExtensions = await this.extensionService.getExtensions();
const runningExtension = runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, extension.identifier))[0];
- disabled = !(runningExtension && extension.server === this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension)));
+ return !(runningExtension && extension.server === this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension)));
}
+ return false;
+ };
+ const updateEnablement = async () => {
+ const disabled = await computeEnablement();
+ const deprecated = !!extension.deprecationInfo;
data.element.classList.toggle('deprecated', deprecated);
data.root.classList.toggle('disabled', disabled);
};
diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts
index 6faf5d3c10a..31cc538fa56 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts
@@ -158,7 +158,12 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio
constructor() {
super({
id: 'workbench.extensions.installLocalExtensions',
- get title() { return localize('select and install local extensions', "Install Local Extensions in '{0}'...", server.label); },
+ get title() {
+ return {
+ value: localize('select and install local extensions', "Install Local Extensions in '{0}'...", server.label),
+ original: `Install Local Extensions in '${server.label}'...`,
+ };
+ },
category: localize({ key: 'remote', comment: ['Remote as in remote machine'] }, "Remote"),
icon: installLocalInRemoteIcon,
f1: true,
diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
index a533f0ad244..6cf90434c7b 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
@@ -45,8 +45,10 @@ import { isBoolean, isUndefined } from 'vs/base/common/types';
import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService';
import { IExtensionService, IExtensionsStatus } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionEditor } from 'vs/workbench/contrib/extensions/browser/extensionEditor';
-import { isWeb } from 'vs/base/common/platform';
+import { isWeb, language } from 'vs/base/common/platform';
import { GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings';
+import { ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks';
+import { ILocaleService } from 'vs/workbench/contrib/localization/common/locale';
interface IExtensionStateProvider<T> {
(extension: Extension): T;
@@ -710,6 +712,8 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
@IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService,
@ILogService private readonly logService: ILogService,
@IExtensionService private readonly extensionService: IExtensionService,
+ @ILanguagePackService private readonly languagePackService: ILanguagePackService,
+ @ILocaleService private readonly localeService: ILocaleService,
) {
super();
const preferPreReleasesValue = configurationService.getValue('_extensions.preferPreReleases');
@@ -1248,6 +1252,34 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
return this.installWithProgress(() => this.installFromGallery(extension, gallery, installOptions), gallery.displayName, progressLocation);
}
+ canSetLanguage(extension: IExtension): boolean {
+ if (!isWeb) {
+ return false;
+ }
+
+ if (!extension.gallery) {
+ return false;
+ }
+
+ const locale = this.languagePackService.getLocale(extension.gallery);
+ if (!locale) {
+ return false;
+ }
+
+ return true;
+ }
+
+ async setLanguage(extension: IExtension): Promise<void> {
+ if (!this.canSetLanguage(extension)) {
+ throw new Error('Can not set language');
+ }
+ const locale = this.languagePackService.getLocale(extension.gallery!);
+ if (locale === language) {
+ return;
+ }
+ return this.localeService.setLocale({ id: locale, galleryExtension: extension.gallery, extensionId: extension.identifier.id, label: extension.displayName });
+ }
+
setEnablement(extensions: IExtension | IExtension[], enablementState: EnablementState): Promise<void> {
extensions = Array.isArray(extensions) ? extensions : [extensions];
return this.promptAndSetEnablement(extensions, enablementState);
diff --git a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts
index d1b91197ff4..57b4387c1c7 100644
--- a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts
+++ b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts
@@ -14,7 +14,7 @@ import { localize } from 'vs/nls';
import { StorageScope, IStorageService, StorageTarget } from 'vs/platform/storage/common/storage';
import { IProductService } from 'vs/platform/product/common/productService';
import { ImportantExtensionTip } from 'vs/base/common/product';
-import { forEach, IStringDictionary } from 'vs/base/common/collections';
+import { IStringDictionary } from 'vs/base/common/collections';
import { ITextModel } from 'vs/editor/common/model';
import { Schemas } from 'vs/base/common/network';
import { basename, extname } from 'vs/base/common/resources';
@@ -115,10 +115,10 @@ export class FileBasedRecommendations extends ExtensionRecommendations {
this.tasExperimentService = tasExperimentService;
if (productService.extensionTips) {
- forEach(productService.extensionTips, ({ key, value }) => this.extensionTips.set(key.toLowerCase(), value));
+ Object.entries(productService.extensionTips).forEach(([key, value]) => this.extensionTips.set(key.toLowerCase(), value));
}
if (productService.extensionImportantTips) {
- forEach(productService.extensionImportantTips, ({ key, value }) => this.importantExtensionTips.set(key.toLowerCase(), value));
+ Object.entries(productService.extensionImportantTips).forEach(([key, value]) => this.importantExtensionTips.set(key.toLowerCase(), value));
}
}
@@ -153,7 +153,7 @@ export class FileBasedRecommendations extends ExtensionRecommendations {
const cachedRecommendations = this.getCachedRecommendations();
const now = Date.now();
// Retire existing recommendations if they are older than a week or are not part of this.productService.extensionTips anymore
- forEach(cachedRecommendations, ({ key, value }) => {
+ Object.entries(cachedRecommendations).forEach(([key, value]) => {
const diff = (now - value) / milliSecondsInADay;
if (diff <= 7 && allRecommendations.indexOf(key) > -1) {
this.fileBasedRecommendations.set(key.toLowerCase(), { recommendedTime: value });
@@ -400,7 +400,7 @@ export class FileBasedRecommendations extends ExtensionRecommendations {
storedRecommendations = storedRecommendations.reduce((result, id) => { result[id] = Date.now(); return result; }, <IStringDictionary<number>>{});
}
const result: IStringDictionary<number> = {};
- forEach(storedRecommendations, ({ key, value }) => {
+ Object.entries(storedRecommendations).forEach(([key, value]) => {
if (typeof value === 'number') {
result[key.toLowerCase()] = value;
}
diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts
index 60c5b415c54..ca1a3a5ff3f 100644
--- a/src/vs/workbench/contrib/extensions/common/extensions.ts
+++ b/src/vs/workbench/contrib/extensions/common/extensions.ts
@@ -107,6 +107,8 @@ export interface IExtensionsWorkbenchService {
uninstall(extension: IExtension): Promise<void>;
installVersion(extension: IExtension, version: string, installOptions?: InstallOptions): Promise<IExtension>;
reinstall(extension: IExtension): Promise<IExtension>;
+ canSetLanguage(extension: IExtension): boolean;
+ setLanguage(extension: IExtension): Promise<void>;
setEnablement(extensions: IExtension | IExtension[], enablementState: EnablementState): Promise<void>;
open(extension: IExtension, options?: IExtensionEditorOptions): Promise<void>;
checkForUpdates(): Promise<void>;
diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts
index 2641877c8cd..67ab692b603 100644
--- a/src/vs/workbench/contrib/files/browser/fileCommands.ts
+++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts
@@ -628,21 +628,23 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
args: [
{
isOptional: true,
- name: 'viewType',
- description: 'The editor view type',
+ name: 'New Untitled File args',
+ description: 'The editor view type and language ID if known',
schema: {
'type': 'object',
- 'required': ['viewType'],
'properties': {
'viewType': {
'type': 'string'
+ },
+ 'languageId': {
+ 'type': 'string'
}
}
}
}
]
},
- handler: async (accessor, args?: { viewType: string }) => {
+ handler: async (accessor, args?: { languageId?: string; viewType?: string }) => {
const editorService = accessor.get(IEditorService);
await editorService.openEditor({
@@ -650,7 +652,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
options: {
override: args?.viewType,
pinned: true
- }
+ },
+ languageId: args?.languageId,
});
}
});
diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts
index 592d559451a..1ebf70ce257 100644
--- a/src/vs/workbench/contrib/files/browser/files.contribution.ts
+++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts
@@ -159,7 +159,7 @@ configurationRegistry.registerConfiguration({
'type': 'string', // expression ({ "**/*.js": { "when": "$(basename).js" } })
'pattern': '\\w*\\$\\(basename\\)\\w*',
'default': '$(basename).ext',
- 'markdownDescription': nls.localize('files.exclude.when', "Additional check on the siblings of a matching file. Use \\$(basename) as variable for the matching file name.")
+ 'markdownDescription': nls.localize({ key: 'files.exclude.when', comment: ['\\$(basename) should not be translated'] }, "Additional check on the siblings of a matching file. Use \\$(basename) as variable for the matching file name.")
}
}
}
@@ -185,7 +185,7 @@ configurationRegistry.registerConfiguration({
'files.autoGuessEncoding': {
'type': 'boolean',
'default': false,
- 'markdownDescription': nls.localize('autoGuessEncoding', "When enabled, the editor will attempt to guess the character set encoding when opening files. This setting can also be configured per language. Note, this setting is not respected by text search. Only `#files.encoding#` is respected."),
+ 'markdownDescription': nls.localize('autoGuessEncoding', "When enabled, the editor will attempt to guess the character set encoding when opening files. This setting can also be configured per language. Note, this setting is not respected by text search. Only {0} is respected.", '`#files.encoding#`'),
'scope': ConfigurationScope.LANGUAGE_OVERRIDABLE
},
'files.eol': {
@@ -475,7 +475,7 @@ configurationRegistry.registerConfiguration({
},
'explorer.excludeGitIgnore': {
type: 'boolean',
- markdownDescription: nls.localize('excludeGitignore', "Controls whether entries in .gitignore should be parsed and excluded from the explorer. Similar to `#files.exclude#`."),
+ markdownDescription: nls.localize('excludeGitignore', "Controls whether entries in .gitignore should be parsed and excluded from the explorer. Similar to {0}.", '`#files.exclude#`'),
default: false,
scope: ConfigurationScope.RESOURCE
},
@@ -487,7 +487,7 @@ configurationRegistry.registerConfiguration({
},
'explorer.fileNesting.expand': {
'type': 'boolean',
- 'markdownDescription': nls.localize('fileNestingExpand', "Controls whether file nests are automatically expanded. `#explorer.fileNesting.enabled#` must be set for this to take effect."),
+ 'markdownDescription': nls.localize('fileNestingExpand', "Controls whether file nests are automatically expanded. {0} must be set for this to take effect.", '`#explorer.fileNesting.enabled#`'),
'default': true,
},
'explorer.fileNesting.patterns': {
diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts
index 1e2e605ec5e..6185a906efa 100644
--- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts
+++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts
@@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri';
import * as perf from 'vs/base/common/performance';
import { IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions';
import { memoize } from 'vs/base/common/decorators';
-import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, ExplorerRootContext, ExplorerResourceReadonlyContext, ExplorerResourceCut, ExplorerResourceMoveableToTrash, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext, ExplorerCompressedLastFocusContext, ExplorerResourceAvailableEditorIdsContext, VIEW_ID, VIEWLET_ID, ExplorerResourceNotReadonlyContext } from 'vs/workbench/contrib/files/common/files';
+import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, ExplorerRootContext, ExplorerResourceReadonlyContext, ExplorerResourceCut, ExplorerResourceMoveableToTrash, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext, ExplorerCompressedLastFocusContext, ExplorerResourceAvailableEditorIdsContext, VIEW_ID, VIEWLET_ID, ExplorerResourceNotReadonlyContext, ViewHasSomeCollapsibleRootItemContext } from 'vs/workbench/contrib/files/common/files';
import { FileCopiedContext, NEW_FILE_COMMAND_ID, NEW_FOLDER_COMMAND_ID } from 'vs/workbench/contrib/files/browser/fileActions';
import * as DOM from 'vs/base/browser/dom';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
@@ -67,13 +67,19 @@ interface IExplorerViewStyles {
listDropBackground?: Color;
}
-function hasExpandedRootChild(tree: WorkbenchCompressibleAsyncDataTree<ExplorerItem | ExplorerItem[], ExplorerItem, FuzzyScore>, treeInput: ExplorerItem[]): boolean {
- for (const folder of treeInput) {
- if (tree.hasNode(folder) && !tree.isCollapsed(folder)) {
- for (const [, child] of folder.children.entries()) {
- if (tree.hasNode(child) && tree.isCollapsible(child) && !tree.isCollapsed(child)) {
- return true;
- }
+// Accepts a single or multiple workspace folders
+function hasExpandedRootChild(tree: WorkbenchCompressibleAsyncDataTree<ExplorerItem | ExplorerItem[], ExplorerItem, FuzzyScore>, treeInput: ExplorerItem | ExplorerItem[]): boolean {
+ const inputsToCheck = [];
+ if (Array.isArray(treeInput)) {
+ inputsToCheck.push(...treeInput.filter(folder => tree.hasNode(folder) && !tree.isCollapsed(folder)));
+ } else {
+ inputsToCheck.push(treeInput);
+ }
+
+ for (const folder of inputsToCheck) {
+ for (const [, child] of folder.children.entries()) {
+ if (tree.hasNode(child) && tree.isCollapsible(child) && !tree.isCollapsed(child)) {
+ return true;
}
}
}
@@ -161,6 +167,8 @@ export class ExplorerView extends ViewPane implements IExplorerView {
private compressedFocusFirstContext: IContextKey<boolean>;
private compressedFocusLastContext: IContextKey<boolean>;
+ private viewHasSomeCollapsibleRootItem: IContextKey<boolean>;
+
private horizontalScrolling: boolean | undefined;
private dragHandler!: DelayedDragHandler;
@@ -207,6 +215,7 @@ export class ExplorerView extends ViewPane implements IExplorerView {
this.compressedFocusContext = ExplorerCompressedFocusContext.bindTo(contextKeyService);
this.compressedFocusFirstContext = ExplorerCompressedFirstFocusContext.bindTo(contextKeyService);
this.compressedFocusLastContext = ExplorerCompressedLastFocusContext.bindTo(contextKeyService);
+ this.viewHasSomeCollapsibleRootItem = ViewHasSomeCollapsibleRootItemContext.bindTo(contextKeyService);
this.explorerService.registerView(this);
}
@@ -493,6 +502,9 @@ export class ExplorerView extends ViewPane implements IExplorerView {
}
}));
+ this._register(this.tree.onDidChangeCollapseState(() => this.updateAnyCollapsedContext()));
+ this.updateAnyCollapsedContext();
+
this._register(this.tree.onMouseDblClick(e => {
if (e.element === null) {
// click in empty area -> create a new file #116676
@@ -769,6 +781,23 @@ export class ExplorerView extends ViewPane implements IExplorerView {
}
}
+ expandAll(): void {
+ if (this.explorerService.isEditable(undefined)) {
+ this.tree.domFocus();
+ }
+
+ const treeInput = this.tree.getInput();
+ if (Array.isArray(treeInput)) {
+ treeInput.forEach(folder => {
+ folder.children.forEach(child => this.tree.hasNode(child) && this.tree.expand(child, true));
+ });
+
+ return;
+ }
+
+ this.tree.expandAll();
+ }
+
collapseAll(): void {
if (this.explorerService.isEditable(undefined)) {
this.tree.domFocus();
@@ -837,6 +866,14 @@ export class ExplorerView extends ViewPane implements IExplorerView {
this.compressedFocusLastContext.set(controller.index === controller.count - 1);
}
+ private updateAnyCollapsedContext(): void {
+ const treeInput = this.tree.getInput();
+ if (treeInput === undefined) {
+ return;
+ }
+ this.viewHasSomeCollapsibleRootItem.set(hasExpandedRootChild(this.tree, treeInput));
+ }
+
styleListDropBackground(styles: IExplorerViewStyles): void {
const content: string[] = [];
@@ -951,7 +988,7 @@ registerAction2(class extends Action2 {
menu: {
id: MenuId.ViewTitle,
group: 'navigation',
- when: ContextKeyExpr.equals('view', VIEW_ID),
+ when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_ID), ViewHasSomeCollapsibleRootItemContext),
order: 40
}
});
@@ -963,3 +1000,26 @@ registerAction2(class extends Action2 {
explorerView.collapseAll();
}
});
+
+registerAction2(class extends Action2 {
+ constructor() {
+ super({
+ id: 'workbench.files.action.expandExplorerFolders',
+ title: { value: nls.localize('expandExplorerFolders', "Expand Folders in Explorer"), original: 'Expand Folders in Explorer' },
+ f1: true,
+ icon: Codicon.expandAll,
+ menu: {
+ id: MenuId.ViewTitle,
+ group: 'navigation',
+ when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_ID), ViewHasSomeCollapsibleRootItemContext.toNegated()),
+ order: 40
+ }
+ });
+ }
+
+ run(accessor: ServicesAccessor) {
+ const viewsService = accessor.get(IViewsService);
+ const explorerView = viewsService.getViewWithId(VIEW_ID) as ExplorerView;
+ explorerView.expandAll();
+ }
+});
diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts
index d903a67b219..b9500df93d9 100644
--- a/src/vs/workbench/contrib/files/common/files.ts
+++ b/src/vs/workbench/contrib/files/common/files.ts
@@ -56,6 +56,8 @@ export const ExplorerCompressedFocusContext = new RawContextKey<boolean>('explor
export const ExplorerCompressedFirstFocusContext = new RawContextKey<boolean>('explorerViewletCompressedFirstFocus', true, { type: 'boolean', description: localize('explorerViewletCompressedFirstFocus', "True when the focus is inside a compact item's first part in the EXPLORER view.") });
export const ExplorerCompressedLastFocusContext = new RawContextKey<boolean>('explorerViewletCompressedLastFocus', true, { type: 'boolean', description: localize('explorerViewletCompressedLastFocus', "True when the focus is inside a compact item's last part in the EXPLORER view.") });
+export const ViewHasSomeCollapsibleRootItemContext = new RawContextKey<boolean>('viewHasSomeCollapsibleItem', false, { type: 'boolean', description: localize('viewHasSomeCollapsibleItem', "True when a workspace in the EXPLORER view has some collapsible root child.") });
+
export const FilesExplorerFocusCondition = ContextKeyExpr.and(ExplorerViewletVisibleContext, FilesExplorerFocusedContext, ContextKeyExpr.not(InputFocusedContextKey));
export const ExplorerFocusCondition = ContextKeyExpr.and(ExplorerViewletVisibleContext, ExplorerFocusedContext, ContextKeyExpr.not(InputFocusedContextKey));
diff --git a/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts b/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts
index 2766516fdc0..e3d83192f51 100644
--- a/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts
+++ b/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts
@@ -174,7 +174,10 @@ registerAction2(class StartReadHints extends EditorAction2 {
constructor() {
super({
id: 'inlayHints.startReadingLineWithHint',
- title: localize('read.title', 'Read Line With Inline Hints'),
+ title: {
+ value: localize('read.title', 'Read Line With Inline Hints'),
+ original: 'Read Line With Inline Hints'
+ },
precondition: EditorContextKeys.hasInlayHintsProvider,
f1: true
});
@@ -193,7 +196,10 @@ registerAction2(class StopReadHints extends EditorAction2 {
constructor() {
super({
id: 'inlayHints.stopReadingLineWithHint',
- title: localize('stop.title', 'Stop Inlay Hints Reading'),
+ title: {
+ value: localize('stop.title', 'Stop Inlay Hints Reading'),
+ original: 'Stop Inlay Hints Reading'
+ },
precondition: InlayHintsAccessibility.IsReading,
f1: true,
keybinding: {
diff --git a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts
index f108213e01b..9969bd9c645 100644
--- a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts
+++ b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts
@@ -9,6 +9,7 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { parse } from 'vs/base/common/marshalling';
import { Schemas } from 'vs/base/common/network';
+import { extname } from 'vs/base/common/resources';
import { isFalsyOrWhitespace } from 'vs/base/common/strings';
import { assertType } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
@@ -23,9 +24,10 @@ import { peekViewBorder /*, peekViewEditorBackground, peekViewResultsBackground
import { Context as SuggestContext } from 'vs/editor/contrib/suggest/browser/suggest';
import { localize } from 'vs/nls';
import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
+import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
-import { EditorActivation } from 'vs/platform/editor/common/editor';
+import { EditorActivation, IResourceEditorInput } from 'vs/platform/editor/common/editor';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
@@ -37,20 +39,21 @@ import { contrastBorder, listInactiveSelectionBackground, registerColor, transpa
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor';
import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
-import { EditorExtensions, EditorsOrder, IEditorSerializer } from 'vs/workbench/common/editor';
+import { EditorExtensions, EditorsOrder, IEditorFactoryRegistry, IEditorSerializer } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
// import { Color } from 'vs/base/common/color';
import { PANEL_BORDER } from 'vs/workbench/common/theme';
import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits';
-import { INTERACTIVE_INPUT_CURSOR_BOUNDARY } from 'vs/workbench/contrib/interactive/browser/interactiveCommon';
+import { InteractiveWindowSetting, INTERACTIVE_INPUT_CURSOR_BOUNDARY } from 'vs/workbench/contrib/interactive/browser/interactiveCommon';
import { IInteractiveDocumentService, InteractiveDocumentService } from 'vs/workbench/contrib/interactive/browser/interactiveDocumentService';
import { InteractiveEditor } from 'vs/workbench/contrib/interactive/browser/interactiveEditor';
import { InteractiveEditorInput } from 'vs/workbench/contrib/interactive/browser/interactiveEditorInput';
import { IInteractiveHistoryService, InteractiveHistoryService } from 'vs/workbench/contrib/interactive/browser/interactiveHistoryService';
import { NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT } from 'vs/workbench/contrib/notebook/browser/controller/coreActions';
+import { INotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget';
import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons';
-import { CellEditType, CellKind, ICellOutput, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon';
+import { CellEditType, CellKind, CellUri, ICellOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
import { INotebookContentProvider, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { columnToEditorGroup } from 'vs/workbench/services/editor/common/editorGroupColumn';
@@ -192,7 +195,7 @@ export class InteractiveDocumentContribution extends Disposable implements IWork
editorResolverService.registerEditor(
`${Schemas.vscodeInteractiveInput}:/**`,
{
- id: InteractiveEditorInput.ID,
+ id: 'vscode-interactive-input',
label: 'Interactive Editor',
priority: RegisteredEditorPriority.exclusive
},
@@ -209,17 +212,31 @@ export class InteractiveDocumentContribution extends Disposable implements IWork
editorResolverService.registerEditor(
`*.interactive`,
{
- id: InteractiveEditorInput.ID,
+ id: 'interactive',
label: 'Interactive Editor',
priority: RegisteredEditorPriority.exclusive
},
{
- canSupportResource: uri => uri.scheme === Schemas.vscodeInteractive,
+ canSupportResource: uri => uri.scheme === Schemas.vscodeInteractive || (uri.scheme === Schemas.vscodeNotebookCell && extname(uri) === '.interactive'),
singlePerResource: true
},
- ({ resource }) => {
- const editorInput = editorService.getEditors(EditorsOrder.SEQUENTIAL).find(editor => editor.editor instanceof InteractiveEditorInput && editor.editor.resource?.toString() === resource.toString());
- return editorInput!;
+ ({ resource, options }) => {
+ const data = CellUri.parse(resource);
+ let notebookUri: URI = resource;
+ let cellOptions: IResourceEditorInput | undefined;
+
+ if (data) {
+ notebookUri = data.notebook;
+ cellOptions = { resource, options };
+ }
+
+ const notebookOptions = { ...options, cellOptions } as INotebookEditorOptions;
+
+ const editorInput = editorService.getEditors(EditorsOrder.SEQUENTIAL).find(editor => editor.editor instanceof InteractiveEditorInput && editor.editor.resource?.toString() === notebookUri.toString());
+ return {
+ editor: editorInput!.editor,
+ options: notebookOptions
+ };
}
);
}
@@ -256,8 +273,13 @@ workbenchContributionsRegistry.registerWorkbenchContribution(InteractiveDocument
workbenchContributionsRegistry.registerWorkbenchContribution(InteractiveInputContentProvider, LifecyclePhase.Starting);
export class InteractiveEditorSerializer implements IEditorSerializer {
+ public static readonly ID = InteractiveEditorInput.ID;
+
+ constructor(@IConfigurationService private configurationService: IConfigurationService) {
+ }
+
canSerialize(): boolean {
- return true;
+ return this.configurationService.getValue<boolean>(InteractiveWindowSetting.interactiveWindowHotExit);
}
serialize(input: EditorInput): string {
@@ -265,11 +287,16 @@ export class InteractiveEditorSerializer implements IEditorSerializer {
return JSON.stringify({
resource: input.primary.resource,
inputResource: input.inputResource,
+ name: input.getName(),
+ data: input.getSerialization()
});
}
deserialize(instantiationService: IInstantiationService, raw: string) {
- type Data = { resource: URI; inputResource: URI };
+ if (!this.canSerialize()) {
+ return undefined;
+ }
+ type Data = { resource: URI; inputResource: URI; data: any };
const data = <Data>parse(raw);
if (!data) {
return undefined;
@@ -280,14 +307,15 @@ export class InteractiveEditorSerializer implements IEditorSerializer {
}
const input = InteractiveEditorInput.create(instantiationService, resource, inputResource);
+ input.restoreSerialization(data.data);
return input;
}
}
-// Registry.as<EditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories).registerEditorInputSerializer(
-// InteractiveEditorInput.ID,
-// InteractiveEditorSerializer
-// );
+Registry.as<IEditorFactoryRegistry>(EditorExtensions.EditorFactory)
+ .registerEditorSerializer(
+ InteractiveEditorSerializer.ID,
+ InteractiveEditorSerializer);
registerSingleton(IInteractiveHistoryService, InteractiveHistoryService);
registerSingleton(IInteractiveDocumentService, InteractiveDocumentService);
@@ -722,15 +750,20 @@ registerThemingParticipant((theme) => {
});
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).registerConfiguration({
- id: 'notebook',
+ id: 'interactiveWindow',
order: 100,
type: 'object',
'properties': {
- [NotebookSetting.interactiveWindowAlwaysScrollOnNewCell]: {
+ [InteractiveWindowSetting.interactiveWindowAlwaysScrollOnNewCell]: {
type: 'boolean',
default: true,
markdownDescription: localize('interactiveWindow.alwaysScrollOnNewCell', "Automatically scroll the interactive window to show the output of the last statement executed. If this value is false, the window will only scroll if the last cell was already the one scrolled to.")
},
+ [InteractiveWindowSetting.interactiveWindowHotExit]: {
+ type: 'boolean',
+ default: false,
+ markdownDescription: localize('interactiveWindow.hotExit', "Controls whether the interactive window sessions should be restored when the workspace reloads.")
+ }
}
});
diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveCommon.ts b/src/vs/workbench/contrib/interactive/browser/interactiveCommon.ts
index ef37c64c93b..edda5c7f25f 100644
--- a/src/vs/workbench/contrib/interactive/browser/interactiveCommon.ts
+++ b/src/vs/workbench/contrib/interactive/browser/interactiveCommon.ts
@@ -6,3 +6,8 @@
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
export const INTERACTIVE_INPUT_CURSOR_BOUNDARY = new RawContextKey<'none' | 'top' | 'bottom' | 'both'>('interactiveInputCursorAtBoundary', 'none');
+
+export const InteractiveWindowSetting = {
+ interactiveWindowAlwaysScrollOnNewCell: 'interactiveWindow.alwaysScrollOnNewCell',
+ interactiveWindowHotExit: 'interactiveWindow.hotExit'
+};
diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts
index 37d2b4fde50..81992e7547d 100644
--- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts
+++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts
@@ -22,7 +22,7 @@ import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
import { EditorPaneSelectionChangeReason, IEditorMemento, IEditorOpenContext, IEditorPaneSelectionChangeEvent } from 'vs/workbench/common/editor';
import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions';
import { InteractiveEditorInput } from 'vs/workbench/contrib/interactive/browser/interactiveEditorInput';
-import { ICellViewModel, INotebookEditorViewState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
+import { ICellViewModel, INotebookEditorOptions, INotebookEditorViewState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions';
import { IBorrowValue, INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService';
import { cellEditorBackground, NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget';
@@ -33,9 +33,8 @@ import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'
import { ILanguageService } from 'vs/editor/common/languages/language';
import { IMenuService, MenuId } from 'vs/platform/actions/common/actions';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
-import { INTERACTIVE_INPUT_CURSOR_BOUNDARY } from 'vs/workbench/contrib/interactive/browser/interactiveCommon';
+import { InteractiveWindowSetting, INTERACTIVE_INPUT_CURSOR_BOUNDARY } from 'vs/workbench/contrib/interactive/browser/interactiveCommon';
import { ComplexNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel';
-import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { NotebookOptions } from 'vs/workbench/contrib/notebook/common/notebookOptions';
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
@@ -57,6 +56,7 @@ import { ITextEditorOptions, TextEditorSelectionSource } from 'vs/platform/edito
import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
import { NOTEBOOK_KERNEL } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
import { ICursorPositionChangedEvent } from 'vs/editor/common/cursorEvents';
+import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
const DECORATION_KEY = 'interactiveInputDecoration';
const INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'InteractiveEditorViewState';
@@ -97,6 +97,7 @@ export class InteractiveEditor extends EditorPane {
#contextMenuService: IContextMenuService;
#editorGroupService: IEditorGroupsService;
#notebookExecutionStateService: INotebookExecutionStateService;
+ #extensionService: IExtensionService;
#widgetDisposableStore: DisposableStore = this._register(new DisposableStore());
#dimension?: DOM.Dimension;
#notebookOptions: NotebookOptions;
@@ -124,7 +125,8 @@ export class InteractiveEditor extends EditorPane {
@IContextMenuService contextMenuService: IContextMenuService,
@IEditorGroupsService editorGroupService: IEditorGroupsService,
@ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService,
- @INotebookExecutionStateService notebookExecutionStateService: INotebookExecutionStateService
+ @INotebookExecutionStateService notebookExecutionStateService: INotebookExecutionStateService,
+ @IExtensionService extensionService: IExtensionService,
) {
super(
InteractiveEditor.ID,
@@ -142,6 +144,7 @@ export class InteractiveEditor extends EditorPane {
this.#contextMenuService = contextMenuService;
this.#editorGroupService = editorGroupService;
this.#notebookExecutionStateService = notebookExecutionStateService;
+ this.#extensionService = extensionService;
this.#notebookOptions = new NotebookOptions(configurationService, notebookExecutionStateService, { cellToolbarInteraction: 'hover', globalToolbar: true, defaultCellCollapseConfig: { codeCell: { inputCollapsed: true } } });
this.#editorMemento = this.getEditorMemento<InteractiveEditorViewState>(editorGroupService, textResourceConfigurationService, INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY);
@@ -397,6 +400,7 @@ export class InteractiveEditor extends EditorPane {
this.#notebookWidget.value?.setParentContextKeyService(this.#contextKeyService);
const viewState = options?.viewState ?? this.#loadNotebookEditorViewState(input);
+ await this.#extensionService.whenInstalledExtensionsRegistered();
await this.#notebookWidget.value!.setModel(model.notebook, viewState?.notebook);
model.notebook.setCellCollapseDefault(this.#notebookOptions.getCellCollapseDefault());
this.#notebookWidget.value!.setOptions({
@@ -501,6 +505,11 @@ export class InteractiveEditor extends EditorPane {
this.#syncWithKernel();
}
+ override setOptions(options: INotebookEditorOptions | undefined): void {
+ this.#notebookWidget.value?.setOptions(options);
+ super.setOptions(options);
+ }
+
#toEditorPaneSelectionChangeReason(e: ICursorPositionChangedEvent): EditorPaneSelectionChangeReason {
switch (e.source) {
case TextEditorSelectionSource.PROGRAMMATIC: return EditorPaneSelectionChangeReason.PROGRAMMATIC;
@@ -523,7 +532,7 @@ export class InteractiveEditor extends EditorPane {
const index = this.#notebookWidget.value!.getCellIndex(cvm);
if (index === this.#notebookWidget.value!.getLength() - 1) {
// If we're already at the bottom or auto scroll is enabled, scroll to the bottom
- if (this.configurationService.getValue<boolean>(NotebookSetting.interactiveWindowAlwaysScrollOnNewCell) || this.#cellAtBottom(cvm)) {
+ if (this.configurationService.getValue<boolean>(InteractiveWindowSetting.interactiveWindowAlwaysScrollOnNewCell) || this.#cellAtBottom(cvm)) {
this.#notebookWidget.value!.scrollToBottom();
}
}
diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts
index 17d8eaa03cf..71b125ec799 100644
--- a/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts
+++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts
@@ -3,19 +3,21 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
+import { VSBuffer } from 'vs/base/common/buffer';
import { Event } from 'vs/base/common/event';
import { IReference } from 'vs/base/common/lifecycle';
import * as paths from 'vs/base/common/path';
import { isEqual } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
-import { IModelService } from 'vs/editor/common/services/model';
import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IUntypedEditorInput } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { IInteractiveDocumentService } from 'vs/workbench/contrib/interactive/browser/interactiveDocumentService';
import { IInteractiveHistoryService } from 'vs/workbench/contrib/interactive/browser/interactiveHistoryService';
-import { IResolvedNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon';
+import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
+import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
+import { CellKind, ICellDto2, IOutputDto, IResolvedNotebookEditorModel, NotebookCellCollapseState, NotebookCellInternalMetadata, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ICompositeNotebookEditorInput, NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput';
export class InteractiveEditorInput extends EditorInput implements ICompositeNotebookEditorInput {
@@ -44,8 +46,10 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot
return [this._notebookEditorInput];
}
- override get resource() {
- return this.primary.resource;
+ private _resource: URI;
+
+ override get resource(): URI {
+ return this._resource;
}
private _inputResource: URI;
@@ -71,7 +75,6 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot
inputResource: URI,
title: string | undefined,
@IInstantiationService instantiationService: IInstantiationService,
- @IModelService modelService: IModelService,
@ITextModelService textModelService: ITextModelService,
@IInteractiveDocumentService interactiveDocumentService: IInteractiveDocumentService,
@@ -82,6 +85,7 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot
this._notebookEditorInput = input;
this._register(this._notebookEditorInput);
this._initTitle = title;
+ this._resource = resource;
this._inputResource = inputResource;
this._inputResolver = null;
this._editorModelReference = null;
@@ -130,7 +134,14 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot
return this._inputResolver;
}
- this._inputResolver = this._resolveEditorModel();
+ this._inputResolver = this._resolveEditorModel().then(editorModel => {
+ if (this._data) {
+ editorModel?.notebook.reset(this._data.notebookData.cells.map((cell: ISerializedCell) => deserializeCell(cell)), this._data.notebookData.metadata, this._data.notebookData.transientOptions);
+ }
+
+ return editorModel;
+ });
+
return this._inputResolver;
}
@@ -142,6 +153,10 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot
this._interactiveDocumentService.willCreateInteractiveDocument(this.resource!, this.inputResource, language);
this._inputModelRef = await this._textModelService.createModelReference(this.inputResource);
+ if (this._data && this._data.inputData) {
+ this._inputModelRef.object.textEditorModel.setValue(this._data.inputData.value);
+ }
+
return this._inputModelRef.object.textEditorModel;
}
@@ -166,6 +181,37 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot
return basename.substr(0, basename.length - paths.extname(p).length);
}
+ getSerialization(): { notebookData: any | undefined; inputData: any | undefined } {
+ return {
+ notebookData: this._serializeNotebook(this._editorModelReference?.notebook),
+ inputData: this._inputModelRef ? {
+ value: this._inputModelRef.object.textEditorModel.getValue(),
+ language: this._inputModelRef.object.textEditorModel.getLanguageId()
+ } : undefined
+ };
+ }
+
+ private _data: { notebookData: any | undefined; inputData: any | undefined } | undefined;
+
+ async restoreSerialization(data: { notebookData: any | undefined; inputData: any | undefined } | undefined) {
+ this._data = data;
+ }
+
+ private _serializeNotebook(notebook?: NotebookTextModel) {
+ if (!notebook) {
+ return undefined;
+ }
+
+ const cells = notebook.cells.map(cell => serializeCell(cell));
+
+ return {
+ cells: cells,
+ metadata: notebook.metadata,
+ transientOptions: notebook.transientOptions
+ };
+ }
+
+
override dispose() {
// we support closing the interactive window without prompt, so the editor model should not be dirty
this._editorModelReference?.revert({ soft: true });
@@ -183,3 +229,74 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot
return this._historyService;
}
}
+
+/**
+ * Serialization of interactive notebook.
+ * This is not placed in notebook land as regular notebooks are handled by file service directly.
+ */
+
+interface ISerializedOutputItem {
+ readonly mime: string;
+ readonly data: number[];
+}
+
+interface ISerializedCellOutput {
+ outputs: ISerializedOutputItem[];
+ metadata?: Record<string, any>;
+ outputId: string;
+}
+
+export interface ISerializedCell {
+ source: string;
+ language: string;
+ mime: string | undefined;
+ cellKind: CellKind;
+ outputs: ISerializedCellOutput[];
+ metadata?: NotebookCellMetadata;
+ internalMetadata?: NotebookCellInternalMetadata;
+ collapseState?: NotebookCellCollapseState;
+}
+
+function serializeCell(cell: NotebookCellTextModel): ISerializedCell {
+ return {
+ cellKind: cell.cellKind,
+ language: cell.language,
+ metadata: cell.metadata,
+ mime: cell.mime,
+ outputs: cell.outputs.map(output => serializeCellOutput(output)),
+ source: cell.getValue()
+ };
+}
+
+function deserializeCell(cell: ISerializedCell): ICellDto2 {
+ return {
+ cellKind: cell.cellKind,
+ source: cell.source,
+ language: cell.language,
+ metadata: cell.metadata,
+ mime: cell.mime,
+ outputs: cell.outputs.map((output) => deserializeCellOutput(output))
+ };
+}
+
+function serializeCellOutput(output: IOutputDto): ISerializedCellOutput {
+ return {
+ outputId: output.outputId,
+ outputs: output.outputs.map(ot => ({
+ mime: ot.mime,
+ data: ot.data.buffer ? Array.from(ot.data.buffer) : []
+ })),
+ metadata: output.metadata
+ };
+}
+
+function deserializeCellOutput(output: ISerializedCellOutput): IOutputDto {
+ return {
+ outputId: output.outputId,
+ outputs: output.outputs.map(ot => ({
+ mime: ot.mime,
+ data: VSBuffer.fromByteArray(ot.data)
+ })),
+ metadata: output.metadata
+ };
+}
diff --git a/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts b/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts
index a931e2bb2f2..ef593cb3de1 100644
--- a/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts
+++ b/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts
@@ -123,7 +123,7 @@ registerAction2(class extends Action2 {
constructor() {
super({
id: detectLanguageCommandId,
- title: localize('detectlang', 'Detect Language from Content'),
+ title: { value: localize('detectlang', 'Detect Language from Content'), original: 'Detect Language from Content' },
f1: true,
precondition: ContextKeyExpr.and(NOTEBOOK_EDITOR_EDITABLE.toNegated(), EditorContextKeys.editorTextFocus),
keybinding: { primary: KeyCode.KeyD | KeyMod.Alt | KeyMod.Shift, weight: KeybindingWeight.WorkbenchContrib }
diff --git a/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts b/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts
index 661059e89b9..63b120d3b3c 100644
--- a/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts
+++ b/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts
@@ -398,8 +398,14 @@ registerAction2(class extends Action2 {
constructor() {
super({
id: 'editor.inlayHints.Reset',
- title: localize('reset', 'Reset Language Status Interaction Counter'),
- category: localize('cat', 'View'),
+ title: {
+ value: localize('reset', 'Reset Language Status Interaction Counter'),
+ original: 'Reset Language Status Interaction Counter'
+ },
+ category: {
+ value: localize('cat', 'View'),
+ original: 'View'
+ },
f1: true
});
}
diff --git a/src/vs/workbench/contrib/localization/browser/localeService.ts b/src/vs/workbench/contrib/localization/browser/localeService.ts
index c59d84821b2..dc8138589bf 100644
--- a/src/vs/workbench/contrib/localization/browser/localeService.ts
+++ b/src/vs/workbench/contrib/localization/browser/localeService.ts
@@ -3,31 +3,62 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { language } from 'vs/base/common/platform';
+import { localize } from 'vs/nls';
+import { Language } from 'vs/base/common/platform';
+import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { ILanguagePackItem } from 'vs/platform/languagePacks/common/languagePacks';
import { ILocaleService } from 'vs/workbench/contrib/localization/common/locale';
+import { IHostService } from 'vs/workbench/services/host/browser/host';
+import { IProductService } from 'vs/platform/product/common/productService';
export class WebLocaleService implements ILocaleService {
declare readonly _serviceBrand: undefined;
- async setLocale(languagePackItem: ILanguagePackItem): Promise<boolean> {
+ constructor(
+ @IDialogService private readonly dialogService: IDialogService,
+ @IHostService private readonly hostService: IHostService,
+ @IProductService private readonly productService: IProductService
+ ) { }
+
+ async setLocale(languagePackItem: ILanguagePackItem): Promise<void> {
const locale = languagePackItem.id;
- if (locale === language || (!locale && language === navigator.language)) {
- return false;
+ if (locale === Language.value() || (!locale && Language.value() === navigator.language)) {
+ return;
}
if (locale) {
window.localStorage.setItem('vscode.nls.locale', locale);
} else {
window.localStorage.removeItem('vscode.nls.locale');
}
- return true;
- }
- async clearLocalePreference(): Promise<boolean> {
- if (language === navigator.language) {
- return false;
+ const restartDialog = await this.dialogService.confirm({
+ type: 'info',
+ message: localize('relaunchDisplayLanguageMessage', "To change the display language, {0} needs to reload", this.productService.nameLong),
+ detail: localize('relaunchDisplayLanguageDetail', "Press the reload button to refresh the page and set the display language to {0}.", languagePackItem.label),
+ primaryButton: localize({ key: 'reload', comment: ['&& denotes a mnemonic character'] }, "&&Reload"),
+ });
+
+ if (restartDialog.confirmed) {
+ this.hostService.restart();
}
+ }
+
+ async clearLocalePreference(): Promise<void> {
window.localStorage.removeItem('vscode.nls.locale');
- return true;
+
+ if (Language.value() === navigator.language) {
+ return;
+ }
+
+ const restartDialog = await this.dialogService.confirm({
+ type: 'info',
+ message: localize('clearDisplayLanguageMessage', "To change the display language, {0} needs to reload", this.productService.nameLong),
+ detail: localize('clearDisplayLanguageDetail', "Press the reload button to refresh the page and use your browser's language."),
+ primaryButton: localize({ key: 'reload', comment: ['&& denotes a mnemonic character'] }, "&&Reload"),
+ });
+
+ if (restartDialog.confirmed) {
+ this.hostService.restart();
+ }
}
}
diff --git a/src/vs/workbench/contrib/localization/browser/localizationsActions.ts b/src/vs/workbench/contrib/localization/browser/localizationsActions.ts
index 5bdb7500724..2be6435942d 100644
--- a/src/vs/workbench/contrib/localization/browser/localizationsActions.ts
+++ b/src/vs/workbench/contrib/localization/browser/localizationsActions.ts
@@ -5,9 +5,6 @@
import { localize } from 'vs/nls';
import { IQuickInputService, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
-import { IHostService } from 'vs/workbench/services/host/browser/host';
-import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
-import { IProductService } from 'vs/platform/product/common/productService';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { Action2, MenuId } from 'vs/platform/actions/common/actions';
@@ -15,8 +12,6 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation
import { ILanguagePackItem, ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks';
import { ILocaleService } from 'vs/workbench/contrib/localization/common/locale';
-const restart = localize('restart', "&&Restart");
-
export class ConfigureDisplayLanguageAction extends Action2 {
public static readonly ID = 'workbench.action.configureLocale';
public static readonly LABEL = localize('configureLocale', "Configure Display Language");
@@ -34,9 +29,6 @@ export class ConfigureDisplayLanguageAction extends Action2 {
public async run(accessor: ServicesAccessor): Promise<void> {
const languagePackService: ILanguagePackService = accessor.get(ILanguagePackService);
const quickInputService: IQuickInputService = accessor.get(IQuickInputService);
- const hostService: IHostService = accessor.get(IHostService);
- const dialogService: IDialogService = accessor.get(IDialogService);
- const productService: IProductService = accessor.get(IProductService);
const localeService: ILocaleService = accessor.get(ILocaleService);
const installedLanguages = await languagePackService.getInstalledLanguages();
@@ -72,19 +64,7 @@ export class ConfigureDisplayLanguageAction extends Action2 {
disposables.add(qp.onDidAccept(async () => {
const selectedLanguage = qp.activeItems[0];
qp.hide();
-
- if (await localeService.setLocale(selectedLanguage)) {
- const restartDialog = await dialogService.confirm({
- type: 'info',
- message: localize('relaunchDisplayLanguageMessage', "A restart is required for the change in display language to take effect."),
- detail: localize('relaunchDisplayLanguageDetail', "Press the restart button to restart {0} and change the display language.", productService.nameLong),
- primaryButton: restart
- });
-
- if (restartDialog.confirmed) {
- hostService.restart();
- }
- }
+ await localeService.setLocale(selectedLanguage);
}));
qp.show();
@@ -108,21 +88,6 @@ export class ClearDisplayLanguageAction extends Action2 {
public async run(accessor: ServicesAccessor): Promise<void> {
const localeService: ILocaleService = accessor.get(ILocaleService);
- const dialogService: IDialogService = accessor.get(IDialogService);
- const productService: IProductService = accessor.get(IProductService);
- const hostService: IHostService = accessor.get(IHostService);
-
- if (await localeService.clearLocalePreference()) {
- const restartDialog = await dialogService.confirm({
- type: 'info',
- message: localize('relaunchAfterClearDisplayLanguageMessage', "A restart is required for the change in display language to take effect."),
- detail: localize('relaunchAfterClearDisplayLanguageDetail', "Press the restart button to restart {0} and change the display language.", productService.nameLong),
- primaryButton: restart
- });
-
- if (restartDialog.confirmed) {
- hostService.restart();
- }
- }
+ await localeService.clearLocalePreference();
}
}
diff --git a/src/vs/workbench/contrib/localization/common/locale.ts b/src/vs/workbench/contrib/localization/common/locale.ts
index f447d40bc41..f2e8417c443 100644
--- a/src/vs/workbench/contrib/localization/common/locale.ts
+++ b/src/vs/workbench/contrib/localization/common/locale.ts
@@ -10,6 +10,6 @@ export const ILocaleService = createDecorator<ILocaleService>('localizationServi
export interface ILocaleService {
readonly _serviceBrand: undefined;
- setLocale(languagePackItem: ILanguagePackItem): Promise<boolean>;
- clearLocalePreference(): Promise<boolean>;
+ setLocale(languagePackItem: ILanguagePackItem): Promise<void>;
+ clearLocalePreference(): Promise<void>;
}
diff --git a/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts b/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts
index 00a6ec7036f..4c0da0bc253 100644
--- a/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts
+++ b/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts
@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { language } from 'vs/base/common/platform';
+import { Language } from 'vs/base/common/platform';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing';
@@ -19,6 +19,9 @@ import { toAction } from 'vs/base/common/actions';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { stripComments } from 'vs/base/common/stripComments';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
+import { IHostService } from 'vs/workbench/services/host/browser/host';
+import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
+import { IProductService } from 'vs/platform/product/common/productService';
export class NativeLocaleService implements ILocaleService {
_serviceBrand: undefined;
@@ -32,7 +35,10 @@ export class NativeLocaleService implements ILocaleService {
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IProgressService private readonly progressService: IProgressService,
@ITextFileService private readonly textFileService: ITextFileService,
- @IEditorService private readonly editorService: IEditorService
+ @IEditorService private readonly editorService: IEditorService,
+ @IDialogService private readonly dialogService: IDialogService,
+ @IHostService private readonly hostService: IHostService,
+ @IProductService private readonly productService: IProductService
) { }
private async validateLocaleFile(): Promise<boolean> {
@@ -69,10 +75,10 @@ export class NativeLocaleService implements ILocaleService {
return true;
}
- async setLocale(languagePackItem: ILanguagePackItem): Promise<boolean> {
+ async setLocale(languagePackItem: ILanguagePackItem): Promise<void> {
const locale = languagePackItem.id;
- if (locale === language || (!locale && language === 'en')) {
- return false;
+ if (locale === Language.value() || (!locale && Language.isDefaultVariant())) {
+ return;
}
const installedLanguages = await this.languagePackService.getInstalledLanguages();
try {
@@ -87,7 +93,7 @@ export class NativeLocaleService implements ILocaleService {
// as of now, there are no 3rd party language packs available on the Marketplace.
const viewlet = await this.paneCompositePartService.openPaneComposite(EXTENSIONS_VIEWLET_ID, ViewContainerLocation.Sidebar);
(viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer).search(`@id:${languagePackItem.extensionId}`);
- return false;
+ return;
}
await this.progressService.withProgress(
@@ -102,22 +108,40 @@ export class NativeLocaleService implements ILocaleService {
);
}
- return await this.writeLocaleValue(locale);
+ if (await this.writeLocaleValue(locale)) {
+ await this.showRestartDialog(languagePackItem.label);
+ }
} catch (err) {
this.notificationService.error(err);
- return false;
}
}
- async clearLocalePreference(): Promise<boolean> {
- if (language === 'en') {
- return false;
- }
+ async clearLocalePreference(): Promise<void> {
try {
- return await this.writeLocaleValue(undefined);
+ await this.writeLocaleValue(undefined);
+ if (!Language.isDefaultVariant()) {
+ await this.showRestartDialog('English');
+ }
} catch (err) {
this.notificationService.error(err);
- return false;
+ }
+ }
+
+ private async showRestartDialog(languageName: string) {
+ const restartDialog = await this.dialogService.confirm({
+ type: 'info',
+ message: localize('restartDisplayLanguageMessage', "To change the display language, {0} needs to restart", this.productService.nameLong),
+ detail: localize(
+ 'restartDisplayLanguageDetail',
+ "Press the restart button to restart {0} and set the display language to {1}.",
+ this.productService.nameLong,
+ languageName
+ ),
+ primaryButton: localize({ key: 'restart', comment: ['&& denotes a mnemonic character'] }, "&&Restart"),
+ });
+
+ if (restartDialog.confirmed) {
+ this.hostService.restart();
}
}
}
diff --git a/src/vs/workbench/contrib/logs/common/logConstants.ts b/src/vs/workbench/contrib/logs/common/logConstants.ts
index 9ba3e7aa0ee..4342dd4a740 100644
--- a/src/vs/workbench/contrib/logs/common/logConstants.ts
+++ b/src/vs/workbench/contrib/logs/common/logConstants.ts
@@ -9,5 +9,6 @@ export const rendererLogChannelId = 'rendererLog';
export const extHostLogChannelId = 'extHostLog';
export const telemetryLogChannelId = 'telemetryLog';
export const userDataSyncLogChannelId = 'userDataSyncLog';
+export const editSessionsLogChannelId = 'editSessionsSyncLog';
export const showWindowLogActionId = 'workbench.action.showWindowLog';
diff --git a/src/vs/workbench/contrib/logs/common/logs.contribution.ts b/src/vs/workbench/contrib/logs/common/logs.contribution.ts
index 5e533149305..f45fe793b09 100644
--- a/src/vs/workbench/contrib/logs/common/logs.contribution.ts
+++ b/src/vs/workbench/contrib/logs/common/logs.contribution.ts
@@ -38,6 +38,7 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution {
private registerCommonContributions(): void {
this.registerLogChannel(Constants.userDataSyncLogChannelId, nls.localize('userDataSyncLog', "Settings Sync"), this.environmentService.userDataSyncLogResource);
+ this.registerLogChannel(Constants.editSessionsLogChannelId, nls.localize('editSessionsLog', "Edit Sessions"), this.environmentService.editSessionsLogResource);
this.registerLogChannel(Constants.rendererLogChannelId, nls.localize('rendererLog', "Window"), this.environmentService.logFile);
const registerTelemetryChannel = () => {
diff --git a/src/vs/workbench/contrib/markers/browser/markersTable.ts b/src/vs/workbench/contrib/markers/browser/markersTable.ts
index a4938d08d58..4d39773daed 100644
--- a/src/vs/workbench/contrib/markers/browser/markersTable.ts
+++ b/src/vs/workbench/contrib/markers/browser/markersTable.ts
@@ -135,15 +135,17 @@ class MarkerCodeColumnRenderer implements ITableRenderer<MarkerTableItem, IMarke
}
renderElement(element: MarkerTableItem, index: number, templateData: IMarkerCodeColumnTemplateData, height: number | undefined): void {
- if (element.marker.source && element.marker.code) {
- templateData.codeColumn.classList.toggle('code-link', typeof element.marker.code !== 'string');
- DOM.show(templateData.codeLabel.element);
+ templateData.codeColumn.classList.remove('code-label');
+ templateData.codeColumn.classList.remove('code-link');
+ if (element.marker.source && element.marker.code) {
if (typeof element.marker.code === 'string') {
+ templateData.codeColumn.classList.add('code-label');
templateData.codeColumn.title = `${element.marker.source} (${element.marker.code})`;
templateData.sourceLabel.set(element.marker.source, element.sourceMatches);
templateData.codeLabel.set(element.marker.code, element.codeMatches);
} else {
+ templateData.codeColumn.classList.add('code-link');
templateData.codeColumn.title = `${element.marker.source} (${element.marker.code.value})`;
templateData.sourceLabel.set(element.marker.source, element.sourceMatches);
@@ -159,7 +161,6 @@ class MarkerCodeColumnRenderer implements ITableRenderer<MarkerTableItem, IMarke
} else {
templateData.codeColumn.title = '';
templateData.sourceLabel.set('-');
- DOM.hide(templateData.codeLabel.element);
}
}
diff --git a/src/vs/workbench/contrib/markers/browser/media/markers.css b/src/vs/workbench/contrib/markers/browser/media/markers.css
index 4ce704bb880..4a47972350e 100644
--- a/src/vs/workbench/contrib/markers/browser/media/markers.css
+++ b/src/vs/workbench/contrib/markers/browser/media/markers.css
@@ -279,17 +279,18 @@
content: ')';
}
-.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code.code-link > .code-label {
+.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code > .code-label,
+.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code > .monaco-link {
display: none;
}
-.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code.code-link > .monaco-link {
+.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code.code-label > .code-label {
display: inline;
- text-decoration: underline;
}
-.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code > .monaco-link {
- display: none;
+.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code.code-link > .monaco-link {
+ display: inline;
+ text-decoration: underline;
}
.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .file > .file-position {
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts
index 4fa542c981d..892febf378e 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts
@@ -6,9 +6,11 @@
import { Codicon } from 'vs/base/common/codicons';
import { URI, UriComponents } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
+import { ILocalizedString } from 'vs/platform/action/common/action';
import { Action2, MenuId } from 'vs/platform/actions/common/actions';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
+import { IOpenerService } from 'vs/platform/opener/common/opener';
import { API_OPEN_DIFF_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
import { MergeEditorInput, MergeEditorInputData } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput';
import { MergeEditor } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor';
@@ -19,7 +21,7 @@ export class OpenMergeEditor extends Action2 {
constructor() {
super({
id: '_open.mergeEditor',
- title: localize('title', "Open Merge Editor"),
+ title: { value: localize('title', "Open Merge Editor"), original: 'Open Merge Editor' },
});
}
run(accessor: ServicesAccessor, ...args: unknown[]): void {
@@ -111,14 +113,19 @@ export class SetMixedLayout extends Action2 {
constructor() {
super({
id: 'merge.mixedLayout',
- title: localize('layout.mixed', "Mixed Layout"),
+ title: {
+ value: localize('layout.mixed', 'Mixed Layout'),
+ original: 'Mixed Layout',
+ },
toggled: ctxMergeEditorLayout.isEqualTo('mixed'),
- menu: [{
- id: MenuId.EditorTitle,
- when: ctxIsMergeEditor,
- group: '1_merge',
- order: 9,
- }],
+ menu: [
+ {
+ id: MenuId.EditorTitle,
+ when: ctxIsMergeEditor,
+ group: '1_merge',
+ order: 9,
+ },
+ ],
precondition: ctxIsMergeEditor,
});
}
@@ -135,7 +142,7 @@ export class SetColumnLayout extends Action2 {
constructor() {
super({
id: 'merge.columnLayout',
- title: localize('layout.column', "Column Layout"),
+ title: { value: localize('layout.column', "Column Layout"), original: 'Column Layout' },
toggled: ctxMergeEditorLayout.isEqualTo('columns'),
menu: [{
id: MenuId.EditorTitle,
@@ -155,18 +162,28 @@ export class SetColumnLayout extends Action2 {
}
}
+const mergeEditorCategory: ILocalizedString = {
+ value: localize('mergeEditor', 'Merge Editor'),
+ original: 'Merge Editor',
+};
+
export class GoToNextConflict extends Action2 {
constructor() {
super({
id: 'merge.goToNextConflict',
- category: localize('mergeEditor', "Merge Editor"),
- title: localize('merge.goToNextConflict', "Go to Next Conflict"),
+ category: mergeEditorCategory,
+ title: {
+ value: localize('merge.goToNextConflict', 'Go to Next Conflict'),
+ original: 'Go to Next Conflict',
+ },
icon: Codicon.arrowDown,
- menu: [{
- id: MenuId.EditorTitle,
- when: ctxIsMergeEditor,
- group: 'navigation',
- }],
+ menu: [
+ {
+ id: MenuId.EditorTitle,
+ when: ctxIsMergeEditor,
+ group: 'navigation',
+ },
+ ],
f1: true,
precondition: ctxIsMergeEditor,
});
@@ -175,7 +192,7 @@ export class GoToNextConflict extends Action2 {
run(accessor: ServicesAccessor): void {
const { activeEditorPane } = accessor.get(IEditorService);
if (activeEditorPane instanceof MergeEditor) {
- activeEditorPane.viewModel.get()?.goToNextConflict();
+ activeEditorPane.viewModel.get()?.goToNextModifiedBaseRange(true);
}
}
}
@@ -184,14 +201,22 @@ export class GoToPreviousConflict extends Action2 {
constructor() {
super({
id: 'merge.goToPreviousConflict',
- category: localize('mergeEditor', "Merge Editor"),
- title: localize('merge.goToPreviousConflict', "Go to Previous Conflict"),
+ category: mergeEditorCategory,
+ title: {
+ value: localize(
+ 'merge.goToPreviousConflict',
+ 'Go to Previous Conflict'
+ ),
+ original: 'Go to Previous Conflict',
+ },
icon: Codicon.arrowUp,
- menu: [{
- id: MenuId.EditorTitle,
- when: ctxIsMergeEditor,
- group: 'navigation',
- }],
+ menu: [
+ {
+ id: MenuId.EditorTitle,
+ when: ctxIsMergeEditor,
+ group: 'navigation',
+ },
+ ],
f1: true,
precondition: ctxIsMergeEditor,
});
@@ -200,7 +225,7 @@ export class GoToPreviousConflict extends Action2 {
run(accessor: ServicesAccessor): void {
const { activeEditorPane } = accessor.get(IEditorService);
if (activeEditorPane instanceof MergeEditor) {
- activeEditorPane.viewModel.get()?.goToPreviousConflict();
+ activeEditorPane.viewModel.get()?.goToPreviousModifiedBaseRange(true);
}
}
}
@@ -209,8 +234,14 @@ export class ToggleActiveConflictInput1 extends Action2 {
constructor() {
super({
id: 'merge.toggleActiveConflictInput1',
- category: localize('mergeEditor', "Merge Editor"),
- title: localize('merge.toggleCurrentConflictFromLeft', "Toggle Current Conflict from Left"),
+ category: mergeEditorCategory,
+ title: {
+ value: localize(
+ 'merge.toggleCurrentConflictFromLeft',
+ 'Toggle Current Conflict from Left'
+ ),
+ original: 'Toggle Current Conflict from Left',
+ },
f1: true,
precondition: ctxIsMergeEditor,
});
@@ -232,8 +263,14 @@ export class ToggleActiveConflictInput2 extends Action2 {
constructor() {
super({
id: 'merge.toggleActiveConflictInput2',
- category: localize('mergeEditor', "Merge Editor"),
- title: localize('merge.toggleCurrentConflictFromRight', "Toggle Current Conflict from Right"),
+ category: mergeEditorCategory,
+ title: {
+ value: localize(
+ 'merge.toggleCurrentConflictFromRight',
+ 'Toggle Current Conflict from Right'
+ ),
+ original: 'Toggle Current Conflict from Right',
+ },
f1: true,
precondition: ctxIsMergeEditor,
});
@@ -255,8 +292,14 @@ export class CompareInput1WithBaseCommand extends Action2 {
constructor() {
super({
id: 'mergeEditor.compareInput1WithBase',
- category: localize('mergeEditor', "Merge Editor"),
- title: localize('mergeEditor.compareInput1WithBase', "Compare Input 1 With Base"),
+ category: mergeEditorCategory,
+ title: {
+ value: localize(
+ 'mergeEditor.compareInput1WithBase',
+ 'Compare Input 1 With Base'
+ ),
+ original: 'Compare Input 1 With Base',
+ },
f1: true,
precondition: ctxIsMergeEditor,
});
@@ -272,8 +315,14 @@ export class CompareInput2WithBaseCommand extends Action2 {
constructor() {
super({
id: 'mergeEditor.compareInput2WithBase',
- category: localize('mergeEditor', "Merge Editor"),
- title: localize('mergeEditor.compareInput2WithBase', "Compare Input 2 With Base"),
+ category: mergeEditorCategory,
+ title: {
+ value: localize(
+ 'mergeEditor.compareInput2WithBase',
+ 'Compare Input 2 With Base'
+ ),
+ original: 'Compare Input 2 With Base',
+ },
f1: true,
precondition: ctxIsMergeEditor,
});
@@ -302,3 +351,30 @@ function mergeEditorCompare(editorService: IEditorService, commandService: IComm
function openDiffEditor(commandService: ICommandService, left: URI, right: URI, label?: string) {
commandService.executeCommand(API_OPEN_DIFF_EDITOR_COMMAND_ID, left, right, label);
}
+
+export class OpenBaseFile extends Action2 {
+ constructor() {
+ super({
+ id: 'merge.openBaseEditor',
+ category: mergeEditorCategory,
+ title: {
+ value: localize('merge.openBaseEditor', 'Open Base File'),
+ original: 'Open Base File',
+ },
+ f1: true,
+ precondition: ctxIsMergeEditor,
+ });
+ }
+
+ run(accessor: ServicesAccessor): void {
+ const openerService = accessor.get(IOpenerService);
+ const { activeEditorPane } = accessor.get(IEditorService);
+ if (activeEditorPane instanceof MergeEditor) {
+ const vm = activeEditorPane.viewModel.get();
+ if (!vm) {
+ return;
+ }
+ openerService.open(vm.model.base.uri);
+ }
+ }
+}
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts
index 83a458d15f5..6728dc93b1a 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts
@@ -29,12 +29,17 @@ interface MergeEditorContents {
}
export class MergeEditorCopyContentsToJSON extends Action2 {
-
constructor() {
super({
id: 'merge.dev.copyContents',
category: 'Merge Editor (Dev)',
- title: localize('merge.dev.copyContents', "Copy Contents of Inputs, Base and Result as JSON"),
+ title: {
+ value: localize(
+ 'merge.dev.copyContents',
+ 'Copy Contents of Inputs, Base and Result as JSON'
+ ),
+ original: 'Copy Contents of Inputs, Base and Result as JSON',
+ },
icon: Codicon.layoutCentered,
f1: true,
precondition: ctxIsMergeEditor,
@@ -75,12 +80,17 @@ export class MergeEditorCopyContentsToJSON extends Action2 {
}
export class MergeEditorOpenContents extends Action2 {
-
constructor() {
super({
id: 'merge.dev.openContents',
category: 'Merge Editor (Dev)',
- title: localize('merge.dev.openContents', "Open Contents of Inputs, Base and Result from JSON"),
+ title: {
+ value: localize(
+ 'merge.dev.openContents',
+ 'Open Contents of Inputs, Base and Result from JSON'
+ ),
+ original: 'Open Contents of Inputs, Base and Result from JSON',
+ },
icon: Codicon.layoutCentered,
f1: true,
});
@@ -98,11 +108,14 @@ export class MergeEditorOpenContents extends Action2 {
prompt: localize('mergeEditor.enterJSON', 'Enter JSON'),
value: await clipboardService.readText(),
});
- if (!result) {
+ if (result === undefined) {
return;
}
- const content: MergeEditorContents = JSON.parse(result);
+ const content: MergeEditorContents =
+ result !== ''
+ ? JSON.parse(result)
+ : { base: '', input1: '', input2: '', result: '', languageId: 'plaintext' };
const scheme = 'merge-editor-dev';
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts
index 136249c41fa..09206f43520 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts
@@ -8,11 +8,13 @@ import { registerAction2 } from 'vs/platform/actions/common/actions';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { Registry } from 'vs/platform/registry/common/platform';
import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor';
+import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor';
-import { CompareInput1WithBaseCommand, CompareInput2WithBaseCommand, GoToNextConflict, GoToPreviousConflict, OpenMergeEditor, ToggleActiveConflictInput1, ToggleActiveConflictInput2, SetColumnLayout, SetMixedLayout } from 'vs/workbench/contrib/mergeEditor/browser/commands/commands';
+import { CompareInput1WithBaseCommand, CompareInput2WithBaseCommand, GoToNextConflict, GoToPreviousConflict, OpenBaseFile, OpenMergeEditor, SetColumnLayout, SetMixedLayout, ToggleActiveConflictInput1, ToggleActiveConflictInput2 } from 'vs/workbench/contrib/mergeEditor/browser/commands/commands';
import { MergeEditorCopyContentsToJSON, MergeEditorOpenContents } from 'vs/workbench/contrib/mergeEditor/browser/commands/devCommands';
import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput';
-import { MergeEditor } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor';
+import { MergeEditor, MergeEditorOpenHandlerContribution } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor';
+import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { MergeEditorSerializer } from './mergeEditorSerializer';
Registry.as<IEditorPaneRegistry>(EditorExtensions.EditorPane).registerEditorPane(
@@ -34,6 +36,7 @@ Registry.as<IEditorFactoryRegistry>(EditorExtensions.EditorFactory).registerEdit
registerAction2(SetMixedLayout);
registerAction2(SetColumnLayout);
registerAction2(OpenMergeEditor);
+registerAction2(OpenBaseFile);
registerAction2(MergeEditorCopyContentsToJSON);
registerAction2(MergeEditorOpenContents);
@@ -46,3 +49,8 @@ registerAction2(ToggleActiveConflictInput2);
registerAction2(CompareInput1WithBaseCommand);
registerAction2(CompareInput2WithBaseCommand);
+
+
+Registry
+ .as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
+ .registerWorkbenchContribution(MergeEditorOpenHandlerContribution, LifecyclePhase.Restored);
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts
index 87aa4ac9b0c..4f29941dc13 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts
@@ -14,14 +14,13 @@ import { IFileService } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILabelService } from 'vs/platform/label/common/label';
import { IEditorIdentifier, IUntypedEditorInput } from 'vs/workbench/common/editor';
-import { EditorInput } from 'vs/workbench/common/editor/editorInput';
+import { EditorInput, IEditorCloseHandler } from 'vs/workbench/common/editor/editorInput';
import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput';
import { EditorWorkerServiceDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer';
-import { autorun } from 'vs/workbench/contrib/audioCues/browser/observable';
import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
-import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { ILanguageSupport, ITextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
+import { autorun } from 'vs/base/common/observable';
export class MergeEditorInputData {
constructor(
@@ -38,7 +37,8 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
private _model?: MergeEditorModel;
private _outTextModel?: ITextFileEditorModel;
- private _ignoreUnhandledConflictsForDirtyState?: true;
+
+ override closeHandler: MergeEditorCloseHandler | undefined;
constructor(
public readonly base: URI,
@@ -47,8 +47,6 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
public readonly result: URI,
@IInstantiationService private readonly _instaService: IInstantiationService,
@ITextModelService private readonly _textModelService: ITextModelService,
- @IDialogService private readonly _dialogService: IDialogService,
- @IFilesConfigurationService private readonly _filesConfigurationService: IFilesConfigurationService,
@IEditorService editorService: IEditorService,
@ITextFileService textFileService: ITextFileService,
@ILabelService labelService: ILabelService,
@@ -112,8 +110,18 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
this.input2.description,
result.object.textEditorModel,
this._instaService.createInstance(EditorWorkerServiceDiffComputer),
+ {
+ resetUnknownOnInitialization: true
+ },
);
+ // set/unset the closeHandler whenever unhandled conflicts are detected
+ const closeHandler = this._instaService.createInstance(MergeEditorCloseHandler, this._model);
+ this._store.add(autorun('closeHandler', reader => {
+ const value = this._model!.hasUnhandledConflicts.read(reader);
+ this.closeHandler = value ? closeHandler : undefined;
+ }));
+
await this._model.onInitialized;
this._store.add(this._model);
@@ -121,14 +129,8 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
this._store.add(input1);
this._store.add(input2);
this._store.add(result);
-
- this._store.add(autorun(reader => {
- this._model?.hasUnhandledConflicts.read(reader);
- this._onDidChangeDirty.fire(undefined);
- }, 'drive::onDidChangeDirty'));
}
- this._ignoreUnhandledConflictsForDirtyState = undefined;
return this._model;
}
@@ -145,64 +147,57 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
// ---- FileEditorInput
override isDirty(): boolean {
- const textModelDirty = Boolean(this._outTextModel?.isDirty());
- if (textModelDirty) {
- // text model dirty -> 3wm is dirty
- return true;
- }
- if (!this._ignoreUnhandledConflictsForDirtyState) {
- // unhandled conflicts -> 3wm is dirty UNLESS we explicitly set this input
- // to ignore unhandled conflicts for the dirty-state. This happens only
- // after confirming to ignore unhandled changes
- return Boolean(this._model && this._model.hasUnhandledConflicts.get());
- }
- return false;
+ return Boolean(this._outTextModel?.isDirty());
}
- override async confirm(editors?: ReadonlyArray<IEditorIdentifier>): Promise<ConfirmResult> {
+ setLanguageId(languageId: string, _setExplicitly?: boolean): void {
+ this._model?.setLanguageId(languageId);
+ }
- const inputs: MergeEditorInput[] = [this];
- if (editors) {
- for (const { editor } of editors) {
- if (editor instanceof MergeEditorInput) {
- inputs.push(editor);
- }
- }
- }
+ // implement get/set languageId
+ // implement get/set encoding
+}
+
+class MergeEditorCloseHandler implements IEditorCloseHandler {
+
+ private _ignoreUnhandledConflicts: boolean = false;
+
+ constructor(
+ private readonly _model: MergeEditorModel,
+ @IDialogService private readonly _dialogService: IDialogService,
+ ) { }
+
+ showConfirm(): boolean {
+ // unhandled conflicts -> 3wm asks to confirm UNLESS we explicitly set this input
+ // to ignore unhandled conflicts. This happens only after confirming to ignore unhandled changes
+ return !this._ignoreUnhandledConflicts && this._model.hasUnhandledConflicts.get();
+ }
- const inputsWithUnhandledConflicts = inputs
+ async confirm(editors?: readonly IEditorIdentifier[] | undefined): Promise<ConfirmResult> {
+
+ const handler: MergeEditorCloseHandler[] = [this];
+ editors?.forEach(candidate => candidate.editor.closeHandler instanceof MergeEditorCloseHandler && handler.push(candidate.editor.closeHandler));
+
+ const inputsWithUnhandledConflicts = handler
.filter(input => input._model && input._model.hasUnhandledConflicts.get());
if (inputsWithUnhandledConflicts.length === 0) {
+ // shouldn't happen
return ConfirmResult.SAVE;
}
- const actions: string[] = [];
+ const actions: string[] = [
+ localize('unhandledConflicts.ignore', "Continue with Conflicts"),
+ localize('unhandledConflicts.discard', "Discard Merge Changes"),
+ localize('unhandledConflicts.cancel', "Cancel"),
+ ];
const options = {
- cancelId: 0,
- detail: inputs.length > 1
- ? localize('unhandledConflicts.detailN', 'Merge conflicts in {0} editors will remain unhandled.', inputs.length)
+ cancelId: 2,
+ detail: handler.length > 1
+ ? localize('unhandledConflicts.detailN', 'Merge conflicts in {0} editors will remain unhandled.', handler.length)
: localize('unhandledConflicts.detail1', 'Merge conflicts in this editor will remain unhandled.')
};
- const isAnyAutoSave = this._filesConfigurationService.getAutoSaveMode() !== AutoSaveMode.OFF;
- if (!isAnyAutoSave) {
- // manual-save: FYI and discard
- actions.push(
- localize('unhandledConflicts.manualSaveIgnore', "Save and Continue with Conflicts"), // 0
- localize('unhandledConflicts.manualSaveNoSave', "Don't Save") // 1
- );
-
- } else {
- // auto-save: only FYI
- actions.push(
- localize('unhandledConflicts.ignore', "Continue with Conflicts"), // 0
- );
- }
-
- actions.push(localize('unhandledConflicts.cancel', "Cancel"));
- options.cancelId = actions.length - 1;
-
const { choice } = await this._dialogService.show(
Severity.Info,
localize('unhandledConflicts.msg', 'Do you want to continue with unhandled conflicts?'), // 1
@@ -217,23 +212,36 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
// save or revert: in both cases we tell the inputs to ignore unhandled conflicts
// for the dirty state computation.
- for (const input of inputs) {
- input._ignoreUnhandledConflictsForDirtyState = true;
+ for (const input of handler) {
+ input._ignoreUnhandledConflicts = true;
}
if (choice === 0) {
// conflicts: continue with remaining conflicts
return ConfirmResult.SAVE;
- }
- // don't save
- return ConfirmResult.DONT_SAVE;
- }
+ } else if (choice === 1) {
+ // discard: undo all changes and save original (pre-merge) state
+ for (const input of handler) {
+ input._discardMergeChanges();
+ }
+ return ConfirmResult.SAVE;
- setLanguageId(languageId: string, _setExplicitly?: boolean): void {
- this._model?.setLanguageId(languageId);
+ } else {
+ // don't save
+ return ConfirmResult.DONT_SAVE;
+ }
}
- // implement get/set languageId
- // implement get/set encoding
+ private _discardMergeChanges(): void {
+ const chunks: string[] = [];
+ while (true) {
+ const chunk = this._model.resultSnapshot.read();
+ if (chunk === null) {
+ break;
+ }
+ chunks.push(chunk);
+ }
+ this._model.result.setValue(chunks.join());
+ }
}
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts
index d9f61d89baf..255615448d3 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts
@@ -5,11 +5,11 @@
import { CompareResult, equals } from 'vs/base/common/arrays';
import { BugIndicatingError } from 'vs/base/common/errors';
+import { ISettableObservable, derived, waitForState, observableValue, keepAlive, autorunHandleChanges, transaction, IReader, ITransaction, IObservable } from 'vs/base/common/observable';
import { ILanguageService } from 'vs/editor/common/languages/language';
-import { ITextModel } from 'vs/editor/common/model';
+import { ITextModel, ITextSnapshot } from 'vs/editor/common/model';
import { IModelService } from 'vs/editor/common/services/model';
import { EditorModel } from 'vs/workbench/common/editor/editorModel';
-import { autorunHandleChanges, derivedObservable, IObservable, IReader, ITransaction, keepAlive, ObservableValue, transaction, waitForState } from 'vs/workbench/contrib/audioCues/browser/observable';
import { IDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer';
import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';
import { DetailedLineRangeMapping, DocumentMapping, LineRangeMapping } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping';
@@ -28,7 +28,7 @@ export class MergeEditorModel extends EditorModel {
private readonly input2TextModelDiffs = this._register(new TextModelDiffs(this.base, this.input2, this.diffComputer));
private readonly resultTextModelDiffs = this._register(new TextModelDiffs(this.base, this.result, this.diffComputer));
- public readonly state = derivedObservable('state', reader => {
+ public readonly state = derived('state', reader => {
const states = [
this.input1TextModelDiffs,
this.input2TextModelDiffs,
@@ -44,11 +44,11 @@ export class MergeEditorModel extends EditorModel {
return MergeEditorModelState.upToDate;
});
- public readonly isUpToDate = derivedObservable('isUpdating', reader => this.state.read(reader) === MergeEditorModelState.upToDate);
+ public readonly isUpToDate = derived('isUpToDate', reader => this.state.read(reader) === MergeEditorModelState.upToDate);
public readonly onInitialized = waitForState(this.state, state => state === MergeEditorModelState.upToDate);
- public readonly modifiedBaseRanges = derivedObservable<ModifiedBaseRange[]>('modifiedBaseRanges', (reader) => {
+ public readonly modifiedBaseRanges = derived<ModifiedBaseRange[]>('modifiedBaseRanges', (reader) => {
const input1Diffs = this.input1TextModelDiffs.diffs.read(reader);
const input2Diffs = this.input2TextModelDiffs.diffs.read(reader);
@@ -60,22 +60,22 @@ export class MergeEditorModel extends EditorModel {
public readonly resultDiffs = this.resultTextModelDiffs.diffs;
private readonly modifiedBaseRangeStateStores =
- derivedObservable('modifiedBaseRangeStateStores', reader => {
+ derived('modifiedBaseRangeStateStores', reader => {
const map = new Map(
- this.modifiedBaseRanges.read(reader).map(s => ([s, new ObservableValue(ModifiedBaseRangeState.default, 'State')]))
+ this.modifiedBaseRanges.read(reader).map(s => ([s, observableValue(`BaseRangeState${s.baseRange}`, ModifiedBaseRangeState.default)]))
);
return map;
});
private readonly modifiedBaseRangeHandlingStateStores =
- derivedObservable('modifiedBaseRangeHandlingStateStores', reader => {
+ derived('modifiedBaseRangeHandlingStateStores', reader => {
const map = new Map(
- this.modifiedBaseRanges.read(reader).map(s => ([s, new ObservableValue(false, 'State')]))
+ this.modifiedBaseRanges.read(reader).map(s => ([s, observableValue(`BaseRangeHandledState${s.baseRange}`, false)]))
);
return map;
});
- public readonly unhandledConflictsCount = derivedObservable('unhandledConflictsCount', reader => {
+ public readonly unhandledConflictsCount = derived('unhandledConflictsCount', reader => {
const map = this.modifiedBaseRangeHandlingStateStores.read(reader);
let handledCount = 0;
for (const [_key, value] of map) {
@@ -84,9 +84,9 @@ export class MergeEditorModel extends EditorModel {
return map.size - handledCount;
});
- public readonly hasUnhandledConflicts = this.unhandledConflictsCount.map(value => value > 0);
+ public readonly hasUnhandledConflicts = this.unhandledConflictsCount.map(value => /** @description hasUnhandledConflicts */ value > 0);
- public readonly input1ResultMapping = derivedObservable('input1ResultMapping', reader => {
+ public readonly input1ResultMapping = derived('input1ResultMapping', reader => {
const resultDiffs = this.resultDiffs.read(reader);
const modifiedBaseRanges = DocumentMapping.betweenOutputs(this.input1LinesDiffs.read(reader), resultDiffs, this.input1.getLineCount());
@@ -103,7 +103,7 @@ export class MergeEditorModel extends EditorModel {
);
});
- public readonly input2ResultMapping = derivedObservable('input2ResultMapping', reader => {
+ public readonly input2ResultMapping = derived('input2ResultMapping', reader => {
const resultDiffs = this.resultDiffs.read(reader);
const modifiedBaseRanges = DocumentMapping.betweenOutputs(this.input2LinesDiffs.read(reader), resultDiffs, this.input2.getLineCount());
@@ -120,6 +120,8 @@ export class MergeEditorModel extends EditorModel {
);
});
+ readonly resultSnapshot: ITextSnapshot;
+
constructor(
readonly base: ITextModel,
readonly input1: ITextModel,
@@ -132,11 +134,13 @@ export class MergeEditorModel extends EditorModel {
readonly input2Description: string | undefined,
readonly result: ITextModel,
private readonly diffComputer: IDiffComputer,
+ options: { resetUnknownOnInitialization: boolean },
@IModelService private readonly modelService: IModelService,
@ILanguageService private readonly languageService: ILanguageService,
) {
super();
+ this.resultSnapshot = result.createSnapshot();
this._register(keepAlive(this.modifiedBaseRangeStateStores));
this._register(keepAlive(this.modifiedBaseRangeHandlingStateStores));
this._register(keepAlive(this.input1ResultMapping));
@@ -145,7 +149,7 @@ export class MergeEditorModel extends EditorModel {
let shouldResetHandlingState = true;
this._register(
autorunHandleChanges(
- 'Recompute State',
+ 'Merge Editor Model: Recompute State',
{
handleChange: (ctx) => {
if (ctx.didChange(this.modifiedBaseRangeHandlingStateStores)) {
@@ -165,6 +169,7 @@ export class MergeEditorModel extends EditorModel {
const resultDiffs = this.resultTextModelDiffs.diffs.read(reader);
const stores = this.modifiedBaseRangeStateStores.read(reader);
transaction(tx => {
+ /** @description Merge Editor Model: Recompute State */
this.recomputeState(resultDiffs, stores, tx);
if (shouldResetHandlingState) {
shouldResetHandlingState = false;
@@ -179,16 +184,18 @@ export class MergeEditorModel extends EditorModel {
)
);
- this.onInitialized.then(() => {
- this.resetUnknown();
- });
+ if (options.resetUnknownOnInitialization) {
+ this.onInitialized.then(() => {
+ this.resetUnknown();
+ });
+ }
}
public getRangeInResult(baseRange: LineRange, reader?: IReader): LineRange {
return this.resultTextModelDiffs.getResultRange(baseRange, reader);
}
- private recomputeState(resultDiffs: DetailedLineRangeMapping[], stores: Map<ModifiedBaseRange, ObservableValue<ModifiedBaseRangeState>>, tx: ITransaction): void {
+ private recomputeState(resultDiffs: DetailedLineRangeMapping[], stores: Map<ModifiedBaseRange, ISettableObservable<ModifiedBaseRangeState>>, tx: ITransaction): void {
const baseRangeWithStoreAndTouchingDiffs = leftJoin(
stores,
resultDiffs,
@@ -202,12 +209,16 @@ export class MergeEditorModel extends EditorModel {
);
for (const row of baseRangeWithStoreAndTouchingDiffs) {
- row.left[1].set(this.computeState(row.left[0], row.rights), tx);
+ const newState = this.computeState(row.left[0], row.rights);
+ if (!row.left[1].get().equals(newState)) {
+ row.left[1].set(newState, tx);
+ }
}
}
public resetUnknown(): void {
transaction(tx => {
+ /** @description Reset Unknown Base Range States */
for (const range of this.modifiedBaseRanges.get()) {
if (this.getState(range).get().conflicting) {
this.setState(range, ModifiedBaseRangeState.default, false, tx);
@@ -218,6 +229,7 @@ export class MergeEditorModel extends EditorModel {
public mergeNonConflictingDiffs(): void {
transaction((tx) => {
+ /** @description Merge None Conflicting Diffs */
for (const m of this.modifiedBaseRanges.get()) {
if (m.isConflicting) {
continue;
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange.ts
index 4dadbb6816a..2e6b6031382 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange.ts
@@ -281,18 +281,30 @@ export class ModifiedBaseRangeState {
}
public toString(): string {
- const arr: ('1' | '2')[] = [];
+ const arr: string[] = [];
if (this.input1) {
- arr.push('1');
+ arr.push('1✓');
}
if (this.input2) {
- arr.push('2');
+ arr.push('2✓');
}
if (this.input2First) {
arr.reverse();
}
+ if (this.conflicting) {
+ arr.push('conflicting');
+ }
return arr.join(',');
}
+
+ equals(other: ModifiedBaseRangeState): boolean {
+ return (
+ this.input1 === other.input1 &&
+ this.input2 === other.input2 &&
+ this.input2First === other.input2First &&
+ this.conflicting === other.conflicting
+ );
+ }
}
export const enum InputState {
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts
index c57c0ff51ea..af21adbebb5 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts
@@ -7,17 +7,17 @@ import { compareBy, numberComparator } from 'vs/base/common/arrays';
import { BugIndicatingError } from 'vs/base/common/errors';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { ITextModel } from 'vs/editor/common/model';
-import { IObservable, IReader, ITransaction, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable';
import { DetailedLineRangeMapping } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping';
import { LineRangeEdit } from 'vs/workbench/contrib/mergeEditor/browser/model/editing';
import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';
import { ReentrancyBarrier } from 'vs/workbench/contrib/mergeEditor/browser/utils';
import { IDiffComputer } from './diffComputer';
+import { IObservable, IReader, ITransaction, observableValue, transaction } from 'vs/base/common/observable';
export class TextModelDiffs extends Disposable {
private updateCount = 0;
- private readonly _state = new ObservableValue<TextModelDiffState, TextModelDiffChangeReason>(TextModelDiffState.initializing, 'LiveDiffState');
- private readonly _diffs = new ObservableValue<DetailedLineRangeMapping[], TextModelDiffChangeReason>([], 'LiveDiffs');
+ private readonly _state = observableValue<TextModelDiffState, TextModelDiffChangeReason>('LiveDiffState', TextModelDiffState.initializing);
+ private readonly _diffs = observableValue<DetailedLineRangeMapping[], TextModelDiffChangeReason>('LiveDiffs', []);
private readonly barrier = new ReentrancyBarrier();
private isDisposed = false;
@@ -54,6 +54,7 @@ export class TextModelDiffs extends Disposable {
}
transaction(tx => {
+ /** @description Starting Diff Computation. */
this._state.set(
initializing ? TextModelDiffState.initializing : TextModelDiffState.updating,
tx,
@@ -72,6 +73,7 @@ export class TextModelDiffs extends Disposable {
}
transaction(tx => {
+ /** @description Completed Diff Computation */
if (result.diffs) {
this._state.set(TextModelDiffState.upToDate, tx, TextModelDiffChangeReason.textChange);
this._diffs.set(result.diffs, tx, TextModelDiffChangeReason.textChange);
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/utils.ts b/src/vs/workbench/contrib/mergeEditor/browser/utils.ts
index 23277aeae95..7e92f970387 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/utils.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/utils.ts
@@ -5,11 +5,10 @@
import { CompareResult, ArrayQueue } from 'vs/base/common/arrays';
import { BugIndicatingError } from 'vs/base/common/errors';
-import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
+import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
+import { IObservable, autorun } from 'vs/base/common/observable';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { IModelDeltaDecoration } from 'vs/editor/common/model';
-import { IObservable, autorun } from 'vs/workbench/contrib/audioCues/browser/observable';
-import { IDisposable } from 'xterm';
export class ReentrancyBarrier {
private isActive = false;
@@ -74,12 +73,12 @@ function toSize(value: number | string): string {
export function applyObservableDecorations(editor: CodeEditorWidget, decorations: IObservable<IModelDeltaDecoration[]>): IDisposable {
const d = new DisposableStore();
let decorationIds: string[] = [];
- d.add(autorun(reader => {
+ d.add(autorun(`Apply decorations from ${decorations.debugName}`, reader => {
const d = decorations.read(reader);
editor.changeDecorations(a => {
decorationIds = a.deltaDecorations(decorationIds, d);
});
- }, 'Update Decorations'));
+ }));
d.add({
dispose: () => {
editor.changeDecorations(a => {
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts
index 2b208650738..7289cc61637 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts
@@ -5,22 +5,23 @@
import { h } from 'vs/base/browser/dom';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
+import { autorun, IReader, observableFromEvent, observableSignalFromEvent } from 'vs/base/common/observable';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
-import { EditorOption } from 'vs/editor/common/config/editorOptions';
-import { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';
import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';
export class EditorGutter<T extends IGutterItemInfo = IGutterItemInfo> extends Disposable {
private readonly scrollTop = observableFromEvent(
this._editor.onDidScrollChange,
- (e) => this._editor.getScrollTop()
+ (e) => /** @description editor.onDidScrollChange */ this._editor.getScrollTop()
);
+ private readonly isScrollTopZero = this.scrollTop.map((scrollTop) => /** @description isScrollTopZero */ scrollTop === 0);
private readonly modelAttached = observableFromEvent(
this._editor.onDidChangeModel,
- (e) => this._editor.hasModel()
+ (e) => /** @description editor.onDidChangeModel */ this._editor.hasModel()
);
- private readonly changeCounter = new ObservableValue(0, 'counter');
+ private readonly editorOnDidChangeViewZones = observableSignalFromEvent('onDidChangeViewZones', this._editor.onDidChangeViewZones);
+ private readonly editorOnDidContentSizeChange = observableSignalFromEvent('onDidContentSizeChange', this._editor.onDidContentSizeChange);
constructor(
private readonly _editor: CodeEditorWidget,
@@ -34,20 +35,11 @@ export class EditorGutter<T extends IGutterItemInfo = IGutterItemInfo> extends D
.root
);
- this._register(autorun((reader) => {
- scrollDecoration.className = this.scrollTop.read(reader) === 0 ? '' : 'scroll-decoration';
- }, 'update scroll decoration'));
+ this._register(autorun('update scroll decoration', (reader) => {
+ scrollDecoration.className = this.isScrollTopZero.read(reader) ? '' : 'scroll-decoration';
+ }));
-
- this._register(autorun((reader) => this.render(reader), 'Render'));
-
- this._editor.onDidChangeViewZones(e => {
- this.changeCounter.set(this.changeCounter.get() + 1, undefined);
- });
-
- this._editor.onDidContentSizeChange(e => {
- this.changeCounter.set(this.changeCounter.get() + 1, undefined);
- });
+ this._register(autorun('EditorGutter.Render', (reader) => this.render(reader)));
}
private readonly views = new Map<string, ManagedGutterItemView>();
@@ -56,7 +48,10 @@ export class EditorGutter<T extends IGutterItemInfo = IGutterItemInfo> extends D
if (!this.modelAttached.read(reader)) {
return;
}
- this.changeCounter.read(reader);
+
+ this.editorOnDidChangeViewZones.read(reader);
+ this.editorOnDidContentSizeChange.read(reader);
+
const scrollTop = this.scrollTop.read(reader);
const visibleRanges = this._editor.getVisibleRanges();
@@ -68,15 +63,13 @@ export class EditorGutter<T extends IGutterItemInfo = IGutterItemInfo> extends D
const visibleRange2 = new LineRange(
visibleRange.startLineNumber,
visibleRange.endLineNumber - visibleRange.startLineNumber
- );
+ ).deltaEnd(1);
const gutterItems = this.itemProvider.getIntersectingGutterItems(
visibleRange2,
reader
);
- const lineHeight = this._editor.getOptions().get(EditorOption.lineHeight);
-
for (const gutterItem of gutterItems) {
if (!gutterItem.range.touches(visibleRange2)) {
continue;
@@ -99,19 +92,10 @@ export class EditorGutter<T extends IGutterItemInfo = IGutterItemInfo> extends D
}
const top =
- (gutterItem.range.startLineNumber === 1
- ? -lineHeight
- : this._editor.getTopForLineNumber(
- gutterItem.range.startLineNumber - 1
- )) -
- scrollTop +
- lineHeight;
-
- const bottom = (
- gutterItem.range.endLineNumberExclusive <= this._editor.getModel()!.getLineCount()
- ? this._editor.getTopForLineNumber(gutterItem.range.endLineNumberExclusive)
- : this._editor.getTopForLineNumber(gutterItem.range.endLineNumberExclusive - 1) + lineHeight
- ) - scrollTop;
+ gutterItem.range.startLineNumber <= this._editor.getModel()!.getLineCount()
+ ? this._editor.getTopForLineNumber(gutterItem.range.startLineNumber, true) - scrollTop
+ : this._editor.getBottomForLineNumber(gutterItem.range.startLineNumber - 1, false) - scrollTop;
+ const bottom = this._editor.getBottomForLineNumber(gutterItem.range.endLineNumberExclusive - 1, true) - scrollTop;
const height = bottom - top;
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts
index e9a1b888ce0..321d1594dd5 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts
@@ -3,28 +3,32 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { h } from 'vs/base/browser/dom';
+import { h, reset } from 'vs/base/browser/dom';
import { IView, IViewSize } from 'vs/base/browser/ui/grid/grid';
-import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel';
+import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
+import { IObservable, observableFromEvent, observableValue, transaction } from 'vs/base/common/observable';
import { IEditorContributionDescription } from 'vs/editor/browser/editorExtensions';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { ITextModel } from 'vs/editor/common/model';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { DEFAULT_EDITOR_MAX_DIMENSIONS, DEFAULT_EDITOR_MIN_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor';
-import { IObservable, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';
import { setStyle } from 'vs/workbench/contrib/mergeEditor/browser/utils';
import { MergeEditorViewModel } from 'vs/workbench/contrib/mergeEditor/browser/view/viewModel';
export abstract class CodeEditorView extends Disposable {
- private readonly _viewModel = new ObservableValue<undefined | MergeEditorViewModel>(undefined, 'viewModel');
+ private readonly _viewModel = observableValue<undefined | MergeEditorViewModel>('viewModel', undefined);
readonly viewModel: IObservable<undefined | MergeEditorViewModel> = this._viewModel;
- readonly model = this._viewModel.map(m => m?.model);
+ readonly model = this._viewModel.map(m => /** @description model */ m?.model);
protected readonly htmlElements = h('div.code-view', [
- h('div.title', { $: 'title' }),
+ h('div.title', { $: 'header' }, [
+ h('span.title', { $: 'title' }),
+ h('span.description', { $: 'description' }),
+ h('span.detail', { $: 'detail' }),
+ ]),
h('div.container', [
h('div.gutter', { $: 'gutterDiv' }),
h('div', { $: 'editor' }),
@@ -44,7 +48,7 @@ export abstract class CodeEditorView extends Disposable {
setStyle(this.htmlElements.root, { width, height, top, left });
this.editor.layout({
width: width - this.htmlElements.gutterDiv.clientWidth,
- height: height - this.htmlElements.title.clientHeight,
+ height: height - this.htmlElements.header.clientHeight,
});
}
// preferredWidth?: number | undefined;
@@ -53,9 +57,6 @@ export abstract class CodeEditorView extends Disposable {
// snap?: boolean | undefined;
};
- private readonly _title = new IconLabel(this.htmlElements.title, { supportIcons: true });
- protected readonly _detail = new IconLabel(this.htmlElements.title, { supportIcons: true });
-
public readonly editor = this.instantiationService.createInstance(
CodeEditorWidget,
this.htmlElements.editor,
@@ -71,15 +72,15 @@ export abstract class CodeEditorView extends Disposable {
public readonly isFocused = observableFromEvent(
Event.any(this.editor.onDidBlurEditorWidget, this.editor.onDidFocusEditorWidget),
- () => this.editor.hasWidgetFocus()
+ () => /** @description editor.hasWidgetFocus */ this.editor.hasWidgetFocus()
);
public readonly cursorPosition = observableFromEvent(
this.editor.onDidChangeCursorPosition,
- () => this.editor.getPosition()
+ () => /** @description editor.getPosition */ this.editor.getPosition()
);
- public readonly cursorLineNumber = this.cursorPosition.map(p => p?.lineNumber);
+ public readonly cursorLineNumber = this.cursorPosition.map(p => /** @description cursorPosition.lineNumber */ p?.lineNumber);
constructor(
@IInstantiationService
@@ -100,9 +101,14 @@ export abstract class CodeEditorView extends Disposable {
detail: string | undefined
): void {
this.editor.setModel(textModel);
- this._title.setLabel(title, description);
- this._detail.setLabel('', detail);
- this._viewModel.set(viewModel, undefined);
+ reset(this.htmlElements.title, ...renderLabelWithIcons(title));
+ reset(this.htmlElements.description, ...(description ? renderLabelWithIcons(description) : []));
+ reset(this.htmlElements.detail, ...(detail ? renderLabelWithIcons(detail) : []));
+
+ transaction(tx => {
+ /** @description CodeEditorView: Set Model */
+ this._viewModel.set(viewModel, tx);
+ });
}
}
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts
index 1610a9a0b0d..abadef681ba 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts
@@ -8,6 +8,7 @@ import { Toggle } from 'vs/base/browser/ui/toggle/toggle';
import { Action, IAction, Separator } from 'vs/base/common/actions';
import { Codicon } from 'vs/base/common/codicons';
import { Disposable } from 'vs/base/common/lifecycle';
+import { autorun, derived, IObservable, ISettableObservable, ITransaction, transaction, observableValue } from 'vs/base/common/observable';
import { noBreakWhitespace } from 'vs/base/common/strings';
import { isDefined } from 'vs/base/common/types';
import { EditorExtensionsRegistry, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions';
@@ -18,7 +19,6 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { attachToggleStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
-import { autorun, derivedObservable, IObservable, ITransaction, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable';
import { InputState, ModifiedBaseRangeState } from 'vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange';
import { applyObservableDecorations, setFields } from 'vs/workbench/contrib/mergeEditor/browser/utils';
import { handledConflictMinimapOverViewRulerColor, unhandledConflictMinimapOverViewRulerColor } from 'vs/workbench/contrib/mergeEditor/browser/view/colors';
@@ -26,7 +26,7 @@ import { EditorGutter, IGutterItemInfo, IGutterItemView } from '../editorGutter'
import { CodeEditorView } from './codeEditorView';
export class InputCodeEditorView extends CodeEditorView {
- private readonly decorations = derivedObservable('decorations', reader => {
+ private readonly decorations = derived(`input${this.inputNumber}.decorations`, reader => {
const viewModel = this.viewModel.read(reader);
if (!viewModel) {
return [];
@@ -99,6 +99,136 @@ export class InputCodeEditorView extends CodeEditorView {
return result;
});
+ private readonly modifiedBaseRangeGutterItemInfos = derived(`input${this.inputNumber}.modifiedBaseRangeGutterItemInfos`, reader => {
+ const viewModel = this.viewModel.read(reader);
+ if (!viewModel) { return []; }
+ const model = viewModel.model;
+ const inputNumber = this.inputNumber;
+
+ return model.modifiedBaseRanges.read(reader)
+ .filter((r) => r.getInputDiffs(this.inputNumber).length > 0)
+ .map<ModifiedBaseRangeGutterItemInfo>((baseRange, idx) => ({
+ id: idx.toString(),
+ range: baseRange.getInputRange(this.inputNumber),
+ enabled: model.isUpToDate,
+ toggleState: derived('checkbox is checked', (reader) => {
+ const input = model
+ .getState(baseRange)
+ .read(reader)
+ .getInput(this.inputNumber);
+ return input === InputState.second && !baseRange.isOrderRelevant
+ ? InputState.first
+ : input;
+ }
+ ),
+ setState: (value, tx) => viewModel.setState(
+ baseRange,
+ model
+ .getState(baseRange)
+ .get()
+ .withInputValue(this.inputNumber, value),
+ tx
+ ),
+ toggleBothSides() {
+ transaction(tx => {
+ /** @description Context Menu: toggle both sides */
+ const state = model
+ .getState(baseRange)
+ .get();
+ model.setState(
+ baseRange,
+ state
+ .toggle(inputNumber)
+ .toggle(inputNumber === 1 ? 2 : 1),
+ true,
+ tx
+ );
+ });
+ },
+ getContextMenuActions: () => {
+ const state = model.getState(baseRange).get();
+ const handled = model.isHandled(baseRange).get();
+
+ const update = (newState: ModifiedBaseRangeState) => {
+ transaction(tx => {
+ /** @description Context Menu: Update Base Range State */
+ return viewModel.setState(baseRange, newState, tx);
+ });
+ };
+
+ function action(id: string, label: string, targetState: ModifiedBaseRangeState, checked: boolean) {
+ const action = new Action(id, label, undefined, true, () => {
+ update(targetState);
+ });
+ action.checked = checked;
+ return action;
+ }
+ const both = state.input1 && state.input2;
+
+ return [
+ baseRange.input1Diffs.length > 0
+ ? action(
+ 'mergeEditor.acceptInput1',
+ localize('mergeEditor.accept', 'Accept {0}', model.input1Title),
+ state.toggle(1),
+ state.input1
+ )
+ : undefined,
+ baseRange.input2Diffs.length > 0
+ ? action(
+ 'mergeEditor.acceptInput2',
+ localize('mergeEditor.accept', 'Accept {0}', model.input2Title),
+ state.toggle(2),
+ state.input2
+ )
+ : undefined,
+ baseRange.isConflicting
+ ? setFields(
+ action(
+ 'mergeEditor.acceptBoth',
+ localize(
+ 'mergeEditor.acceptBoth',
+ 'Accept Both'
+ ),
+ state.withInput1(!both).withInput2(!both),
+ both
+ ),
+ { enabled: baseRange.canBeCombined }
+ )
+ : undefined,
+ new Separator(),
+ baseRange.isConflicting
+ ? setFields(
+ action(
+ 'mergeEditor.swap',
+ localize('mergeEditor.swap', 'Swap'),
+ state.swap(),
+ false
+ ),
+ { enabled: !state.isEmpty && (!both || baseRange.isOrderRelevant) }
+ )
+ : undefined,
+
+ setFields(
+ new Action(
+ 'mergeEditor.markAsHandled',
+ localize('mergeEditor.markAsHandled', 'Mark as Handled'),
+ undefined,
+ true,
+ () => {
+ transaction((tx) => {
+ /** @description Context Menu: Mark as handled */
+ model.setHandled(baseRange, !handled, tx);
+ });
+ }
+ ),
+ { checked: handled }
+ ),
+ ].filter(isDefined);
+ }
+ }));
+ });
+
constructor(
public readonly inputNumber: 1 | 2,
@IInstantiationService instantiationService: IInstantiationService,
@@ -112,112 +242,7 @@ export class InputCodeEditorView extends CodeEditorView {
this._register(
new EditorGutter(this.editor, this.htmlElements.gutterDiv, {
getIntersectingGutterItems: (range, reader) => {
- const viewModel = this.viewModel.read(reader);
- if (!viewModel) { return []; }
- const model = viewModel.model;
-
- return model.modifiedBaseRanges.read(reader)
- .filter((r) => r.getInputDiffs(this.inputNumber).length > 0)
- .map<ModifiedBaseRangeGutterItemInfo>((baseRange, idx) => ({
- id: idx.toString(),
- range: baseRange.getInputRange(this.inputNumber),
- enabled: model.isUpToDate,
- toggleState: derivedObservable('toggle', (reader) => {
- const input = model
- .getState(baseRange)
- .read(reader)
- .getInput(this.inputNumber);
- return input === InputState.second && !baseRange.isOrderRelevant
- ? InputState.first
- : input;
- }
- ),
- setState: (value, tx) => viewModel.setState(
- baseRange,
- model
- .getState(baseRange)
- .get()
- .withInputValue(this.inputNumber, value),
- tx
- ),
- getContextMenuActions: () => {
- const state = model.getState(baseRange).get();
- const handled = model.isHandled(baseRange).get();
-
- const update = (newState: ModifiedBaseRangeState) => {
- transaction(tx => viewModel.setState(baseRange, newState, tx));
- };
-
- function action(id: string, label: string, targetState: ModifiedBaseRangeState, checked: boolean) {
- const action = new Action(id, label, undefined, true, () => {
- update(targetState);
- });
- action.checked = checked;
- return action;
- }
- const both = state.input1 && state.input2;
-
- return [
- baseRange.input1Diffs.length > 0
- ? action(
- 'mergeEditor.acceptInput1',
- localize('mergeEditor.accept', 'Accept {0}', model.input1Title),
- state.toggle(1),
- state.input1
- )
- : undefined,
- baseRange.input2Diffs.length > 0
- ? action(
- 'mergeEditor.acceptInput2',
- localize('mergeEditor.accept', 'Accept {0}', model.input2Title),
- state.toggle(2),
- state.input2
- )
- : undefined,
- baseRange.isConflicting
- ? setFields(
- action(
- 'mergeEditor.acceptBoth',
- localize(
- 'mergeEditor.acceptBoth',
- 'Accept Both'
- ),
- state.withInput1(!both).withInput2(!both),
- both
- ),
- { enabled: baseRange.canBeCombined }
- )
- : undefined,
- new Separator(),
- baseRange.isConflicting
- ? setFields(
- action(
- 'mergeEditor.swap',
- localize('mergeEditor.swap', 'Swap'),
- state.swap(),
- false
- ),
- { enabled: !state.isEmpty && (!both || baseRange.isOrderRelevant) }
- )
- : undefined,
-
- setFields(
- new Action(
- 'mergeEditor.markAsHandled',
- localize('mergeEditor.markAsHandled', 'Mark as Handled'),
- undefined,
- true,
- () => {
- transaction((tx) => {
- model.setHandled(baseRange, !handled, tx);
- });
- }
- ),
- { checked: handled }
- ),
- ].filter(isDefined);
- }
- }));
+ return this.modifiedBaseRangeGutterItemInfos.read(reader);
},
createView: (item, target) => new MergeConflictGutterItemView(item, target, contextMenuService, themeService),
})
@@ -233,11 +258,12 @@ export interface ModifiedBaseRangeGutterItemInfo extends IGutterItemInfo {
enabled: IObservable<boolean>;
toggleState: IObservable<InputState>;
setState(value: boolean, tx: ITransaction): void;
+ toggleBothSides(): void;
getContextMenuActions(): readonly IAction[];
}
export class MergeConflictGutterItemView extends Disposable implements IGutterItemView<ModifiedBaseRangeGutterItemInfo> {
- private readonly item = new ObservableValue<ModifiedBaseRangeGutterItemInfo | undefined>(undefined, 'item');
+ private readonly item: ISettableObservable<ModifiedBaseRangeGutterItemInfo>;
constructor(
item: ModifiedBaseRangeGutterItemInfo,
@@ -247,7 +273,7 @@ export class MergeConflictGutterItemView extends Disposable implements IGutterIt
) {
super();
- this.item.set(item, undefined);
+ this.item = observableValue('item', item);
target.classList.add('merge-accept-gutter-marker');
@@ -257,12 +283,12 @@ export class MergeConflictGutterItemView extends Disposable implements IGutterIt
this._register(
dom.addDisposableListener(checkBox.domNode, dom.EventType.MOUSE_DOWN, (e) => {
- if (e.button === 2) {
- const item = this.item.get();
- if (!item) {
- return;
- }
+ const item = this.item.get();
+ if (!item) {
+ return;
+ }
+ if (e.button === /* Right */ 2) {
contextMenuService.showContextMenu({
getAnchor: () => checkBox.domNode,
getActions: item.getContextMenuActions,
@@ -270,13 +296,19 @@ export class MergeConflictGutterItemView extends Disposable implements IGutterIt
e.stopPropagation();
e.preventDefault();
+ } else if (e.button === /* Middle */ 1) {
+ item.toggleBothSides();
+
+ e.stopPropagation();
+ e.preventDefault();
}
})
);
+
checkBox.domNode.classList.add('accept-conflict-group');
this._register(
- autorun((reader) => {
+ autorun('Update Checkbox', (reader) => {
const item = this.item.read(reader)!;
const value = item.toggleState.read(reader);
const iconMap: Record<InputState, { icon: Codicon | undefined; checked: boolean }> = {
@@ -293,11 +325,12 @@ export class MergeConflictGutterItemView extends Disposable implements IGutterIt
} else {
checkBox.enable();
}
- }, 'Update Toggle State')
+ })
);
this._register(checkBox.onChange(() => {
transaction(tx => {
+ /** @description Handle Checkbox Change */
this.item.get()!.setState(checkBox.checked, tx);
});
}));
@@ -315,6 +348,9 @@ export class MergeConflictGutterItemView extends Disposable implements IGutterIt
}
update(baseRange: ModifiedBaseRangeGutterItemInfo): void {
- this.item.set(baseRange, undefined);
+ transaction(tx => {
+ /** @description MergeConflictGutterItemView: Updating new base range */
+ this.item.set(baseRange, tx);
+ });
}
}
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts
index fc6919af08a..e1557eb0c9a 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts
@@ -4,17 +4,17 @@
*--------------------------------------------------------------------------------------------*/
import { CompareResult } from 'vs/base/common/arrays';
+import { autorun, derived } from 'vs/base/common/observable';
import { IModelDeltaDecoration, MinimapPosition, OverviewRulerLane } from 'vs/editor/common/model';
import { localize } from 'vs/nls';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
-import { autorun, derivedObservable } from 'vs/workbench/contrib/audioCues/browser/observable';
import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';
import { applyObservableDecorations, join } from 'vs/workbench/contrib/mergeEditor/browser/utils';
import { handledConflictMinimapOverViewRulerColor, unhandledConflictMinimapOverViewRulerColor } from 'vs/workbench/contrib/mergeEditor/browser/view/colors';
import { CodeEditorView } from './codeEditorView';
export class ResultCodeEditorView extends CodeEditorView {
- private readonly decorations = derivedObservable('decorations', reader => {
+ private readonly decorations = derived('result.decorations', reader => {
const viewModel = this.viewModel.read(reader);
if (!viewModel) {
return [];
@@ -107,24 +107,25 @@ export class ResultCodeEditorView extends CodeEditorView {
this._register(applyObservableDecorations(this.editor, this.decorations));
- this._register(autorun(reader => {
+ this._register(autorun('update remainingConflicts label', reader => {
const model = this.model.read(reader);
if (!model) {
return;
}
const count = model.unhandledConflictsCount.read(reader);
- this._detail.setLabel(count === 1
+ this.htmlElements.detail.innerText = count === 1
? localize(
'mergeEditor.remainingConflicts',
- '{0} Remaining Conflict',
+ '{0} Conflict Remaining',
count
)
: localize(
'mergeEditor.remainingConflict',
- '{0} Remaining Conflicts',
+ '{0} Conflicts Remaining ',
count
- ));
- }, 'update label'));
+ );
+
+ }));
}
}
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/media/mergeEditor.css b/src/vs/workbench/contrib/mergeEditor/browser/view/media/mergeEditor.css
index b832e88bd84..e9c0ada7df3 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/view/media/mergeEditor.css
+++ b/src/vs/workbench/contrib/mergeEditor/browser/view/media/mergeEditor.css
@@ -8,11 +8,27 @@
height: 30px;
display: flex;
align-content: center;
- justify-content: space-between;
}
-.monaco-workbench .merge-editor .code-view > .title .monaco-icon-label {
- margin: auto 0;
+.monaco-workbench .merge-editor .code-view > .title>SPAN {
+ align-self: center;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ padding-right: 6px;
+}
+
+.monaco-workbench .merge-editor .code-view > .title>SPAN.title {
+ flex-shrink: 0;
+}
+
+.monaco-workbench .merge-editor .code-view > .title>SPAN.description {
+ opacity: 0.7;
+ font-size: 90%;
+}
+
+.monaco-workbench .merge-editor .code-view > .title>SPAN.detail {
+ margin-left: auto;
+ flex-shrink: 0;
}
.monaco-workbench .merge-editor .code-view > .container {
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts
index ae2fcbcd7f1..abd5efbe468 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts
@@ -10,11 +10,14 @@ import { IAction } from 'vs/base/common/actions';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Color } from 'vs/base/common/color';
import { BugIndicatingError } from 'vs/base/common/errors';
-import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
+import { Emitter, Event } from 'vs/base/common/event';
+import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
+import { autorunWithStore, IObservable } from 'vs/base/common/observable';
import { isEqual } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import 'vs/css!./media/mergeEditor';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
+import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions';
import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon';
@@ -24,7 +27,7 @@ import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/men
import { IMenuService, MenuId } from 'vs/platform/actions/common/actions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
-import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor';
+import { IEditorOptions, ITextEditorOptions, ITextResourceEditorInput } from 'vs/platform/editor/common/editor';
import { IFileService } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILabelService } from 'vs/platform/label/common/label';
@@ -33,10 +36,9 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor';
import { AbstractTextEditor } from 'vs/workbench/browser/parts/editor/textEditor';
-import { EditorInputWithOptions, EditorResourceAccessor, IEditorOpenContext } from 'vs/workbench/common/editor';
+import { IEditorOpenContext } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { applyTextEditorOptions } from 'vs/workbench/common/editor/editorOptions';
-import { autorunWithStore, IObservable } from 'vs/workbench/contrib/audioCues/browser/observable';
import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput';
import { DocumentMapping, getOppositeDirection, MappingDirection } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping';
import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel';
@@ -45,7 +47,6 @@ import { MergeEditorViewModel } from 'vs/workbench/contrib/mergeEditor/browser/v
import { ctxBaseResourceScheme, ctxIsMergeEditor, ctxMergeEditorLayout, MergeEditorLayoutTypes } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor';
import { settingsSashBorder } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry';
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
-import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import './colors';
import { InputCodeEditorView } from './editors/inputCodeEditorView';
@@ -85,11 +86,9 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
private readonly _sessionDisposables = new DisposableStore();
private _grid!: Grid<IView>;
-
-
- private readonly input1View = this._register(this.instantiation.createInstance(InputCodeEditorView, 1));
- private readonly input2View = this._register(this.instantiation.createInstance(InputCodeEditorView, 2));
- private readonly inputResultView = this._register(this.instantiation.createInstance(ResultCodeEditorView));
+ private readonly input1View = this._register(this.instantiationService.createInstance(InputCodeEditorView, 1));
+ private readonly input2View = this._register(this.instantiationService.createInstance(InputCodeEditorView, 2));
+ private readonly inputResultView = this._register(this.instantiationService.createInstance(ResultCodeEditorView));
private readonly _layoutMode: MergeEditorLayout;
private readonly _ctxIsMergeEditor: IContextKey<boolean>;
@@ -104,7 +103,7 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
}
constructor(
- @IInstantiationService private readonly instantiation: IInstantiationService,
+ @IInstantiationService instantiation: IInstantiationService,
@ILabelService private readonly _labelService: ILabelService,
@IMenuService private readonly _menuService: IMenuService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@@ -116,7 +115,6 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
@IEditorService editorService: IEditorService,
@IEditorGroupsService editorGroupService: IEditorGroupsService,
@IFileService fileService: IFileService,
- @IEditorResolverService private readonly _editorResolverService: IEditorResolverService,
) {
super(MergeEditor.ID, telemetryService, instantiation, storageService, textResourceConfigurationService, themeService, editorService, editorGroupService, fileService);
@@ -187,7 +185,7 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
createAndFillInActionBarActions(toolbarMenu, { renderShortTitle: true, shouldForwardArgs: true }, actions);
if (actions.length > 0) {
const [first] = actions;
- const acceptBtn = this.instantiation.createInstance(FloatingClickWidget, this.inputResultView.editor, first.label, first.id);
+ const acceptBtn = this.instantiationService.createInstance(FloatingClickWidget, this.inputResultView.editor, first.label, first.id);
toolbarMenuDisposables.add(acceptBtn.onClick(() => first.run(this.inputResultView.editor.getModel()?.uri)));
toolbarMenuDisposables.add(acceptBtn);
acceptBtn.render();
@@ -209,6 +207,19 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
super.dispose();
}
+ // --- layout constraints
+
+ private readonly _onDidChangeSizeConstraints = new Emitter<void>();
+ override readonly onDidChangeSizeConstraints: Event<void> = this._onDidChangeSizeConstraints.event;
+
+ override get minimumWidth() {
+ return this._layoutMode.value === 'mixed'
+ ? this.input1View.view.minimumWidth + this.input1View.view.minimumWidth
+ : this.input1View.view.minimumWidth + this.input1View.view.minimumWidth + this.inputResultView.view.minimumWidth;
+ }
+
+ // ---
+
override getTitle(): string {
if (this.input) {
return this.input.getName();
@@ -284,7 +295,6 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
await super.setInput(input, options, context, token);
this._sessionDisposables.clear();
- this._toggleEditorOverwrite(true);
const model = await input.resolve();
this._model = model;
@@ -297,16 +307,17 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
this._ctxBaseResourceScheme.set(model.base.uri.scheme);
const viewState = this.loadEditorViewState(input, context);
- this._applyViewState(viewState);
-
- this._sessionDisposables.add(thenIfNotDisposed(model.onInitialized, () => {
- const firstConflict = model.modifiedBaseRanges.get().find(r => r.isConflicting);
- if (!firstConflict) {
- return;
- }
-
- this.input1View.editor.revealLineInCenter(firstConflict.input1Range.startLineNumber);
- }));
+ if (viewState) {
+ this._applyViewState(viewState);
+ } else {
+ this._sessionDisposables.add(thenIfNotDisposed(model.onInitialized, () => {
+ const firstConflict = model.modifiedBaseRanges.get().find(r => r.isConflicting);
+ if (!firstConflict) {
+ return;
+ }
+ this.input1View.editor.revealLineInCenter(firstConflict.input1Range.startLineNumber);
+ }));
+ }
this._sessionDisposables.add(autorunWithStore((reader, store) => {
@@ -361,7 +372,6 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
super.clearInput();
this._sessionDisposables.clear();
- this._toggleEditorOverwrite(false);
for (const { editor } of [this.input1View, this.input2View, this.inputResultView]) {
editor.setModel(null);
@@ -393,39 +403,6 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
}
this._ctxIsMergeEditor.set(visible);
- this._toggleEditorOverwrite(visible);
- }
-
- private readonly _editorOverrideHandle = this._store.add(new MutableDisposable());
-
- private _toggleEditorOverwrite(haveIt: boolean) {
- if (!haveIt) {
- this._editorOverrideHandle.clear();
- return;
- }
- // this is RATHER UGLY. I dynamically register an editor for THIS (editor,input) so that
- // navigating within the merge editor works, e.g navigating from the outline or breakcrumps
- // or revealing a definition, reference etc
- // TODO@jrieken @bpasero @lramos15
- const input = this.input;
- if (input instanceof MergeEditorInput) {
- this._editorOverrideHandle.value = this._editorResolverService.registerEditor(
- `${input.result.scheme}:${input.result.fsPath}`,
- {
- id: `${this.getId()}/fake`,
- label: this.input?.getName()!,
- priority: RegisteredEditorPriority.exclusive
- },
- {},
- (candidate): EditorInputWithOptions => {
- const resource = EditorResourceAccessor.getCanonicalUri(candidate);
- if (!isEqual(resource, this.model?.result.uri)) {
- throw new Error(`Expected to be called WITH ${input.result.toString()}`);
- }
- return { editor: input };
- }
- );
- }
}
// ---- interact with "outside world" via`getControl`, `scopedContextKeyService`: we only expose the result-editor keep the others internal
@@ -454,6 +431,7 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
}
this._layoutMode.value = newValue;
this._ctxUsesColumnLayout.set(newValue);
+ this._onDidChangeSizeConstraints.fire();
}
private _applyViewState(state: IMergeEditorViewState | undefined) {
@@ -493,6 +471,37 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
}
}
+export class MergeEditorOpenHandlerContribution extends Disposable {
+
+ constructor(
+ @IEditorService private readonly _editorService: IEditorService,
+ @ICodeEditorService codeEditorService: ICodeEditorService,
+ ) {
+ super();
+ this._store.add(codeEditorService.registerCodeEditorOpenHandler(this.openCodeEditorFromMergeEditor.bind(this)));
+ }
+
+ private async openCodeEditorFromMergeEditor(input: ITextResourceEditorInput, _source: ICodeEditor | null, sideBySide?: boolean | undefined): Promise<ICodeEditor | null> {
+ const activePane = this._editorService.activeEditorPane;
+ if (!sideBySide
+ && input.options
+ && activePane instanceof MergeEditor
+ && activePane.getControl()
+ && activePane.input instanceof MergeEditorInput
+ && isEqual(input.resource, activePane.input.result)
+ ) {
+ // Special: stay inside the merge editor when it is active and when the input
+ // targets the result editor of the merge editor.
+ const targetEditor = <ICodeEditor>activePane.getControl()!;
+ applyTextEditorOptions(input.options, targetEditor, ScrollType.Smooth);
+ return targetEditor;
+ }
+
+ // cannot handle this
+ return null;
+ }
+}
+
type IMergeEditorViewState = ICodeEditorViewState & {
readonly input1State?: ICodeEditorViewState;
readonly input2State?: ICodeEditorViewState;
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts
index b53549fea4b..de910cceaee 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts
@@ -3,13 +3,12 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { findLast, lastOrDefault } from 'vs/base/common/arrays';
+import { findLast } from 'vs/base/common/arrays';
+import { derived, derivedObservableWithWritableCache, IReader, ITransaction, observableValue, transaction } from 'vs/base/common/observable';
import { ScrollType } from 'vs/editor/common/editorCommon';
-import { derivedObservable, derivedObservableWithWritableCache, IReader, ITransaction, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable';
import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';
import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel';
import { ModifiedBaseRange, ModifiedBaseRangeState } from 'vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange';
-import { elementAtOrUndefined } from 'vs/workbench/contrib/mergeEditor/browser/utils';
import { CodeEditorView } from 'vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView';
import { InputCodeEditorView } from 'vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView';
import { ResultCodeEditorView } from 'vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView';
@@ -26,9 +25,9 @@ export class MergeEditorViewModel {
return editors.find((e) => e.isFocused.read(reader)) || lastValue;
});
- private readonly manuallySetActiveModifiedBaseRange = new ObservableValue<
+ private readonly manuallySetActiveModifiedBaseRange = observableValue<
ModifiedBaseRange | undefined
- >(undefined, 'manuallySetActiveModifiedBaseRange');
+ >('manuallySetActiveModifiedBaseRange', undefined);
private getRange(editor: CodeEditorView, modifiedBaseRange: ModifiedBaseRange, reader: IReader | undefined): LineRange {
if (editor === this.resultCodeEditorView) {
@@ -39,7 +38,7 @@ export class MergeEditorViewModel {
}
}
- public readonly activeModifiedBaseRange = derivedObservable(
+ public readonly activeModifiedBaseRange = derived(
'activeModifiedBaseRange',
(reader) => {
const focusedEditor = this.lastFocusedEditor.read(reader);
@@ -78,7 +77,7 @@ export class MergeEditorViewModel {
this.model.setState(baseRange, state, true, tx);
}
- public goToConflict(getModifiedBaseRange: (editor: CodeEditorView, curLineNumber: number) => ModifiedBaseRange | undefined): void {
+ private goToConflict(getModifiedBaseRange: (editor: CodeEditorView, curLineNumber: number) => ModifiedBaseRange | undefined): void {
const lastFocusedEditor = this.lastFocusedEditor.get();
if (!lastFocusedEditor) {
return;
@@ -98,23 +97,35 @@ export class MergeEditorViewModel {
}
}
- public goToNextConflict(): void {
+ public goToNextModifiedBaseRange(onlyConflicting: boolean): void {
this.goToConflict(
(e, l) =>
this.model.modifiedBaseRanges
.get()
- .find((r) => this.getRange(e, r, undefined).startLineNumber > l) ||
- elementAtOrUndefined(this.model.modifiedBaseRanges.get(), 0)
+ .find(
+ (r) =>
+ (!onlyConflicting || r.isConflicting) &&
+ this.getRange(e, r, undefined).startLineNumber > l
+ ) ||
+ this.model.modifiedBaseRanges
+ .get()
+ .find((r) => !onlyConflicting || r.isConflicting)
);
}
- public goToPreviousConflict(): void {
+ public goToPreviousModifiedBaseRange(onlyConflicting: boolean): void {
this.goToConflict(
(e, l) =>
findLast(
this.model.modifiedBaseRanges.get(),
- (r) => this.getRange(e, r, undefined).endLineNumberExclusive < l
- ) || lastOrDefault(this.model.modifiedBaseRanges.get())
+ (r) =>
+ (!onlyConflicting || r.isConflicting) &&
+ this.getRange(e, r, undefined).endLineNumberExclusive < l
+ ) ||
+ findLast(
+ this.model.modifiedBaseRanges.get(),
+ (r) => !onlyConflicting || r.isConflicting
+ )
);
}
@@ -124,6 +135,7 @@ export class MergeEditorViewModel {
return;
}
transaction(tx => {
+ /** @description Toggle Active Conflict */
this.setState(
activeModifiedBaseRange,
this.model.getState(activeModifiedBaseRange).get().toggle(inputNumber),
diff --git a/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts b/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts
index 9963829694f..ebef4581ab6 100644
--- a/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts
+++ b/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts
@@ -5,13 +5,13 @@
import assert = require('assert');
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
+import { transaction } from 'vs/base/common/observable';
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
import { Range } from 'vs/editor/common/core/range';
-import { ITextModel } from 'vs/editor/common/model';
+import { EndOfLinePreference, ITextModel } from 'vs/editor/common/model';
import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker';
import { createModelServices, createTextModel } from 'vs/editor/test/common/testTextModel';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
-import { transaction } from 'vs/workbench/contrib/audioCues/browser/observable';
import { EditorWorkerServiceDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer';
import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel';
@@ -29,9 +29,10 @@ suite('merge editor model', () => {
},
model => {
assert.deepStrictEqual(model.getProjections(), {
- base: '⟦⟧₀line1\nline2',
- input1: '⟦0\n⟧₀line1\nline2',
- input2: '⟦0\n⟧₀line1\nline2',
+ base: ['⟦⟧₀line1', 'line2'],
+ input1: ['⟦0', '⟧₀line1', 'line2'],
+ input2: ['⟦0', '⟧₀line1', 'line2'],
+ result: ['⟦⟧{conflicting}₀'],
});
model.toggleConflict(0, 1);
@@ -59,7 +60,12 @@ suite('merge editor model', () => {
"result": ""
},
model => {
- assert.deepStrictEqual(model.getProjections(), ({ base: "⟦⟧₀", input1: "⟦input1⟧₀", input2: "⟦input2⟧₀" }));
+ assert.deepStrictEqual(model.getProjections(), {
+ base: ['⟦⟧₀'],
+ input1: ['⟦input1⟧₀'],
+ input2: ['⟦input2⟧₀'],
+ result: ['⟦⟧{}₀'],
+ });
model.toggleConflict(0, 1);
assert.deepStrictEqual(
@@ -87,9 +93,10 @@ suite('merge editor model', () => {
},
model => {
assert.deepStrictEqual(model.getProjections(), {
- base: '⟦hello⟧₀',
- input1: '⟦hallo⟧₀',
- input2: '⟦helloworld⟧₀',
+ base: ['⟦hello⟧₀'],
+ input1: ['⟦hallo⟧₀'],
+ input2: ['⟦helloworld⟧₀'],
+ result: ['⟦⟧{conflicting}₀'],
});
model.toggleConflict(0, 1);
@@ -115,11 +122,34 @@ suite('merge editor model', () => {
},
model => {
assert.deepStrictEqual(model.getProjections(), {
- base: 'Zürich\nBern\n⟦Basel\n⟧₀Chur\n⟦⟧₁Genf\nThun⟦⟧₂',
- input1:
- 'Zürich\nBern\n⟦⟧₀Chur\n⟦Davos\n⟧₁Genf\nThun\n⟦function f(b:boolean) {}⟧₂',
- input2:
- 'Zürich\nBern\n⟦Basel (FCB)\n⟧₀Chur\n⟦⟧₁Genf\nThun\n⟦function f(a:number) {}⟧₂',
+ base: ['Zürich', 'Bern', '⟦Basel', '⟧₀Chur', '⟦⟧₁Genf', 'Thun⟦⟧₂'],
+ input1: [
+ 'Zürich',
+ 'Bern',
+ '⟦⟧₀Chur',
+ '⟦Davos',
+ '⟧₁Genf',
+ 'Thun',
+ '⟦function f(b:boolean) {}⟧₂',
+ ],
+ input2: [
+ 'Zürich',
+ 'Bern',
+ '⟦Basel (FCB)',
+ '⟧₀Chur',
+ '⟦⟧₁Genf',
+ 'Thun',
+ '⟦function f(a:number) {}⟧₂',
+ ],
+ result: [
+ 'Zürich',
+ 'Bern',
+ '⟦Basel',
+ '⟧{}₀Chur',
+ '⟦Davos',
+ '⟧{1✓}₁Genf',
+ 'Thun⟦⟧{}₂',
+ ],
});
model.toggleConflict(2, 1);
@@ -135,6 +165,61 @@ suite('merge editor model', () => {
}
);
});
+
+ test('conflicts are reset', async () => {
+ await testMergeModel(
+ {
+ "languageId": "typescript",
+ "base": "import { h } from 'vs/base/browser/dom';\nimport { Disposable, IDisposable } from 'vs/base/common/lifecycle';\nimport { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';\nimport { EditorOption } from 'vs/editor/common/config/editorOptions';\nimport { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';\nimport { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';\n",
+ "input1": "import { h } from 'vs/base/browser/dom';\nimport { Disposable, IDisposable } from 'vs/base/common/lifecycle';\nimport { observableSignalFromEvent } from 'vs/base/common/observable';\nimport { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';\nimport { autorun, IReader, observableFromEvent } from 'vs/workbench/contrib/audioCues/browser/observable';\nimport { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';\n",
+ "input2": "import { h } from 'vs/base/browser/dom';\nimport { Disposable, IDisposable } from 'vs/base/common/lifecycle';\nimport { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';\nimport { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';\nimport { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';\n",
+ "result": "import { h } from 'vs/base/browser/dom';\r\nimport { Disposable, IDisposable } from 'vs/base/common/lifecycle';\r\nimport { observableSignalFromEvent } from 'vs/base/common/observable';\r\nimport { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';\r\n<<<<<<< Updated upstream\r\nimport { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';\r\n=======\r\nimport { autorun, IReader, observableFromEvent } from 'vs/workbench/contrib/audioCues/browser/observable';\r\n>>>>>>> Stashed changes\r\nimport { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';\r\n"
+ },
+ model => {
+ assert.deepStrictEqual(model.getProjections(), {
+ base: [
+ "import { h } from 'vs/base/browser/dom';",
+ "import { Disposable, IDisposable } from 'vs/base/common/lifecycle';",
+ "⟦⟧₀import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';",
+ "⟦import { EditorOption } from 'vs/editor/common/config/editorOptions';",
+ "import { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';",
+ "⟧₁import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';",
+ '',
+ ],
+ input1: [
+ "import { h } from 'vs/base/browser/dom';",
+ "import { Disposable, IDisposable } from 'vs/base/common/lifecycle';",
+ "⟦import { observableSignalFromEvent } from 'vs/base/common/observable';",
+ "⟧₀import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';",
+ "⟦import { autorun, IReader, observableFromEvent } from 'vs/workbench/contrib/audioCues/browser/observable';",
+ "⟧₁import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';",
+ '',
+ ],
+ input2: [
+ "import { h } from 'vs/base/browser/dom';",
+ "import { Disposable, IDisposable } from 'vs/base/common/lifecycle';",
+ "⟦⟧₀import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';",
+ "⟦import { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';",
+ "⟧₁import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';",
+ '',
+ ],
+ result: [
+ "import { h } from 'vs/base/browser/dom';",
+ "import { Disposable, IDisposable } from 'vs/base/common/lifecycle';",
+ "⟦import { observableSignalFromEvent } from 'vs/base/common/observable';",
+ "⟧{1✓}₀import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';",
+ '⟦<<<<<<< Updated upstream',
+ "import { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';",
+ '=======',
+ "import { autorun, IReader, observableFromEvent } from 'vs/workbench/contrib/audioCues/browser/observable';",
+ '>>>>>>> Stashed changes',
+ "⟧{conflicting}₁import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';",
+ '',
+ ],
+ });
+ }
+ );
+ });
});
async function testMergeModel(
@@ -197,7 +282,9 @@ class MergeModelInterface extends Disposable {
),
};
},
- }
+ }, {
+ resetUnknownOnInitialization: false
+ }
));
}
@@ -241,14 +328,25 @@ class MergeModelInterface extends Disposable {
}))
);
+ const resultTextModel = createTextModel(this.mergeModel.result.getValue());
+ applyRanges(
+ resultTextModel,
+ baseRanges.map<LabeledRange>((r, idx) => ({
+ range: this.mergeModel.getRangeInResult(r.baseRange).toRange(),
+ label: `{${this.mergeModel.getState(r).get()}}${toSmallNumbersDec(idx)}`,
+ }))
+ );
+
const result = {
- base: baseTextModel.getValue(),
- input1: input1TextModel.getValue(),
- input2: input2TextModel.getValue(),
+ base: baseTextModel.getValue(EndOfLinePreference.LF).split('\n'),
+ input1: input1TextModel.getValue(EndOfLinePreference.LF).split('\n'),
+ input2: input2TextModel.getValue(EndOfLinePreference.LF).split('\n'),
+ result: resultTextModel.getValue(EndOfLinePreference.LF).split('\n'),
};
baseTextModel.dispose();
input1TextModel.dispose();
input2TextModel.dispose();
+ resultTextModel.dispose();
return result;
}
diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts
index 2adc303d4e1..5074d759b06 100644
--- a/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts
+++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts
@@ -31,7 +31,10 @@ registerAction2(class extends NotebookCellAction {
super(
{
id: MOVE_CELL_UP_COMMAND_ID,
- title: localize('notebookActions.moveCellUp', "Move Cell Up"),
+ title: {
+ value: localize('notebookActions.moveCellUp', "Move Cell Up"),
+ original: 'Move Cell Up'
+ },
icon: icons.moveUpIcon,
keybinding: {
primary: KeyMod.Alt | KeyCode.UpArrow,
@@ -57,7 +60,10 @@ registerAction2(class extends NotebookCellAction {
super(
{
id: MOVE_CELL_DOWN_COMMAND_ID,
- title: localize('notebookActions.moveCellDown', "Move Cell Down"),
+ title: {
+ value: localize('notebookActions.moveCellDown', "Move Cell Down"),
+ original: 'Move Cell Down'
+ },
icon: icons.moveDownIcon,
keybinding: {
primary: KeyMod.Alt | KeyCode.DownArrow,
@@ -83,7 +89,10 @@ registerAction2(class extends NotebookCellAction {
super(
{
id: COPY_CELL_UP_COMMAND_ID,
- title: localize('notebookActions.copyCellUp', "Copy Cell Up"),
+ title: {
+ value: localize('notebookActions.copyCellUp', "Copy Cell Up"),
+ original: 'Copy Cell Up'
+ },
keybinding: {
primary: KeyMod.Alt | KeyMod.Shift | KeyCode.UpArrow,
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()),
@@ -102,7 +111,10 @@ registerAction2(class extends NotebookCellAction {
super(
{
id: COPY_CELL_DOWN_COMMAND_ID,
- title: localize('notebookActions.copyCellDown', "Copy Cell Down"),
+ title: {
+ value: localize('notebookActions.copyCellDown', "Copy Cell Down"),
+ original: 'Copy Cell Down'
+ },
keybinding: {
primary: KeyMod.Alt | KeyMod.Shift | KeyCode.DownArrow,
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()),
@@ -137,7 +149,10 @@ registerAction2(class extends NotebookCellAction {
super(
{
id: SPLIT_CELL_COMMAND_ID,
- title: localize('notebookActions.splitCell', "Split Cell"),
+ title: {
+ value: localize('notebookActions.splitCell', "Split Cell"),
+ original: 'Split Cell'
+ },
menu: {
id: MenuId.NotebookCellTitle,
when: ContextKeyExpr.and(
@@ -212,7 +227,10 @@ registerAction2(class extends NotebookCellAction {
super(
{
id: JOIN_CELL_ABOVE_COMMAND_ID,
- title: localize('notebookActions.joinCellAbove', "Join With Previous Cell"),
+ title: {
+ value: localize('notebookActions.joinCellAbove', "Join With Previous Cell"),
+ original: 'Join With Previous Cell'
+ },
keybinding: {
when: NOTEBOOK_EDITOR_FOCUSED,
primary: KeyMod.WinCtrl | KeyMod.Alt | KeyMod.Shift | KeyCode.KeyJ,
@@ -238,7 +256,10 @@ registerAction2(class extends NotebookCellAction {
super(
{
id: JOIN_CELL_BELOW_COMMAND_ID,
- title: localize('notebookActions.joinCellBelow', "Join With Next Cell"),
+ title: {
+ value: localize('notebookActions.joinCellBelow', "Join With Next Cell"),
+ original: 'Join With Next Cell'
+ },
keybinding: {
when: NOTEBOOK_EDITOR_FOCUSED,
primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.KeyJ,
@@ -270,7 +291,10 @@ registerAction2(class ChangeCellToCodeAction extends NotebookMultiCellAction {
constructor() {
super({
id: CHANGE_CELL_TO_CODE_COMMAND_ID,
- title: localize('notebookActions.changeCellToCode', "Change Cell to Code"),
+ title: {
+ value: localize('notebookActions.changeCellToCode', "Change Cell to Code"),
+ original: 'Change Cell to Code'
+ },
keybinding: {
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)),
primary: KeyCode.KeyY,
@@ -294,7 +318,10 @@ registerAction2(class ChangeCellToMarkdownAction extends NotebookMultiCellAction
constructor() {
super({
id: CHANGE_CELL_TO_MARKDOWN_COMMAND_ID,
- title: localize('notebookActions.changeCellToMarkdown', "Change Cell to Markdown"),
+ title: {
+ value: localize('notebookActions.changeCellToMarkdown', "Change Cell to Markdown"),
+ original: 'Change Cell to Markdown'
+ },
keybinding: {
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)),
primary: KeyCode.KeyM,
@@ -330,7 +357,10 @@ registerAction2(class CollapseCellInputAction extends NotebookMultiCellAction {
constructor() {
super({
id: COLLAPSE_CELL_INPUT_COMMAND_ID,
- title: localize('notebookActions.collapseCellInput', "Collapse Cell Input"),
+ title: {
+ value: localize('notebookActions.collapseCellInput', "Collapse Cell Input"),
+ original: 'Collapse Cell Input'
+ },
keybinding: {
when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_INPUT_COLLAPSED.toNegated(), InputFocusedContext.toNegated()),
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyC),
@@ -356,7 +386,10 @@ registerAction2(class ExpandCellInputAction extends NotebookMultiCellAction {
constructor() {
super({
id: EXPAND_CELL_INPUT_COMMAND_ID,
- title: localize('notebookActions.expandCellInput', "Expand Cell Input"),
+ title: {
+ value: localize('notebookActions.expandCellInput', "Expand Cell Input"),
+ original: 'Expand Cell Input'
+ },
keybinding: {
when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_INPUT_COLLAPSED),
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyC),
@@ -382,7 +415,10 @@ registerAction2(class CollapseCellOutputAction extends NotebookMultiCellAction {
constructor() {
super({
id: COLLAPSE_CELL_OUTPUT_COMMAND_ID,
- title: localize('notebookActions.collapseCellOutput', "Collapse Cell Output"),
+ title: {
+ value: localize('notebookActions.collapseCellOutput', "Collapse Cell Output"),
+ original: 'Collapse Cell Output'
+ },
keybinding: {
when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED.toNegated(), InputFocusedContext.toNegated(), NOTEBOOK_CELL_HAS_OUTPUTS),
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyT),
@@ -404,7 +440,10 @@ registerAction2(class ExpandCellOuputAction extends NotebookMultiCellAction {
constructor() {
super({
id: EXPAND_CELL_OUTPUT_COMMAND_ID,
- title: localize('notebookActions.expandCellOutput', "Expand Cell Output"),
+ title: {
+ value: localize('notebookActions.expandCellOutput', "Expand Cell Output"),
+ original: 'Expand Cell Output'
+ },
keybinding: {
when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED),
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyT),
@@ -427,7 +466,10 @@ registerAction2(class extends NotebookMultiCellAction {
super({
id: TOGGLE_CELL_OUTPUTS_COMMAND_ID,
precondition: NOTEBOOK_CELL_LIST_FOCUSED,
- title: localize('notebookActions.toggleOutputs', "Toggle Outputs"),
+ title: {
+ value: localize('notebookActions.toggleOutputs', "Toggle Outputs"),
+ original: 'Toggle Outputs'
+ },
description: {
description: localize('notebookActions.toggleOutputs', "Toggle Outputs"),
args: cellExecutionArgs
@@ -457,7 +499,10 @@ registerAction2(class CollapseAllCellInputsAction extends NotebookMultiCellActio
constructor() {
super({
id: COLLAPSE_ALL_CELL_INPUTS_COMMAND_ID,
- title: localize('notebookActions.collapseAllCellInput', "Collapse All Cell Inputs"),
+ title: {
+ value: localize('notebookActions.collapseAllCellInput', "Collapse All Cell Inputs"),
+ original: 'Collapse All Cell Inputs'
+ },
f1: true,
});
}
@@ -471,7 +516,10 @@ registerAction2(class ExpandAllCellInputsAction extends NotebookMultiCellAction
constructor() {
super({
id: EXPAND_ALL_CELL_INPUTS_COMMAND_ID,
- title: localize('notebookActions.expandAllCellInput', "Expand All Cell Inputs"),
+ title: {
+ value: localize('notebookActions.expandAllCellInput', "Expand All Cell Inputs"),
+ original: 'Expand All Cell Inputs'
+ },
f1: true
});
}
@@ -485,7 +533,10 @@ registerAction2(class CollapseAllCellOutputsAction extends NotebookMultiCellActi
constructor() {
super({
id: COLLAPSE_ALL_CELL_OUTPUTS_COMMAND_ID,
- title: localize('notebookActions.collapseAllCellOutput', "Collapse All Cell Outputs"),
+ title: {
+ value: localize('notebookActions.collapseAllCellOutput', "Collapse All Cell Outputs"),
+ original: 'Collapse All Cell Outputs'
+ },
f1: true,
});
}
@@ -499,7 +550,10 @@ registerAction2(class ExpandAllCellOutputsAction extends NotebookMultiCellAction
constructor() {
super({
id: EXPAND_ALL_CELL_OUTPUTS_COMMAND_ID,
- title: localize('notebookActions.expandAllCellOutput', "Expand All Cell Outputs"),
+ title: {
+ value: localize('notebookActions.expandAllCellOutput', "Expand All Cell Outputs"),
+ original: 'Expand All Cell Outputs'
+ },
f1: true
});
}
diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts
index 285dae8f94e..53ce0466795 100644
--- a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts
+++ b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts
@@ -243,7 +243,7 @@ registerAction2(class extends Action2 {
quickPickItems.push({
id: 'installSuggested',
description: suggestedExtension.displayName ?? suggestedExtension.extensionId,
- label: nls.localize('installSuggestedKernel', '$({0}) Install suggested extensions', Codicon.lightbulb.id),
+ label: `$(${Codicon.lightbulb.id}) ` + nls.localize('installSuggestedKernel', 'Install suggested extensions'),
});
}
// there is no kernel, show the install from marketplace
diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/gettingStarted/notebookGettingStarted.ts b/src/vs/workbench/contrib/notebook/browser/contrib/gettingStarted/notebookGettingStarted.ts
index 3e1c47af18a..2165fa94cc2 100644
--- a/src/vs/workbench/contrib/notebook/browser/contrib/gettingStarted/notebookGettingStarted.ts
+++ b/src/vs/workbench/contrib/notebook/browser/contrib/gettingStarted/notebookGettingStarted.ts
@@ -81,7 +81,10 @@ registerAction2(class NotebookClearNotebookLayoutAction extends Action2 {
constructor() {
super({
id: 'workbench.notebook.layout.gettingStarted',
- title: localize('workbench.notebook.layout.gettingStarted.label', "Reset notebook getting started"),
+ title: {
+ value: localize('workbench.notebook.layout.gettingStarted.label', "Reset notebook getting started"),
+ original: 'Reset notebook getting started'
+ },
f1: true,
precondition: ContextKeyExpr.equals(`config.${NotebookSetting.openGettingStarted}`, true),
category: CATEGORIES.Developer,
diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts b/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts
index b3af4143741..ab714f85b51 100644
--- a/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts
+++ b/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts
@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Disposable, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
+import { localize } from 'vs/nls';
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { CATEGORIES } from 'vs/workbench/common/actions';
@@ -121,7 +122,10 @@ registerAction2(class extends Action2 {
constructor() {
super({
id: 'notebook.toggleLayoutTroubleshoot',
- title: 'Toggle Notebook Layout Troubleshoot',
+ title: {
+ value: localize('workbench.notebook.toggleLayoutTroubleshoot', "Toggle Layout Troubleshoot"),
+ original: 'Toggle Notebook Layout Troubleshoot'
+ },
category: CATEGORIES.Developer,
f1: true
});
@@ -144,7 +148,10 @@ registerAction2(class extends Action2 {
constructor() {
super({
id: 'notebook.inspectLayout',
- title: 'Inspect Notebook Layout',
+ title: {
+ value: localize('workbench.notebook.inspectLayout', "Inspect Notebook Layout"),
+ original: 'Inspect Notebook Layout'
+ },
category: CATEGORIES.Developer,
f1: true
});
@@ -169,7 +176,10 @@ registerAction2(class extends Action2 {
constructor() {
super({
id: 'notebook.clearNotebookEdtitorTypeCache',
- title: 'Clear Notebook Editor Cache',
+ title: {
+ value: localize('workbench.notebook.clearNotebookEdtitorTypeCache', "Clear Notebook Editor Type Cache"),
+ original: 'Clear Notebook Editor Cache'
+ },
category: CATEGORIES.Developer,
f1: true
});
diff --git a/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts
index f7b01782aa0..9a42a89b378 100644
--- a/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts
+++ b/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts
@@ -42,7 +42,8 @@ export const enum CellToolbarOrder {
export const enum CellOverflowToolbarGroups {
Copy = '1_copy',
Insert = '2_insert',
- Edit = '3_edit'
+ Edit = '3_edit',
+ Share = '4_share'
}
export interface INotebookActionContext {
@@ -427,3 +428,9 @@ MenuRegistry.appendMenuItem(MenuId.EditorContext, {
group: CellOverflowToolbarGroups.Insert,
when: NOTEBOOK_EDITOR_FOCUSED
});
+
+MenuRegistry.appendMenuItem(MenuId.NotebookCellTitle, {
+ title: localize('miShare', "Share"),
+ submenu: MenuId.EditorContextShare,
+ group: CellOverflowToolbarGroups.Share
+});
diff --git a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts
index 7250d2dc560..1f64bddb5e3 100644
--- a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts
+++ b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts
@@ -49,6 +49,7 @@ export class DeleteCellAction extends MenuItemAction {
},
undefined,
{ shouldForwardArgs: true },
+ undefined,
contextKeyService,
commandService);
}
@@ -469,7 +470,7 @@ registerAction2(class DetectCellLanguageAction extends NotebookCellAction {
constructor() {
super({
id: DETECT_CELL_LANGUAGE,
- title: localize('detectLanguage', 'Accept Detected Language for Cell'),
+ title: { value: localize('detectLanguage', 'Accept Detected Language for Cell'), original: 'Accept Detected Language for Cell' },
f1: true,
precondition: ContextKeyExpr.and(NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE),
keybinding: { primary: KeyCode.KeyD | KeyMod.Alt | KeyMod.Shift, weight: KeybindingWeight.WorkbenchContrib }
diff --git a/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts
index 6a85c41a24a..454b565d71e 100644
--- a/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts
+++ b/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts
@@ -224,7 +224,7 @@ registerAction2(class InsertMarkdownCellAtTopAction extends NotebookAction {
MenuRegistry.appendMenuItem(MenuId.NotebookCellBetween, {
command: {
id: INSERT_CODE_CELL_BELOW_COMMAND_ID,
- title: localize('notebookActions.menu.insertCode', "$(add) Code"),
+ title: '$(add) ' + localize('notebookActions.menu.insertCode', "Code"),
tooltip: localize('notebookActions.menu.insertCode.tooltip', "Add Code Cell")
},
order: 0,
@@ -269,7 +269,7 @@ MenuRegistry.appendMenuItem(MenuId.NotebookToolbar, {
MenuRegistry.appendMenuItem(MenuId.NotebookCellListTop, {
command: {
id: INSERT_CODE_CELL_AT_TOP_COMMAND_ID,
- title: localize('notebookActions.menu.insertCode', "$(add) Code"),
+ title: '$(add) ' + localize('notebookActions.menu.insertCode', "Code"),
tooltip: localize('notebookActions.menu.insertCode.tooltip', "Add Code Cell")
},
order: 0,
@@ -299,7 +299,7 @@ MenuRegistry.appendMenuItem(MenuId.NotebookCellListTop, {
MenuRegistry.appendMenuItem(MenuId.NotebookCellBetween, {
command: {
id: INSERT_MARKDOWN_CELL_BELOW_COMMAND_ID,
- title: localize('notebookActions.menu.insertMarkdown', "$(add) Markdown"),
+ title: '$(add) ' + localize('notebookActions.menu.insertMarkdown', "Markdown"),
tooltip: localize('notebookActions.menu.insertMarkdown.tooltip', "Add Markdown Cell")
},
order: 1,
@@ -331,7 +331,7 @@ MenuRegistry.appendMenuItem(MenuId.NotebookToolbar, {
MenuRegistry.appendMenuItem(MenuId.NotebookCellListTop, {
command: {
id: INSERT_MARKDOWN_CELL_AT_TOP_COMMAND_ID,
- title: localize('notebookActions.menu.insertMarkdown', "$(add) Markdown"),
+ title: '$(add) ' + localize('notebookActions.menu.insertMarkdown', "Markdown"),
tooltip: localize('notebookActions.menu.insertMarkdown.tooltip', "Add Markdown Cell")
},
order: 1,
diff --git a/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts
index 4f6a3e489c5..7f6ac0a645b 100644
--- a/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts
+++ b/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts
@@ -21,7 +21,10 @@ registerAction2(class NotebookConfigureLayoutAction extends Action2 {
constructor() {
super({
id: 'workbench.notebook.layout.select',
- title: localize('workbench.notebook.layout.select.label', "Select between Notebook Layouts"),
+ title: {
+ value: localize('workbench.notebook.layout.select.label', "Select between Notebook Layouts"),
+ original: 'Select between Notebook Layouts'
+ },
f1: true,
precondition: ContextKeyExpr.equals(`config.${NotebookSetting.openGettingStarted}`, true),
category: NOTEBOOK_ACTIONS_CATEGORY,
@@ -57,7 +60,10 @@ registerAction2(class NotebookConfigureLayoutAction extends Action2 {
constructor() {
super({
id: 'workbench.notebook.layout.configure',
- title: localize('workbench.notebook.layout.configure.label', "Customize Notebook Layout"),
+ title: {
+ value: localize('workbench.notebook.layout.configure.label', "Customize Notebook Layout"),
+ original: 'Customize Notebook Layout'
+ },
f1: true,
category: NOTEBOOK_ACTIONS_CATEGORY,
menu: [
@@ -79,7 +85,10 @@ registerAction2(class NotebookConfigureLayoutFromEditorTitle extends Action2 {
constructor() {
super({
id: 'workbench.notebook.layout.configure.editorTitle',
- title: localize('workbench.notebook.layout.configure.label', "Customize Notebook Layout"),
+ title: {
+ value: localize('workbench.notebook.layout.configure.label', "Customize Notebook Layout"),
+ original: 'Customize Notebook Layout'
+ },
f1: false,
category: NOTEBOOK_ACTIONS_CATEGORY,
menu: [
@@ -177,7 +186,10 @@ registerAction2(class SaveMimeTypeDisplayOrder extends Action2 {
constructor() {
super({
id: 'notebook.saveMimeTypeOrder',
- title: localize('notebook.saveMimeTypeOrder', 'Save Mimetype Display Order'),
+ title: {
+ value: localize('notebook.saveMimeTypeOrder', 'Save Mimetype Display Order'),
+ original: 'Save Mimetype Display Order'
+ },
f1: true,
category: NOTEBOOK_ACTIONS_CATEGORY,
precondition: NOTEBOOK_IS_ACTIVE_EDITOR,
diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts
index c7d1f0d6594..da1a9c3a04a 100644
--- a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts
+++ b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts
@@ -156,7 +156,7 @@ class PropertyHeader extends Disposable {
this._toolbar = new ToolBar(cellToolbarContainer, this.contextMenuService, {
actionViewItemProvider: action => {
if (action instanceof MenuItemAction) {
- const item = new CodiconActionViewItem(action, this.keybindingService, this.notificationService, this.contextKeyService, this.themeService);
+ const item = new CodiconActionViewItem(action, undefined, this.keybindingService, this.notificationService, this.contextKeyService, this.themeService, this.contextMenuService);
return item;
}
diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts
index 07374920577..244f65f50ca 100644
--- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts
+++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts
@@ -187,7 +187,7 @@ export class CellDiffSideBySideRenderer implements IListRenderer<SideBySideDiffE
const toolbar = new ToolBar(cellToolbarContainer, this.contextMenuService, {
actionViewItemProvider: action => {
if (action instanceof MenuItemAction) {
- const item = new CodiconActionViewItem(action, this.keybindingService, this.notificationService, this.contextKeyService, this.themeService);
+ const item = new CodiconActionViewItem(action, undefined, this.keybindingService, this.notificationService, this.contextKeyService, this.themeService, this.contextMenuService);
return item;
}
diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts
index 466fd1bf565..33bf079467f 100644
--- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts
+++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts
@@ -846,9 +846,10 @@ configurationRegistry.registerConfiguration({
[NotebookSetting.showFoldingControls]: {
description: nls.localize('notebook.showFoldingControls.description', "Controls when the Markdown header folding arrow is shown."),
type: 'string',
- enum: ['always', 'mouseover'],
+ enum: ['always', 'never', 'mouseover'],
enumDescriptions: [
nls.localize('showFoldingControls.always', "The folding controls are always visible."),
+ nls.localize('showFoldingControls.never', "Never show the folding controls and reduce the gutter size."),
nls.localize('showFoldingControls.mouseover', "The folding controls are visible only on mouseover."),
],
default: 'mouseover',
@@ -880,7 +881,7 @@ configurationRegistry.registerConfiguration({
tags: ['notebookLayout']
},
[NotebookSetting.markupFontSize]: {
- markdownDescription: nls.localize('notebook.markup.fontSize', "Controls the font size in pixels of rendered markup in notebooks. When set to `0`, 120% of `#editor.fontSize#` is used."),
+ markdownDescription: nls.localize('notebook.markup.fontSize', "Controls the font size in pixels of rendered markup in notebooks. When set to {0}, 120% of {1} is used.", '`0`', '`#editor.fontSize#`'),
type: 'number',
default: 0,
tags: ['notebookLayout']
@@ -899,13 +900,13 @@ configurationRegistry.registerConfiguration({
tags: ['notebookLayout']
},
[NotebookSetting.outputFontSize]: {
- markdownDescription: nls.localize('notebook.outputFontSize', "Font size for the output text for notebook cells. When set to 0 `#editor.fontSize#` is used."),
+ markdownDescription: nls.localize('notebook.outputFontSize', "Font size for the output text for notebook cells. When set to {0}, {1} is used.", '`0`', '`#editor.fontSize#`'),
type: 'number',
default: 0,
tags: ['notebookLayout']
},
[NotebookSetting.outputFontFamily]: {
- markdownDescription: nls.localize('notebook.outputFontFamily', "The font family for the output text for notebook cells. When set to empty, the `#editor.fontFamily#` is used."),
+ markdownDescription: nls.localize('notebook.outputFontFamily', "The font family for the output text for notebook cells. When set to empty, the {0} is used.", '`#editor.fontFamily#`'),
type: 'string',
tags: ['notebookLayout']
},
diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts
index 367c1f80e41..99d1d8de1f0 100644
--- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts
+++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts
@@ -244,6 +244,7 @@ export class NotebookEditor extends EditorPane implements IEditorPaneWithSelecti
type WorkbenchNotebookOpenClassification = {
owner: 'rebornix';
+ comment: 'The notebook file open metrics. Used to get a better understanding of the performance of notebook file opening';
scheme: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
ext: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
viewType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts
index 6047d713272..3debc2599a6 100644
--- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts
+++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts
@@ -1100,6 +1100,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
}
type WorkbenchNotebookOpenClassification = {
owner: 'rebornix';
+ comment: 'Identify the notebook editor view type';
scheme: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
ext: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
viewType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
@@ -1692,8 +1693,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
private _restoreSelectedKernel(viewState: INotebookEditorViewState | undefined): void {
if (viewState?.selectedKernelId && this.textModel) {
- const kernel = this.notebookKernelService.getMatchingKernel(this.textModel).all.find(k => k.id === viewState.selectedKernelId);
- if (kernel) {
+ const matching = this.notebookKernelService.getMatchingKernel(this.textModel);
+ const kernel = matching.all.find(k => k.id === viewState.selectedKernelId);
+ // Selected kernel may have already been picked prior to the view state loading
+ // If so, don't overwrite it with the saved kernel.
+ if (kernel && !matching.selected) {
this.notebookKernelService.selectKernelForNotebook(kernel, this.textModel);
}
}
diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts
index fe682e0e9aa..1808c007e8f 100644
--- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts
+++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts
@@ -6,22 +6,9 @@
import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
import * as DOM from 'vs/base/browser/dom';
import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
-import { MenuItemAction } from 'vs/platform/actions/common/actions';
-import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
-import { INotificationService } from 'vs/platform/notification/common/notification';
-import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
-import { IThemeService } from 'vs/platform/theme/common/themeService';
export class CodiconActionViewItem extends MenuEntryActionViewItem {
- constructor(
- _action: MenuItemAction,
- @IKeybindingService keybindingService: IKeybindingService,
- @INotificationService notificationService: INotificationService,
- @IContextKeyService contextKeyService: IContextKeyService,
- @IThemeService themeService: IThemeService,
- ) {
- super(_action, undefined, keybindingService, notificationService, contextKeyService, themeService);
- }
+
override updateLabel(): void {
if (this.options.label && this.label) {
DOM.reset(this.label, ...renderLabelWithIcons(this._commandAction.label ?? ''));
@@ -32,16 +19,6 @@ export class CodiconActionViewItem extends MenuEntryActionViewItem {
export class ActionViewWithLabel extends MenuEntryActionViewItem {
private _actionLabel?: HTMLAnchorElement;
- constructor(
- _action: MenuItemAction,
- @IKeybindingService keybindingService: IKeybindingService,
- @INotificationService notificationService: INotificationService,
- @IContextKeyService contextKeyService: IContextKeyService,
- @IThemeService themeService: IThemeService,
- ) {
- super(_action, undefined, keybindingService, notificationService, contextKeyService, themeService);
- }
-
override render(container: HTMLElement): void {
super.render(container);
container.classList.add('notebook-action-view-item');
diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts
index c84443b70ab..35502e4e1a9 100644
--- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts
+++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts
@@ -42,7 +42,7 @@ export class BetweenCellToolbar extends CellPart {
actionViewItemProvider: action => {
if (action instanceof MenuItemAction) {
if (this._notebookEditor.notebookOptions.getLayoutConfiguration().insertToolbarAlignment === 'center') {
- return instantiationService.createInstance(CodiconActionViewItem, action);
+ return instantiationService.createInstance(CodiconActionViewItem, action, undefined);
} else {
return instantiationService.createInstance(MenuEntryActionViewItem, action, undefined);
}
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 0ce36982b24..f0eb46d08bb 100644
--- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts
+++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts
@@ -400,6 +400,10 @@ async function webviewPreloads(ctx: PreloadContext) {
function focusFirstFocusableInCell(cellId: string) {
const cellOutputContainer = document.getElementById(cellId);
if (cellOutputContainer) {
+ if (cellOutputContainer.contains(document.activeElement)) {
+ return;
+ }
+
const focusableElement = cellOutputContainer.querySelector('[tabindex="0"], [href], button, input, option, select, textarea') as HTMLElement | null;
focusableElement?.focus();
}
diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts
index 951fbe0e2e2..a024bbe93a6 100644
--- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts
+++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts
@@ -17,7 +17,7 @@ import { FindMatch, IModelDecorationOptions, IModelDeltaDecoration, TrackedRange
import { MultiModelEditStackElement, SingleModelEditStackElement } from 'vs/editor/common/model/editStack';
import { IntervalNode, IntervalTree } from 'vs/editor/common/model/intervalTree';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
-import { WorkspaceTextEdit } from 'vs/editor/common/languages';
+import { IWorkspaceTextEdit } from 'vs/editor/common/languages';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { FoldingRegions } from 'vs/editor/contrib/folding/browser/foldingRanges';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -924,14 +924,15 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
return;
}
- const textEdits: WorkspaceTextEdit[] = [];
+ const textEdits: IWorkspaceTextEdit[] = [];
this._lastNotebookEditResource.push(matches[0].cell.uri);
matches.forEach(match => {
match.matches.forEach((singleMatch, index) => {
if ((singleMatch as OutputFindMatch).index === undefined) {
textEdits.push({
- edit: { range: (singleMatch as FindMatch).range, text: texts[index] },
+ versionId: undefined,
+ textEdit: { range: (singleMatch as FindMatch).range, text: texts[index] },
resource: match.cell.uri
});
}
diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts
index 1d1d38c2689..c80acf0d2ec 100644
--- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts
+++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts
@@ -69,7 +69,7 @@ class FixedLabelStrategy implements IActionLayoutStrategy {
const a = this.editorToolbar.primaryActions.find(a => a.action.id === action.id);
if (a && a.renderLabel) {
- return action instanceof MenuItemAction ? this.instantiationService.createInstance(ActionViewWithLabel, action) : undefined;
+ return action instanceof MenuItemAction ? this.instantiationService.createInstance(ActionViewWithLabel, action, undefined) : undefined;
} else {
return action instanceof MenuItemAction ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined) : undefined;
}
@@ -144,7 +144,7 @@ class DynamicLabelStrategy implements IActionLayoutStrategy {
const a = this.editorToolbar.primaryActions.find(a => a.action.id === action.id);
if (a && a.renderLabel) {
- return action instanceof MenuItemAction ? this.instantiationService.createInstance(ActionViewWithLabel, action) : undefined;
+ return action instanceof MenuItemAction ? this.instantiationService.createInstance(ActionViewWithLabel, action, undefined) : undefined;
} else {
return action instanceof MenuItemAction ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined) : undefined;
}
@@ -360,7 +360,7 @@ export class NotebookEditorToolbar extends Disposable {
if (this._renderLabel !== RenderLabel.Never) {
const a = this._primaryActions.find(a => a.action.id === action.id);
if (a && a.renderLabel) {
- return action instanceof MenuItemAction ? this.instantiationService.createInstance(ActionViewWithLabel, action) : undefined;
+ return action instanceof MenuItemAction ? this.instantiationService.createInstance(ActionViewWithLabel, action, undefined) : undefined;
} else {
return action instanceof MenuItemAction ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined) : undefined;
}
diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts
index 92d6c18e45f..ab97127c986 100644
--- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts
+++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts
@@ -37,7 +37,7 @@ export class ListTopCellToolbar extends Disposable {
this.toolbar = this._register(new ToolBar(this.topCellToolbar, this.contextMenuService, {
actionViewItemProvider: action => {
if (action instanceof MenuItemAction) {
- const item = this.instantiationService.createInstance(CodiconActionViewItem, action);
+ const item = this.instantiationService.createInstance(CodiconActionViewItem, action, undefined);
return item;
}
diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts
index e590c5cddb9..7eca3e5a6cf 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts
@@ -17,7 +17,7 @@ import { ISplice } from 'vs/base/common/sequence';
import { URI, UriComponents } from 'vs/base/common/uri';
import { ILineChange } from 'vs/editor/common/diff/diffComputer';
import * as editorCommon from 'vs/editor/common/editorCommon';
-import { Command } from 'vs/editor/common/languages';
+import { Command, WorkspaceEditMetadata } from 'vs/editor/common/languages';
import { IReadonlyTextBuffer } from 'vs/editor/common/model';
import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
@@ -497,6 +497,14 @@ export interface ICellMoveEdit {
export type IImmediateCellEditOperation = ICellOutputEditByHandle | ICellPartialMetadataEditByHandle | ICellOutputItemEdit | ICellPartialInternalMetadataEdit | ICellPartialInternalMetadataEditByHandle | ICellPartialMetadataEdit;
export type ICellEditOperation = IImmediateCellEditOperation | ICellReplaceEdit | ICellOutputEdit | ICellMetadataEdit | ICellPartialMetadataEdit | ICellPartialInternalMetadataEdit | IDocumentMetadataEdit | ICellMoveEdit | ICellOutputItemEdit | ICellLanguageEdit;
+
+export interface IWorkspaceNotebookCellEdit {
+ metadata?: WorkspaceEditMetadata;
+ resource: URI;
+ notebookVersionId: number | undefined;
+ cellEdit: ICellPartialMetadataEdit | IDocumentMetadataEdit | ICellReplaceEdit;
+}
+
export interface NotebookData {
readonly cells: ICellDto2[];
readonly metadata: NotebookDocumentMetadata;
@@ -927,8 +935,7 @@ export const NotebookSetting = {
interactiveWindowCollapseCodeCells: 'interactiveWindow.collapseCellInputCode',
outputLineHeight: 'notebook.outputLineHeight',
outputFontSize: 'notebook.outputFontSize',
- outputFontFamily: 'notebook.outputFontFamily',
- interactiveWindowAlwaysScrollOnNewCell: 'interactiveWindow.alwaysScrollOnNewCell'
+ outputFontFamily: 'notebook.outputFontFamily'
} as const;
export const enum CellStatusbarAlignment {
diff --git a/src/vs/workbench/contrib/notebook/common/notebookOptions.ts b/src/vs/workbench/contrib/notebook/common/notebookOptions.ts
index 426871f84fa..2aa4ca14cf8 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookOptions.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookOptions.ts
@@ -59,7 +59,7 @@ export interface NotebookLayoutConfiguration {
globalToolbar: boolean;
consolidatedOutputButton: boolean;
consolidatedRunButton: boolean;
- showFoldingControls: 'always' | 'mouseover';
+ showFoldingControls: 'always' | 'never' | 'mouseover';
dragAndDropEnabled: boolean;
fontSize: number;
outputFontSize: number;
@@ -385,7 +385,7 @@ export class NotebookOptions extends Disposable {
}
private _computeShowFoldingControlsOption() {
- return this.configurationService.getValue<'always' | 'mouseover'>(NotebookSetting.showFoldingControls) ?? 'mouseover';
+ return this.configurationService.getValue<'always' | 'never' | 'mouseover'>(NotebookSetting.showFoldingControls) ?? 'mouseover';
}
private _computeFocusIndicatorOption() {
diff --git a/src/vs/workbench/contrib/output/browser/outputServices.ts b/src/vs/workbench/contrib/output/browser/outputServices.ts
index c13ec6b5e0c..e9f97c01b29 100644
--- a/src/vs/workbench/contrib/output/browser/outputServices.ts
+++ b/src/vs/workbench/contrib/output/browser/outputServices.ts
@@ -48,7 +48,7 @@ class OutputChannel extends Disposable implements IOutputChannel {
}
update(mode: OutputChannelUpdateMode, till?: number): void {
- this.model.update(mode, till);
+ this.model.update(mode, till, true);
}
clear(): void {
diff --git a/src/vs/workbench/contrib/output/common/outputChannelModel.ts b/src/vs/workbench/contrib/output/common/outputChannelModel.ts
index 4c01e67797c..58f8374650b 100644
--- a/src/vs/workbench/contrib/output/common/outputChannelModel.ts
+++ b/src/vs/workbench/contrib/output/common/outputChannelModel.ts
@@ -26,7 +26,7 @@ import { OutputChannelUpdateMode } from 'vs/workbench/services/output/common/out
export interface IOutputChannelModel extends IDisposable {
readonly onDispose: Event<void>;
append(output: string): void;
- update(mode: OutputChannelUpdateMode, till?: number): void;
+ update(mode: OutputChannelUpdateMode, till: number | undefined, immediate: boolean): void;
loadModel(): Promise<ITextModel>;
clear(): void;
replace(value: string): void;
@@ -129,12 +129,12 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel
}
clear(): void {
- this.update(OutputChannelUpdateMode.Clear, this.endOffset);
+ this.update(OutputChannelUpdateMode.Clear, this.endOffset, true);
}
- update(mode: OutputChannelUpdateMode, till?: number): void {
+ update(mode: OutputChannelUpdateMode, till: number | undefined, immediate: boolean): void {
const loadModelPromise: Promise<any> = this.loadModelPromise ? this.loadModelPromise : Promise.resolve();
- loadModelPromise.then(() => this.doUpdate(mode, till));
+ loadModelPromise.then(() => this.doUpdate(mode, till, immediate));
}
loadModel(): Promise<ITextModel> {
@@ -174,7 +174,7 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel
return this.model;
}
- private doUpdate(mode: OutputChannelUpdateMode, till?: number): void {
+ private doUpdate(mode: OutputChannelUpdateMode, till: number | undefined, immediate: boolean): void {
if (mode === OutputChannelUpdateMode.Clear || mode === OutputChannelUpdateMode.Replace) {
this.startOffset = this.endOffset = isNumber(till) ? till : this.endOffset;
this.cancelModelUpdate();
@@ -198,7 +198,7 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel
}
else {
- this.appendContent(this.model, token);
+ this.appendContent(this.model, immediate, token);
}
}
@@ -206,7 +206,7 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel
this.doUpdateModel(model, [EditOperation.delete(model.getFullModelRange())], VSBuffer.fromString(''));
}
- private async appendContent(model: ITextModel, token: CancellationToken): Promise<void> {
+ private async appendContent(model: ITextModel, immediate: boolean, token: CancellationToken): Promise<void> {
this.appendThrottler.trigger(async () => {
/* Abort if operation is cancelled */
if (token.isCancellationRequested) {
@@ -234,7 +234,7 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel
const lastLineMaxColumn = model.getLineMaxColumn(lastLine);
const edits = [EditOperation.insert(new Position(lastLine, lastLineMaxColumn), contentToAppend.toString())];
this.doUpdateModel(model, edits, contentToAppend);
- });
+ }, immediate ? 0 : undefined);
}
private async replaceContent(model: ITextModel, token: CancellationToken): Promise<void> {
@@ -298,10 +298,10 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel
if (!this.modelUpdateInProgress) {
if (isNumber(size) && this.endOffset > size) {
// Reset - Content is removed
- this.update(OutputChannelUpdateMode.Clear, 0);
+ this.update(OutputChannelUpdateMode.Clear, 0, true);
}
}
- this.update(OutputChannelUpdateMode.Append);
+ this.update(OutputChannelUpdateMode.Append, undefined, false /* Not needed to update immediately. Wait to collect more changes and update. */);
}
}
@@ -340,13 +340,13 @@ class OutputChannelBackedByFile extends FileOutputChannelModel implements IOutpu
override append(message: string): void {
this.write(message);
- this.update(OutputChannelUpdateMode.Append);
+ this.update(OutputChannelUpdateMode.Append, undefined, this.isVisible());
}
override replace(message: string): void {
const till = this._offset;
this.write(message);
- this.update(OutputChannelUpdateMode.Replace, till);
+ this.update(OutputChannelUpdateMode.Replace, till, true);
}
private write(content: string): void {
@@ -391,8 +391,8 @@ export class DelegatedOutputChannelModel extends Disposable implements IOutputCh
this.outputChannelModel.then(outputChannelModel => outputChannelModel.append(output));
}
- update(mode: OutputChannelUpdateMode, till?: number): void {
- this.outputChannelModel.then(outputChannelModel => outputChannelModel.update(mode, till));
+ update(mode: OutputChannelUpdateMode, till: number | undefined, immediate: boolean): void {
+ this.outputChannelModel.then(outputChannelModel => outputChannelModel.update(mode, till, immediate));
}
loadModel(): Promise<ITextModel> {
diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css
index d44eec7e5d5..11f242b2646 100644
--- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css
+++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css
@@ -351,7 +351,7 @@
font-style: italic;
}
-.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title > .misc-label .setting-item-overrides:hover,
+.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title > .misc-label .setting-item-overrides.with-custom-hover:hover,
.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title > .misc-label .setting-item-ignored:hover,
.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title > .misc-label .setting-item-default-overridden:hover {
text-decoration: underline;
diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts b/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts
index 8f833f178f6..2c59b8bfc44 100644
--- a/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts
+++ b/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts
@@ -554,7 +554,7 @@ export class SettingMatches {
// Trim excess ending characters off the query.
singleWordQuery = singleWordQuery.toLowerCase().replace(/[\s-\._]+$/, '');
lineToSearch = lineToSearch.toLowerCase();
- const singleWordRegex = new RegExp(`\\b${singleWordQuery}\\b`);
+ const singleWordRegex = new RegExp(`\\b${strings.escapeRegExpCharacters(singleWordQuery)}\\b`);
if (singleWordRegex.test(lineToSearch)) {
this.matchType |= SettingMatchType.WholeWordMatch;
}
diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts
index a10179c4ac7..ae71ad571f6 100644
--- a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts
+++ b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts
@@ -51,12 +51,6 @@ export class SettingsTreeIndicatorsLabel implements IDisposable {
this.indicatorsContainerElement = DOM.append(container, $('.misc-label'));
this.indicatorsContainerElement.style.display = 'inline';
- const scopeOverridesIndicator = this.createScopeOverridesIndicator();
- this.scopeOverridesElement = scopeOverridesIndicator.element;
- this.scopeOverridesLabel = scopeOverridesIndicator.label;
- this.syncIgnoredElement = this.createSyncIgnoredElement();
- this.defaultOverrideIndicatorElement = this.createDefaultOverrideIndicator();
-
this.hoverDelegate = {
showHover: (options: IHoverDelegateOptions, focus?: boolean) => {
return hoverService.showHover(options, focus);
@@ -65,6 +59,12 @@ export class SettingsTreeIndicatorsLabel implements IDisposable {
delay: configurationService.getValue<number>('workbench.hover.delay'),
placement: 'element'
};
+
+ const scopeOverridesIndicator = this.createScopeOverridesIndicator();
+ this.scopeOverridesElement = scopeOverridesIndicator.element;
+ this.scopeOverridesLabel = scopeOverridesIndicator.label;
+ this.syncIgnoredElement = this.createSyncIgnoredElement();
+ this.defaultOverrideIndicatorElement = this.createDefaultOverrideIndicator();
}
private createScopeOverridesIndicator(): { element: HTMLElement; label: SimpleIconLabel } {
@@ -139,6 +139,7 @@ export class SettingsTreeIndicatorsLabel implements IDisposable {
// Render inline if we have the flag and there are scope overrides to render,
// or if there is only one scope override to render and no language overrides.
this.scopeOverridesElement.style.display = 'inline';
+ this.scopeOverridesElement.classList.remove('with-custom-hover');
this.hover?.dispose();
// Just show all the text in the label.
@@ -170,6 +171,7 @@ export class SettingsTreeIndicatorsLabel implements IDisposable {
// show the text in a custom hover only if
// the feature flag isn't on.
this.scopeOverridesElement.style.display = 'inline';
+ this.scopeOverridesElement.classList.add('with-custom-hover');
const scopeOverridesLabelText = element.isConfigured ?
localize('alsoConfiguredElsewhere', "Also modified elsewhere") :
localize('configuredElsewhere', "Modified elsewhere");
diff --git a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts
index 148c6c8d379..1680881aeb1 100644
--- a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts
+++ b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts
@@ -26,7 +26,7 @@ interface IConfiguration extends IWindowsConfiguration {
debug?: { console?: { wordWrap?: boolean } };
editor?: { accessibilitySupport?: 'on' | 'off' | 'auto' };
security?: { workspace?: { trust?: { enabled?: boolean } } };
- window: IWindowSettings & { experimental?: { windowControlsOverlay?: { enabled?: boolean } } };
+ window: IWindowSettings & { experimental?: { windowControlsOverlay?: { enabled?: boolean }; useSandbox?: boolean } };
workbench?: { experimental?: { settingsProfiles?: { enabled?: boolean } } };
}
@@ -34,6 +34,7 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo
private titleBarStyle: 'native' | 'custom' | undefined;
private windowControlsOverlayEnabled: boolean | undefined;
+ private windowSandboxEnabled: boolean | undefined;
private nativeTabs: boolean | undefined;
private nativeFullScreen: boolean | undefined;
private clickThroughInactive: boolean | undefined;
@@ -66,11 +67,16 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo
}
// Windows: Window Controls Overlay
- if (isWindows && typeof config.window?.experimental?.windowControlsOverlay?.enabled === 'boolean' && config.window?.experimental?.windowControlsOverlay?.enabled !== this.windowControlsOverlayEnabled) {
+ if (isWindows && typeof config.window?.experimental?.windowControlsOverlay?.enabled === 'boolean' && config.window.experimental.windowControlsOverlay.enabled !== this.windowControlsOverlayEnabled) {
this.windowControlsOverlayEnabled = config.window.experimental.windowControlsOverlay.enabled;
changed = true;
}
+ // Windows: Sandbox
+ if (typeof config.window?.experimental?.useSandbox === 'boolean' && config.window.experimental.useSandbox !== this.windowSandboxEnabled) {
+ this.windowSandboxEnabled = config.window.experimental.useSandbox;
+ changed = true;
+ }
// macOS: Native tabs
if (isMacintosh && typeof config.window?.nativeTabs === 'boolean' && config.window.nativeTabs !== this.nativeTabs) {
diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts
index b6488780711..0b2e94b01bc 100644
--- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts
+++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts
@@ -353,7 +353,7 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
},
'remote.autoForwardPortsSource': {
type: 'string',
- markdownDescription: localize('remote.autoForwardPortsSource', "Sets the source from which ports are automatically forwarded when `remote.autoForwardPorts` is true. On Windows and Mac remotes, the `process` option has no effect and `output` will be used. Requires a reload to take effect."),
+ markdownDescription: localize('remote.autoForwardPortsSource', "Sets the source from which ports are automatically forwarded when {0} is true. On Windows and Mac remotes, the `process` option has no effect and `output` will be used. Requires a reload to take effect.", '`#remote.autoForwardPorts#`'),
enum: ['process', 'output'],
enumDescriptions: [
localize('remote.autoForwardPortsSource.process', "Ports will be automatically forwarded when discovered by watching for processes that are started and include a port."),
@@ -463,7 +463,7 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
}
},
defaultSnippets: [{ body: { onAutoForward: 'ignore' } }],
- markdownDescription: localize('remote.portsAttributes.defaults', "Set default properties that are applied to all ports that don't get properties from the setting `remote.portsAttributes`. For example:\n\n```\n{\n \"onAutoForward\": \"ignore\"\n}\n```"),
+ markdownDescription: localize('remote.portsAttributes.defaults', "Set default properties that are applied to all ports that don't get properties from the setting {0}. For example:\n\n```\n{\n \"onAutoForward\": \"ignore\"\n}\n```", '`#remote.portsAttributes#`'),
additionalProperties: false
},
'remote.localPortHost': {
diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts
index 1a733fb7680..56be4b4065f 100644
--- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts
+++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts
@@ -2400,6 +2400,7 @@ export class SCMViewPane extends ViewPane {
if (widget) {
widget.focus();
+ this.tree.setFocus([], e.browserEvent);
const selection = this.tree.getSelection();
@@ -2411,7 +2412,13 @@ export class SCMViewPane extends ViewPane {
return;
} else if (isSCMActionButton(e.element)) {
this.scmViewService.focus(e.element.repository);
- this.actionButtonRenderer.focusActionButton(e.element);
+
+ // Focus the action button
+ const target = e.browserEvent?.target as HTMLElement;
+ if (target.classList.contains('monaco-tl-row') || target.classList.contains('button-container')) {
+ this.actionButtonRenderer.focusActionButton(e.element);
+ this.tree.setFocus([], e.browserEvent);
+ }
return;
}
@@ -2637,19 +2644,11 @@ export class SCMActionButton implements IDisposable {
return;
}
- const executeButtonAction = async (commandId: string, ...args: any[]) => {
- try {
- await this.commandService.executeCommand(commandId, ...args);
- } catch (ex) {
- this.notificationService.error(ex);
- }
- };
-
if (button.secondaryCommands?.length) {
const actions: IAction[] = [];
for (let index = 0; index < button.secondaryCommands.length; index++) {
for (const command of button.secondaryCommands[index]) {
- actions.push(new Action(command.id, command.title, undefined, true, async () => await executeButtonAction(command.id, ...(command.arguments || []))));
+ actions.push(new Action(command.id, command.title, undefined, true, async () => await this.executeCommand(command.id, ...(command.arguments || []))));
}
if (index !== button.secondaryCommands.length - 1) {
actions.push(new Separator());
@@ -2661,6 +2660,7 @@ export class SCMActionButton implements IDisposable {
actions: actions,
addPrimaryActionToDropdown: false,
contextMenuProvider: this.contextMenuService,
+ title: button.command.tooltip,
supportIcons: true
});
} else if (button.description) {
@@ -2674,8 +2674,7 @@ export class SCMActionButton implements IDisposable {
this.button.enabled = button.enabled;
this.button.label = button.command.title;
- this.button.element.title = button.command.tooltip ?? '';
- this.button.onDidClick(async () => await executeButtonAction(button.command.id, ...(button.command.arguments || [])), null, this.disposables.value);
+ this.button.onDidClick(async () => await this.executeCommand(button.command.id, ...(button.command.arguments || [])), null, this.disposables.value);
this.disposables.value!.add(this.button);
this.disposables.value!.add(attachButtonStyler(this.button, this.themeService));
@@ -2690,4 +2689,12 @@ export class SCMActionButton implements IDisposable {
this.button = undefined;
clearNode(this.container);
}
+
+ private async executeCommand(commandId: string, ...args: any[]): Promise<void> {
+ try {
+ await this.commandService.executeCommand(commandId, ...args);
+ } catch (ex) {
+ this.notificationService.error(ex);
+ }
+ }
}
diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts
index 77782e09059..099169c6ab3 100644
--- a/src/vs/workbench/contrib/search/browser/search.contribution.ts
+++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts
@@ -365,7 +365,10 @@ registerAction2(class CancelSearchAction extends Action2 {
constructor() {
super({
id: 'search.action.cancel',
- title: nls.localize('CancelSearchAction.label', "Cancel Search"),
+ title: {
+ value: nls.localize('CancelSearchAction.label', "Cancel Search"),
+ original: 'Cancel Search'
+ },
icon: searchStopIcon,
category,
f1: true,
@@ -392,7 +395,10 @@ registerAction2(class RefreshAction extends Action2 {
constructor() {
super({
id: 'search.action.refreshSearchResults',
- title: nls.localize('RefreshAction.label', "Refresh"),
+ title: {
+ value: nls.localize('RefreshAction.label', "Refresh"),
+ original: 'Refresh'
+ },
icon: searchRefreshIcon,
precondition: Constants.ViewHasSearchPatternKey,
category,
@@ -414,7 +420,10 @@ registerAction2(class CollapseDeepestExpandedLevelAction extends Action2 {
constructor() {
super({
id: 'search.action.collapseSearchResults',
- title: nls.localize('CollapseDeepestExpandedLevelAction.label', "Collapse All"),
+ title: {
+ value: nls.localize('CollapseDeepestExpandedLevelAction.label', "Collapse All"),
+ original: 'Collapse All'
+ },
category,
icon: searchCollapseAllIcon,
f1: true,
@@ -436,7 +445,10 @@ registerAction2(class ExpandAllAction extends Action2 {
constructor() {
super({
id: 'search.action.expandSearchResults',
- title: nls.localize('ExpandAllAction.label', "Expand All"),
+ title: {
+ value: nls.localize('ExpandAllAction.label', "Expand All"),
+ original: 'Expand All'
+ },
category,
icon: searchExpandAllIcon,
f1: true,
@@ -458,7 +470,10 @@ registerAction2(class ClearSearchResultsAction extends Action2 {
constructor() {
super({
id: 'search.action.clearSearchResults',
- title: nls.localize('ClearSearchResultsAction.label', "Clear Search Results"),
+ title: {
+ value: nls.localize('ClearSearchResultsAction.label', "Clear Search Results"),
+ original: 'Clear Search Results'
+ },
category,
icon: searchClearIcon,
f1: true,
@@ -829,7 +844,7 @@ configurationRegistry.registerConfiguration({
type: 'string', // expression ({ "**/*.js": { "when": "$(basename).js" } })
pattern: '\\w*\\$\\(basename\\)\\w*',
default: '$(basename).ext',
- markdownDescription: nls.localize('exclude.when', 'Additional check on the siblings of a matching file. Use \\$(basename) as variable for the matching file name.')
+ markdownDescription: nls.localize({ key: 'exclude.when', comment: ['\\$(basename) should not be translated'] }, 'Additional check on the siblings of a matching file. Use \\$(basename) as variable for the matching file name.')
}
}
}
@@ -981,7 +996,7 @@ configurationRegistry.registerConfiguration({
'search.searchOnTypeDebouncePeriod': {
type: 'number',
default: 300,
- markdownDescription: nls.localize('search.searchOnTypeDebouncePeriod', "When `#search.searchOnType#` is enabled, controls the timeout in milliseconds between a character being typed and the search starting. Has no effect when `search.searchOnType` is disabled.")
+ markdownDescription: nls.localize('search.searchOnTypeDebouncePeriod', "When {0} is enabled, controls the timeout in milliseconds between a character being typed and the search starting. Has no effect when {0} is disabled.", '`#search.searchOnType#`')
},
'search.searchEditor.doubleClickBehaviour': {
type: 'string',
diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts
index ed23a5f2632..a75b4184bd2 100644
--- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts
+++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts
@@ -41,6 +41,8 @@ import { Disposable } from 'vs/base/common/lifecycle';
const OpenInEditorCommandId = 'search.action.openInEditor';
const OpenNewEditorToSideCommandId = 'search.action.openNewEditorToSide';
const FocusQueryEditorWidgetCommandId = 'search.action.focusQueryEditorWidget';
+const FocusQueryEditorFilesToIncludeCommandId = 'search.action.focusFilesToInclude';
+const FocusQueryEditorFilesToExcludeCommandId = 'search.action.focusFilesToExclude';
const ToggleSearchEditorCaseSensitiveCommandId = 'toggleSearchEditorCaseSensitive';
const ToggleSearchEditorWholeWordCommandId = 'toggleSearchEditorWholeWord';
@@ -377,6 +379,44 @@ registerAction2(class extends Action2 {
registerAction2(class extends Action2 {
constructor() {
super({
+ id: FocusQueryEditorFilesToIncludeCommandId,
+ title: { value: localize('search.action.focusFilesToInclude', "Focus Search Editor Files to Include"), original: 'Focus Search Editor Files to Include' },
+ category,
+ f1: true,
+ precondition: SearchEditorConstants.InSearchEditor,
+ });
+ }
+ async run(accessor: ServicesAccessor) {
+ const editorService = accessor.get(IEditorService);
+ const input = editorService.activeEditor;
+ if (input instanceof SearchEditorInput) {
+ (editorService.activeEditorPane as SearchEditor).focusFilesToIncludeInput();
+ }
+ }
+});
+
+registerAction2(class extends Action2 {
+ constructor() {
+ super({
+ id: FocusQueryEditorFilesToExcludeCommandId,
+ title: { value: localize('search.action.focusFilesToExclude', "Focus Search Editor Files to Exclude"), original: 'Focus Search Editor Files to Exclude' },
+ category,
+ f1: true,
+ precondition: SearchEditorConstants.InSearchEditor,
+ });
+ }
+ async run(accessor: ServicesAccessor) {
+ const editorService = accessor.get(IEditorService);
+ const input = editorService.activeEditor;
+ if (input instanceof SearchEditorInput) {
+ (editorService.activeEditorPane as SearchEditor).focusFilesToExcludeInput();
+ }
+ }
+});
+
+registerAction2(class extends Action2 {
+ constructor() {
+ super({
id: ToggleSearchEditorCaseSensitiveCommandId,
title: { value: localize('searchEditor.action.toggleSearchEditorCaseSensitive', "Toggle Match Case"), original: 'Toggle Match Case' },
category,
diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts
index 380f8dbcc8f..54be4156b6e 100644
--- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts
+++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts
@@ -275,6 +275,20 @@ export class SearchEditor extends AbstractTextCodeEditor<SearchEditorViewState>
this.queryEditorWidget.searchInput.focus();
}
+ focusFilesToIncludeInput() {
+ if (!this.showingIncludesExcludes) {
+ this.toggleIncludesExcludes(true);
+ }
+ this.inputPatternIncludes.focus();
+ }
+
+ focusFilesToExcludeInput() {
+ if (!this.showingIncludesExcludes) {
+ this.toggleIncludesExcludes(true);
+ }
+ this.inputPatternExcludes.focus();
+ }
+
focusNextInput() {
if (this.queryEditorWidget.searchInputHasFocus()) {
if (this.showingIncludesExcludes) {
diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts
index eb1497201da..784296b70de 100644
--- a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts
+++ b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts
@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import { parse as jsonParse, getNodeType } from 'vs/base/common/json';
-import { forEach } from 'vs/base/common/collections';
import { localize } from 'vs/nls';
import { extname, basename } from 'vs/base/common/path';
import { SnippetParser, Variable, Placeholder, Text } from 'vs/editor/contrib/snippet/browser/snippetParser';
@@ -256,17 +255,15 @@ export class SnippetFile {
this._loadPromise = Promise.resolve(this._load()).then(content => {
const data = <JsonSerializedSnippets>jsonParse(content);
if (getNodeType(data) === 'object') {
- forEach(data, entry => {
- const { key: name, value: scopeOrTemplate } = entry;
+ for (const [name, scopeOrTemplate] of Object.entries(data)) {
if (isJsonSerializedSnippet(scopeOrTemplate)) {
this._parseSnippet(name, scopeOrTemplate, this.data);
} else {
- forEach(scopeOrTemplate, entry => {
- const { key: name, value: template } = entry;
+ for (const [name, template] of Object.entries(scopeOrTemplate)) {
this._parseSnippet(name, template, this.data);
- });
+ }
}
- });
+ }
}
return this;
});
diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts
index 366ed64536b..abc007e863d 100644
--- a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts
+++ b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts
@@ -358,7 +358,12 @@ class SnippetsService implements ISnippetsService {
await this._initFolderSnippets(SnippetSource.User, userSnippetsFolder, disposables);
};
this._disposables.add(disposables);
- this._disposables.add(this._userDataProfileService.onDidChangeCurrentProfile(() => this._pendingWork.push(updateUserSnippets())));
+ this._disposables.add(this._userDataProfileService.onDidChangeCurrentProfile(e => e.join((async () => {
+ if (e.preserveData) {
+ await this._fileService.copy(e.previous.snippetsHome, e.profile.snippetsHome);
+ }
+ this._pendingWork.push(updateUserSnippets());
+ })())));
await updateUserSnippets();
}
diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts
index 9ef8982eebe..93cbe879116 100644
--- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts
+++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts
@@ -53,7 +53,9 @@ import {
ITaskSet, TaskGroup, ExecutionEngine, JsonSchemaVersion, TaskSourceKind,
TaskSorter, ITaskIdentifier, TASK_RUNNING_STATE, TaskRunSource,
KeyedTaskIdentifier as KeyedTaskIdentifier, TaskDefinition, RuntimeType,
- USER_TASKS_GROUP_KEY
+ USER_TASKS_GROUP_KEY,
+ TaskSettingId,
+ TasksSchemaProperties
} from 'vs/workbench/contrib/tasks/common/tasks';
import { ITaskService, ITaskProvider, IProblemMatcherRunOptions, ICustomizationProperties, ITaskFilter, IWorkspaceFolderTaskResult, CustomExecutionSupportedContext, ShellExecutionSupportedContext, ProcessExecutionSupportedContext } from 'vs/workbench/contrib/tasks/common/taskService';
import { getTemplates as getTaskTemplates } from 'vs/workbench/contrib/tasks/common/taskTemplates';
@@ -1082,7 +1084,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
}).then((value) => {
if (runSource === TaskRunSource.User) {
this.getWorkspaceTasks().then(workspaceTasks => {
- RunAutomaticTasks.promptForPermission(this, this._storageService, this._notificationService, this._workspaceTrustManagementService, this._openerService, workspaceTasks);
+ RunAutomaticTasks.promptForPermission(this, this._storageService, this._notificationService, this._workspaceTrustManagementService, this._openerService, this._configurationService, workspaceTasks);
});
}
return value;
@@ -1093,7 +1095,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
}
private _isProvideTasksEnabled(): boolean {
- const settingValue = this._configurationService.getValue('task.autoDetect');
+ const settingValue = this._configurationService.getValue(TaskSettingId.AutoDetect);
return settingValue === 'on';
}
@@ -1588,7 +1590,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
{
identifier: id,
dependsOn: extensionTasks.map((extensionTask) => { return { uri: extensionTask.getWorkspaceFolder()!.uri, task: extensionTask._id }; }),
- name: id,
+ name: id
}
);
return { task, resolver };
@@ -1688,7 +1690,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
Prompt = 'prompt'
}
- const saveBeforeRunTaskConfig: SaveBeforeRunConfigOptions = this._configurationService.getValue('task.saveBeforeRun');
+ const saveBeforeRunTaskConfig: SaveBeforeRunConfigOptions = this._configurationService.getValue(TaskSettingId.SaveBeforeRun);
if (saveBeforeRunTaskConfig === SaveBeforeRunConfigOptions.Never) {
return false;
@@ -2738,7 +2740,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
}
this._showQuickPick(tasks ? tasks : taskResult!.tasks, placeholder,
{
- label: nls.localize('TaskService.noEntryToRunSlow', '$(plus) Configure a Task'),
+ label: '$(plus) ' + nls.localize('TaskService.noEntryToRun', 'Configure a Task'),
task: null
},
true).
@@ -2748,7 +2750,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
} else {
this._showTwoLevelQuickPick(placeholder,
{
- label: nls.localize('TaskService.noEntryToRun', '$(plus) Configure a Task'),
+ label: '$(plus) ' + nls.localize('TaskService.noEntryToRun', 'Configure a Task'),
task: null
}).
then(pickThen);
@@ -3523,11 +3525,11 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
}
const configTasks: (TaskConfig.ICustomTask | TaskConfig.IConfiguringTask)[] = [];
- const suppressTaskName = !!this._configurationService.getValue('tasks.suppressTaskName', { resource: folder.uri });
+ const suppressTaskName = !!this._configurationService.getValue(TasksSchemaProperties.SuppressTaskName, { resource: folder.uri });
const globalConfig = {
- windows: <ICommandUpgrade>this._configurationService.getValue('tasks.windows', { resource: folder.uri }),
- osx: <ICommandUpgrade>this._configurationService.getValue('tasks.osx', { resource: folder.uri }),
- linux: <ICommandUpgrade>this._configurationService.getValue('tasks.linux', { resource: folder.uri })
+ windows: <ICommandUpgrade>this._configurationService.getValue(TasksSchemaProperties.Windows, { resource: folder.uri }),
+ osx: <ICommandUpgrade>this._configurationService.getValue(TasksSchemaProperties.Osx, { resource: folder.uri }),
+ linux: <ICommandUpgrade>this._configurationService.getValue(TasksSchemaProperties.Linux, { resource: folder.uri })
};
tasks.get(folder).forEach(task => {
const configTask = this._upgradeTask(task, suppressTaskName, globalConfig);
@@ -3539,14 +3541,14 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
this._workspaceTasksPromise = undefined;
await this._writeConfiguration(folder, 'tasks.tasks', configTasks);
await this._writeConfiguration(folder, 'tasks.version', '2.0.0');
- if (this._configurationService.getValue('tasks.showOutput', { resource: folder.uri })) {
- await this._configurationService.updateValue('tasks.showOutput', undefined, { resource: folder.uri });
+ if (this._configurationService.getValue(TasksSchemaProperties.ShowOutput, { resource: folder.uri })) {
+ await this._configurationService.updateValue(TasksSchemaProperties.ShowOutput, undefined, { resource: folder.uri });
}
- if (this._configurationService.getValue('tasks.isShellCommand', { resource: folder.uri })) {
- await this._configurationService.updateValue('tasks.isShellCommand', undefined, { resource: folder.uri });
+ if (this._configurationService.getValue(TasksSchemaProperties.IsShellCommand, { resource: folder.uri })) {
+ await this._configurationService.updateValue(TasksSchemaProperties.IsShellCommand, undefined, { resource: folder.uri });
}
- if (this._configurationService.getValue('tasks.suppressTaskName', { resource: folder.uri })) {
- await this._configurationService.updateValue('tasks.suppressTaskName', undefined, { resource: folder.uri });
+ if (this._configurationService.getValue(TasksSchemaProperties.SuppressTaskName, { resource: folder.uri })) {
+ await this._configurationService.updateValue(TasksSchemaProperties.SuppressTaskName, undefined, { resource: folder.uri });
}
}
this._updateSetup();
diff --git a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts
index 3d91091af90..aa0d0441b79 100644
--- a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts
+++ b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts
@@ -8,7 +8,6 @@ import * as resources from 'vs/base/common/resources';
import { Disposable } from 'vs/base/common/lifecycle';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { ITaskService, IWorkspaceFolderTaskResult } from 'vs/workbench/contrib/tasks/common/taskService';
-import { forEach } from 'vs/base/common/collections';
import { RunOnOptions, Task, TaskRunSource, TaskSource, TaskSourceKind, TASKS_CATEGORY, WorkspaceFileTaskSource, IWorkspaceTaskSource } from 'vs/workbench/contrib/tasks/common/tasks';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
@@ -16,18 +15,19 @@ import { IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/commo
import { Action2 } from 'vs/platform/actions/common/actions';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust';
-import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
+import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { URI } from 'vs/base/common/uri';
import { Event } from 'vs/base/common/event';
import { ILogService } from 'vs/platform/log/common/log';
-const ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE = 'tasks.run.allowAutomatic';
+const HAS_PROMPTED_FOR_AUTOMATIC_TASKS = 'task.hasPromptedForAutomaticTasks';
+const ALLOW_AUTOMATIC_TASKS = 'task.allowAutomaticTasks';
export class RunAutomaticTasks extends Disposable implements IWorkbenchContribution {
constructor(
@ITaskService private readonly _taskService: ITaskService,
- @IStorageService private readonly _storageService: IStorageService,
+ @IConfigurationService private readonly _configurationService: IConfigurationService,
@IWorkspaceTrustManagementService private readonly _workspaceTrustManagementService: IWorkspaceTrustManagementService,
@ILogService private readonly _logService: ILogService) {
super();
@@ -43,7 +43,7 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut
}
this._logService.trace('RunAutomaticTasks: Checking if automatic tasks should run.');
- const isFolderAutomaticAllowed = this._storageService.getBoolean(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, StorageScope.WORKSPACE, undefined);
+ const isFolderAutomaticAllowed = this._configurationService.getValue(ALLOW_AUTOMATIC_TASKS) !== 'off';
await this._workspaceTrustManagementService.workspaceTrustInitialized;
const isWorkspaceTrusted = this._workspaceTrustManagementService.isWorkspaceTrusted();
// Only run if allowed. Prompting for permission occurs when a user first tries to run a task.
@@ -106,22 +106,22 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut
});
}
if (resultElement.configurations) {
- forEach(resultElement.configurations.byIdentifier, (configedTask) => {
- if (configedTask.value.runOptions.runOn === RunOnOptions.folderOpen) {
+ for (const configuredTask of Object.values(resultElement.configurations.byIdentifier)) {
+ if (configuredTask.runOptions.runOn === RunOnOptions.folderOpen) {
tasks.push(new Promise<Task | undefined>(resolve => {
- taskService.getTask(resultElement.workspaceFolder, configedTask.value._id, true).then(task => resolve(task));
+ taskService.getTask(resultElement.workspaceFolder, configuredTask._id, true).then(task => resolve(task));
}));
- if (configedTask.value._label) {
- taskNames.push(configedTask.value._label);
+ if (configuredTask._label) {
+ taskNames.push(configuredTask._label);
} else {
- taskNames.push(configedTask.value.configures.task);
+ taskNames.push(configuredTask.configures.task);
}
- const location = RunAutomaticTasks._getTaskSource(configedTask.value._source);
+ const location = RunAutomaticTasks._getTaskSource(configuredTask._source);
if (location) {
locations.set(location.fsPath, location);
}
}
- });
+ }
}
});
}
@@ -129,30 +129,33 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut
}
public static async promptForPermission(taskService: ITaskService, storageService: IStorageService, notificationService: INotificationService, workspaceTrustManagementService: IWorkspaceTrustManagementService,
- openerService: IOpenerService, workspaceTaskResult: Map<string, IWorkspaceFolderTaskResult>) {
+ openerService: IOpenerService, configurationService: IConfigurationService, workspaceTaskResult: Map<string, IWorkspaceFolderTaskResult>) {
const isWorkspaceTrusted = workspaceTrustManagementService.isWorkspaceTrusted;
if (!isWorkspaceTrusted) {
return;
}
-
- const isFolderAutomaticAllowed = storageService.getBoolean(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, StorageScope.WORKSPACE, undefined);
- if (isFolderAutomaticAllowed !== undefined) {
+ if (configurationService.getValue(ALLOW_AUTOMATIC_TASKS) === 'off') {
return;
}
+ const hasShownPromptForAutomaticTasks = storageService.getBoolean(HAS_PROMPTED_FOR_AUTOMATIC_TASKS, StorageScope.WORKSPACE, undefined);
const { tasks, taskNames, locations } = RunAutomaticTasks._findAutoTasks(taskService, workspaceTaskResult);
if (taskNames.length > 0) {
- // We have automatic tasks, prompt to allow.
- this._showPrompt(notificationService, storageService, taskService, openerService, taskNames, locations).then(allow => {
- if (allow) {
- RunAutomaticTasks._runTasks(taskService, tasks);
- }
- });
+ if (configurationService.getValue(ALLOW_AUTOMATIC_TASKS) === 'on') {
+ RunAutomaticTasks._runTasks(taskService, tasks);
+ } else if (!hasShownPromptForAutomaticTasks) {
+ // We have automatic tasks, prompt to allow.
+ this._showPrompt(notificationService, storageService, openerService, configurationService, taskNames, locations).then(allow => {
+ if (allow) {
+ RunAutomaticTasks._runTasks(taskService, tasks);
+ }
+ });
+ }
}
}
- private static _showPrompt(notificationService: INotificationService, storageService: IStorageService, taskService: ITaskService,
- openerService: IOpenerService, taskNames: Array<string>, locations: Map<string, URI>): Promise<boolean> {
+ private static _showPrompt(notificationService: INotificationService, storageService: IStorageService,
+ openerService: IOpenerService, configurationService: IConfigurationService, taskNames: Array<string>, locations: Map<string, URI>): Promise<boolean> {
return new Promise<boolean>(resolve => {
notificationService.prompt(Severity.Info, nls.localize('tasks.run.allowAutomatic',
"This workspace has tasks ({0}) defined ({1}) that run automatically when you open this workspace. Do you allow automatic tasks to run when you open this workspace?",
@@ -163,14 +166,15 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut
label: nls.localize('allow', "Allow and run"),
run: () => {
resolve(true);
- storageService.store(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, true, StorageScope.WORKSPACE, StorageTarget.MACHINE);
+ configurationService.updateValue(ALLOW_AUTOMATIC_TASKS, true, ConfigurationTarget.WORKSPACE);
}
},
{
label: nls.localize('disallow', "Disallow"),
run: () => {
resolve(false);
- storageService.store(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, false, StorageScope.WORKSPACE, StorageTarget.MACHINE);
+ configurationService.updateValue(ALLOW_AUTOMATIC_TASKS, false, ConfigurationTarget.WORKSPACE);
+
}
},
{
@@ -183,9 +187,9 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut
}
}]
);
+ storageService.store(HAS_PROMPTED_FOR_AUTOMATIC_TASKS, true, StorageScope.WORKSPACE, StorageTarget.MACHINE);
});
}
-
}
export class ManageAutomaticTaskRunning extends Action2 {
@@ -203,14 +207,13 @@ export class ManageAutomaticTaskRunning extends Action2 {
public async run(accessor: ServicesAccessor): Promise<any> {
const quickInputService = accessor.get(IQuickInputService);
- const storageService = accessor.get(IStorageService);
+ const configurationService = accessor.get(IConfigurationService);
const allowItem: IQuickPickItem = { label: nls.localize('workbench.action.tasks.allowAutomaticTasks', "Allow Automatic Tasks in Folder") };
const disallowItem: IQuickPickItem = { label: nls.localize('workbench.action.tasks.disallowAutomaticTasks', "Disallow Automatic Tasks in Folder") };
const value = await quickInputService.pick([allowItem, disallowItem], { canPickMany: false });
if (!value) {
return;
}
-
- storageService.store(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, value === allowItem, StorageScope.WORKSPACE, StorageTarget.MACHINE);
+ configurationService.updateValue(ALLOW_AUTOMATIC_TASKS, value === allowItem, ConfigurationTarget.WORKSPACE);
}
}
diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts
index eff66ddba5f..00c85294b7b 100644
--- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts
+++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts
@@ -20,7 +20,7 @@ import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatus
import { IOutputChannelRegistry, Extensions as OutputExt } from 'vs/workbench/services/output/common/output';
-import { ITaskEvent, TaskEventKind, TaskGroup, TASKS_CATEGORY, TASK_RUNNING_STATE } from 'vs/workbench/contrib/tasks/common/tasks';
+import { ITaskEvent, TaskEventKind, TaskGroup, TaskSettingId, TASKS_CATEGORY, TASK_RUNNING_STATE } from 'vs/workbench/contrib/tasks/common/tasks';
import { ITaskService, ProcessExecutionSupportedContext, ShellExecutionSupportedContext } from 'vs/workbench/contrib/tasks/common/taskService';
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions';
@@ -431,7 +431,7 @@ configurationRegistry.registerConfiguration({
title: nls.localize('tasksConfigurationTitle', "Tasks"),
type: 'object',
properties: {
- 'task.problemMatchers.neverPrompt': {
+ [TaskSettingId.ProblemMatchersNeverPrompt]: {
markdownDescription: nls.localize('task.problemMatchers.neverPrompt', "Configures whether to show the problem matcher prompt when running a task. Set to `true` to never prompt, or use a dictionary of task types to turn off prompting only for specific task types."),
'oneOf': [
{
@@ -453,13 +453,13 @@ configurationRegistry.registerConfiguration({
],
default: false
},
- 'task.autoDetect': {
+ [TaskSettingId.AutoDetect]: {
markdownDescription: nls.localize('task.autoDetect', "Controls enablement of `provideTasks` for all task provider extension. If the Tasks: Run Task command is slow, disabling auto detect for task providers may help. Individual extensions may also provide settings that disable auto detection."),
type: 'string',
enum: ['on', 'off'],
default: 'on'
},
- 'task.slowProviderWarning': {
+ [TaskSettingId.SlowProviderWarning]: {
markdownDescription: nls.localize('task.slowProviderWarning', "Configures whether a warning is shown when a provider is slow"),
'oneOf': [
{
@@ -476,32 +476,44 @@ configurationRegistry.registerConfiguration({
],
default: true
},
- 'task.quickOpen.history': {
+ [TaskSettingId.QuickOpenHistory]: {
markdownDescription: nls.localize('task.quickOpen.history', "Controls the number of recent items tracked in task quick open dialog."),
type: 'number',
default: 30, minimum: 0, maximum: 30
},
- 'task.quickOpen.detail': {
+ [TaskSettingId.QuickOpenDetail]: {
markdownDescription: nls.localize('task.quickOpen.detail', "Controls whether to show the task detail for tasks that have a detail in task quick picks, such as Run Task."),
type: 'boolean',
default: true
},
- 'task.quickOpen.skip': {
+ [TaskSettingId.QuickOpenSkip]: {
type: 'boolean',
description: nls.localize('task.quickOpen.skip', "Controls whether the task quick pick is skipped when there is only one task to pick from."),
default: false
},
- 'task.quickOpen.showAll': {
+ [TaskSettingId.QuickOpenShowAll]: {
type: 'boolean',
description: nls.localize('task.quickOpen.showAll', "Causes the Tasks: Run Task command to use the slower \"show all\" behavior instead of the faster two level picker where tasks are grouped by provider."),
default: false
},
- 'task.showDecorations': {
+ [TaskSettingId.AllowAutomaticTasks]: {
+ type: 'string',
+ enum: ['on', 'auto', 'off'],
+ enumDescriptions: [
+ nls.localize('ttask.allowAutomaticTasks.on', "Always"),
+ nls.localize('task.allowAutomaticTasks.auto', "Prompt for permission for each folder"),
+ nls.localize('task.allowAutomaticTasks.off', "Never"),
+ ],
+ description: nls.localize('task.allowAutomaticTasks', "Enable automatic tasks in the folder."),
+ default: 'auto',
+ restricted: true
+ },
+ [TaskSettingId.ShowDecorations]: {
type: 'boolean',
description: nls.localize('task.showDecorations', "Shows decorations at points of interest in the terminal buffer such as the first problem found via a watch task. Note that this will only take effect for future tasks."),
default: true
},
- 'task.saveBeforeRun': {
+ [TaskSettingId.SaveBeforeRun]: {
markdownDescription: nls.localize(
'task.saveBeforeRun',
'Save all dirty editors before running a task.'
diff --git a/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts b/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts
index 77dc6f60769..a2cc123e381 100644
--- a/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts
+++ b/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts
@@ -24,7 +24,6 @@ import { TaskQuickPickEntryType } from 'vs/workbench/contrib/tasks/browser/abstr
export const QUICKOPEN_DETAIL_CONFIG = 'task.quickOpen.detail';
export const QUICKOPEN_SKIP_CONFIG = 'task.quickOpen.skip';
-
export function isWorkspaceFolder(folder: IWorkspace | IWorkspaceFolder): folder is IWorkspaceFolder {
return 'uri' in folder;
}
@@ -108,7 +107,9 @@ export class TaskQuickPick extends Disposable {
groupLabel: string, extraButtons: IQuickInputButton[] = []) {
entries.push({ type: 'separator', label: groupLabel });
tasks.forEach(task => {
- entries.push(this._createTaskEntry(task, extraButtons));
+ if (!task.configurationProperties.hide) {
+ entries.push(this._createTaskEntry(task, extraButtons));
+ }
});
}
@@ -304,7 +305,7 @@ export class TaskQuickPick extends Disposable {
private async _doPickerSecondLevel(picker: IQuickPick<ITaskTwoLevelQuickPickEntry>, type: string) {
picker.busy = true;
if (type === SHOW_ALL) {
- const items = (await this._taskService.tasks()).sort((a, b) => this._sorter.compare(a, b)).map(task => this._createTaskEntry(task));
+ const items = (await this._taskService.tasks()).filter(t => !t.configurationProperties.hide).sort((a, b) => this._sorter.compare(a, b)).map(task => this._createTaskEntry(task));
items.push(...TaskQuickPick.allSettingEntries(this._configurationService));
picker.items = items;
} else {
@@ -353,9 +354,13 @@ export class TaskQuickPick extends Disposable {
private async _getEntriesForProvider(type: string): Promise<QuickPickInput<ITaskTwoLevelQuickPickEntry>[]> {
const tasks = (await this._taskService.tasks({ type })).sort((a, b) => this._sorter.compare(a, b));
- let taskQuickPickEntries: QuickPickInput<ITaskTwoLevelQuickPickEntry>[];
+ let taskQuickPickEntries: QuickPickInput<ITaskTwoLevelQuickPickEntry>[] = [];
if (tasks.length > 0) {
- taskQuickPickEntries = tasks.map(task => this._createTaskEntry(task));
+ for (const task of tasks) {
+ if (!task.configurationProperties.hide) {
+ taskQuickPickEntries.push(this._createTaskEntry(task));
+ }
+ }
taskQuickPickEntries.push({
type: 'separator'
}, {
diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts
index d1dd693f370..7cfab363b6d 100644
--- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts
+++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts
@@ -31,7 +31,7 @@ import { IOutputService } from 'vs/workbench/services/output/common/output';
import { StartStopProblemCollector, WatchingProblemCollector, ProblemCollectorEventKind, ProblemHandlingStrategy } from 'vs/workbench/contrib/tasks/common/problemCollectors';
import {
Task, CustomTask, ContributedTask, RevealKind, CommandOptions, IShellConfiguration, RuntimeType, PanelKind,
- TaskEvent, TaskEventKind, IShellQuotingOptions, ShellQuoting, CommandString, ICommandConfiguration, IExtensionTaskSource, TaskScope, RevealProblemKind, DependsOrder, TaskSourceKind, InMemoryTask, ITaskEvent
+ TaskEvent, TaskEventKind, IShellQuotingOptions, ShellQuoting, CommandString, ICommandConfiguration, IExtensionTaskSource, TaskScope, RevealProblemKind, DependsOrder, TaskSourceKind, InMemoryTask, ITaskEvent, TaskSettingId
} from 'vs/workbench/contrib/tasks/common/tasks';
import {
ITaskSystem, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, ITaskResolver,
@@ -209,10 +209,10 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
private readonly _onDidStateChange: Emitter<ITaskEvent>;
get taskShellIntegrationStartSequence(): string {
- return this._configurationService.getValue('task.showDecorations') ? VSCodeSequence(VSCodeOscPt.PromptStart) + VSCodeSequence(VSCodeOscPt.Property, `${VSCodeOscProperty.Task}=True`) + VSCodeSequence(VSCodeOscPt.CommandStart) : '';
+ return this._configurationService.getValue(TaskSettingId.ShowDecorations) ? VSCodeSequence(VSCodeOscPt.PromptStart) + VSCodeSequence(VSCodeOscPt.Property, `${VSCodeOscProperty.Task}=True`) + VSCodeSequence(VSCodeOscPt.CommandStart) : '';
}
get taskShellIntegrationOutputSequence(): string {
- return this._configurationService.getValue('task.showDecorations') ? VSCodeSequence(VSCodeOscPt.CommandExecuted) : '';
+ return this._configurationService.getValue(TaskSettingId.ShowDecorations) ? VSCodeSequence(VSCodeOscPt.CommandExecuted) : '';
}
constructor(
@@ -516,12 +516,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
for (const dependency of task.configurationProperties.dependsOn) {
const dependencyTask = await resolver.resolve(dependency.uri, dependency.task!);
if (dependencyTask) {
- if (dependencyTask.configurationProperties.icon) {
- dependencyTask.configurationProperties.icon.id ||= task.configurationProperties.icon?.id;
- dependencyTask.configurationProperties.icon.color ||= task.configurationProperties.icon?.color;
- } else {
- dependencyTask.configurationProperties.icon = task.configurationProperties.icon;
- }
+ this._adoptConfigurationForDependencyTask(dependencyTask, task);
const key = dependencyTask.getMapKey();
let promise = this._activeTasks[key] ? this._getDependencyPromise(this._activeTasks[key]) : undefined;
if (!promise) {
@@ -590,6 +585,21 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
});
}
+ private _adoptConfigurationForDependencyTask(dependencyTask: Task, task: Task): void {
+ if (dependencyTask.configurationProperties.icon) {
+ dependencyTask.configurationProperties.icon.id ||= task.configurationProperties.icon?.id;
+ dependencyTask.configurationProperties.icon.color ||= task.configurationProperties.icon?.color;
+ } else {
+ dependencyTask.configurationProperties.icon = task.configurationProperties.icon;
+ }
+
+ if (dependencyTask.configurationProperties.hide) {
+ dependencyTask.configurationProperties.hide ||= task.configurationProperties.hide;
+ } else {
+ dependencyTask.configurationProperties.hide = task.configurationProperties.hide;
+ }
+ }
+
private async _getDependencyPromise(task: IActiveTerminalData): Promise<ITaskSummary> {
if (!task.task.configurationProperties.isBackground) {
return task.promise;
@@ -1147,7 +1157,10 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
}, 'Executing task: {0}', commandLine), { excludeLeadingNewLine: true }) + this.taskShellIntegrationOutputSequence;
}
} else {
- shellLaunchConfig.initialText = this.taskShellIntegrationStartSequence + this.taskShellIntegrationOutputSequence;
+ shellLaunchConfig.initialText = {
+ text: this.taskShellIntegrationStartSequence + this.taskShellIntegrationOutputSequence,
+ trailingNewLine: false
+ };
}
} else {
const commandExecutable = (task.command.runtime !== RuntimeType.CustomExecution) ? CommandString.value(command) : undefined;
@@ -1187,7 +1200,10 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
}, 'Executing task: {0}', `${shellLaunchConfig.executable} ${getArgsToEcho(shellLaunchConfig.args)}`), { excludeLeadingNewLine: true }) + this.taskShellIntegrationOutputSequence;
}
} else {
- shellLaunchConfig.initialText = this.taskShellIntegrationStartSequence + this.taskShellIntegrationOutputSequence;
+ shellLaunchConfig.initialText = {
+ text: this.taskShellIntegrationStartSequence + this.taskShellIntegrationOutputSequence,
+ trailingNewLine: false
+ };
}
}
diff --git a/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts b/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts
index 077bd89a60e..fa67f8ecf65 100644
--- a/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts
+++ b/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts
@@ -45,6 +45,13 @@ const shellCommand: IJSONSchema = {
deprecationMessage: nls.localize('JsonSchema.tasks.isShellCommand.deprecated', 'The property isShellCommand is deprecated. Use the type property of the task and the shell property in the options instead. See also the 1.14 release notes.')
};
+
+const hide: IJSONSchema = {
+ type: 'boolean',
+ description: nls.localize('JsonSchema.hide', 'Hide this task from the run task quick pick'),
+ default: true
+};
+
const taskIdentifier: IJSONSchema = {
type: 'object',
additionalProperties: true,
@@ -407,6 +414,7 @@ const taskConfiguration: IJSONSchema = {
},
presentation: Objects.deepClone(presentation),
icon: Objects.deepClone(icon),
+ hide: Objects.deepClone(hide),
options: options,
problemMatcher: {
$ref: '#/definitions/problemMatcherType',
@@ -479,6 +487,7 @@ taskDescriptionProperties.command = Objects.deepClone(command);
taskDescriptionProperties.args = Objects.deepClone(args);
taskDescriptionProperties.isShellCommand = Objects.deepClone(shellCommand);
taskDescriptionProperties.dependsOn = dependsOn;
+taskDescriptionProperties.hide = Objects.deepClone(hide);
taskDescriptionProperties.dependsOrder = dependsOrder;
taskDescriptionProperties.identifier = Objects.deepClone(identifier);
taskDescriptionProperties.type = Objects.deepClone(taskType);
diff --git a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts
index c5d4058bcb0..0bb00f57620 100644
--- a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts
+++ b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts
@@ -362,6 +362,11 @@ export interface IConfigurationProperties {
* The icon's color in the terminal tabs list
*/
color?: string;
+
+ /**
+ * Do not show this task in the run task quickpick
+ */
+ hide?: boolean;
}
export interface ICustomTask extends ICommandProperties, IConfigurationProperties {
@@ -1322,7 +1327,8 @@ namespace ConfigurationProperties {
{ property: 'presentation', type: CommandConfiguration.PresentationOptions },
{ property: 'problemMatchers' },
{ property: 'options' },
- { property: 'icon' }
+ { property: 'icon' },
+ { property: 'hide' }
];
export function from(this: void, external: IConfigurationProperties & { [key: string]: any }, context: IParseContext,
@@ -1350,7 +1356,7 @@ namespace ConfigurationProperties {
result.identifier = external.identifier;
}
result.icon = external.icon;
-
+ result.hide = external.hide;
if (external.isBackground !== undefined) {
result.isBackground = !!external.isBackground;
}
@@ -1483,7 +1489,7 @@ namespace ConfiguringTask {
type,
taskIdentifier,
RunOptions.fromConfiguration(external.runOptions),
- {}
+ { hide: external.hide }
);
const configuration = ConfigurationProperties.from(external, context, true, source, typeDeclaration.properties);
result.addTaskLoadMessages(configuration.errors);
@@ -1635,7 +1641,8 @@ namespace CustomTask {
{
name: configuredProps.configurationProperties.name || contributedTask.configurationProperties.name,
identifier: configuredProps.configurationProperties.identifier || contributedTask.configurationProperties.identifier,
- icon: configuredProps.configurationProperties.icon
+ icon: configuredProps.configurationProperties.icon,
+ hide: configuredProps.configurationProperties.hide
},
);
@@ -2119,7 +2126,7 @@ class ConfigurationParser {
identifier: name,
group: Tasks.TaskGroup.Build,
isBackground: isBackground,
- problemMatchers: matchers,
+ problemMatchers: matchers
}
);
const taskGroupKind = GroupKind.from(fileConfig.group);
diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts
index 1b52c33d02a..5877beb6437 100644
--- a/src/vs/workbench/contrib/tasks/common/tasks.ts
+++ b/src/vs/workbench/contrib/tasks/common/tasks.ts
@@ -549,6 +549,11 @@ export interface IConfigurationProperties {
* The icon for this task in the terminal tabs list
*/
icon?: { id?: string; color?: string };
+
+ /**
+ * Do not show this task in the run task quickpick
+ */
+ hide?: boolean;
}
export enum RunOnOptions {
@@ -914,6 +919,11 @@ export class ContributedTask extends CommonTask {
*/
icon: { id?: string; color?: string } | undefined;
+ /**
+ * Don't show the task in the run task quickpick
+ */
+ hide?: boolean;
+
public constructor(id: string, source: IExtensionTaskSource, label: string, type: string | undefined, defines: KeyedTaskIdentifier,
command: ICommandConfiguration, hasDefinedMatchers: boolean, runOptions: IRunOptions,
configurationProperties: IConfigurationProperties) {
@@ -922,6 +932,7 @@ export class ContributedTask extends CommonTask {
this.hasDefinedMatchers = hasDefinedMatchers;
this.command = command;
this.icon = configurationProperties.icon;
+ this.hide = configurationProperties.hide;
}
public override clone(): ContributedTask {
@@ -1179,6 +1190,30 @@ export namespace KeyedTaskIdentifier {
}
}
+export const enum TaskSettingId {
+ AutoDetect = 'task.autoDetect',
+ SaveBeforeRun = 'task.saveBeforeRun',
+ ShowDecorations = 'task.showDecorations',
+ ProblemMatchersNeverPrompt = 'task.problemMatchers.neverPrompt',
+ SlowProviderWarning = 'task.slowProviderWarning',
+ QuickOpenHistory = 'task.quickOpen.history',
+ QuickOpenDetail = 'task.quickOpen.detail',
+ QuickOpenSkip = 'task.quickOpen.skip',
+ QuickOpenShowAll = 'task.quickOpen.showAll',
+ AllowAutomaticTasks = 'task.allowAutomaticTasks'
+}
+
+export const enum TasksSchemaProperties {
+ Tasks = 'tasks',
+ SuppressTaskName = 'tasks.suppressTaskName',
+ Windows = 'tasks.windows',
+ Osx = 'tasks.osx',
+ Linux = 'tasks.linux',
+ ShowOutput = 'tasks.showOutput',
+ IsShellCommand = 'tasks.isShellCommand',
+ ServiceTestSetting = 'tasks.service.testSetting',
+}
+
export namespace TaskDefinition {
export function createTaskIdentifier(external: ITaskIdentifier, reporter: { error(message: string): void }): KeyedTaskIdentifier | undefined {
const definition = TaskDefinitionRegistry.get(external.type);
diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts
index bfc044ba133..6fc5f4eb043 100644
--- a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts
+++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts
@@ -189,9 +189,23 @@ export class TerminalSearchLinkOpener implements ITerminalLinkOpener {
// Try open as an absolute link
let resourceMatch: IResourceMatch | undefined;
if (absolutePath) {
- const slashNormalizedPath = this._os === OperatingSystem.Windows ? absolutePath.replace(/\\/g, '/') : absolutePath;
- const scheme = this._workbenchEnvironmentService.remoteAuthority ? Schemas.vscodeRemote : Schemas.file;
- const uri = URI.from({ scheme, path: slashNormalizedPath });
+ let normalizedAbsolutePath: string = absolutePath;
+ if (this._os === OperatingSystem.Windows) {
+ normalizedAbsolutePath = absolutePath.replace(/\\/g, '/');
+ if (normalizedAbsolutePath.match(/[a-z]:/i)) {
+ normalizedAbsolutePath = `/${normalizedAbsolutePath}`;
+ }
+ }
+ let uri: URI;
+ if (this._workbenchEnvironmentService.remoteAuthority) {
+ uri = URI.from({
+ scheme: Schemas.vscodeRemote,
+ authority: this._workbenchEnvironmentService.remoteAuthority,
+ path: normalizedAbsolutePath
+ });
+ } else {
+ uri = URI.file(normalizedAbsolutePath);
+ }
try {
const fileStat = await this._fileService.stat(uri);
resourceMatch = { uri, isDirectory: fileStat.isDirectory };
@@ -218,6 +232,16 @@ export class TerminalSearchLinkOpener implements ITerminalLinkOpener {
private async _tryOpenExactLink(text: string, link: ITerminalSimpleLink): Promise<boolean> {
const sanitizedLink = text.replace(/:\d+(:\d+)?$/, '');
+ // For links made up of only a file name (no folder), disallow exact link matching. For
+ // example searching for `foo.txt` when there is no cwd information available (ie. only the
+ // initial cwd) should NOT search as it's ambiguous if there are multiple matches.
+ //
+ // However, for a link like `src/foo.txt`, if there's an exact match for `src/foo.txt` in
+ // any folder we want to take it, even if there are partial matches like `src2/foo.txt`
+ // available.
+ if (!sanitizedLink.match(/[\\/]/)) {
+ return false;
+ }
try {
const result = await this._getExactMatch(sanitizedLink);
if (result) {
diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts
index 48e5c0659bc..cec80cf90b1 100644
--- a/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts
+++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts
@@ -49,10 +49,11 @@ export const winLocalLinkClause = '((' + winPathPrefix + '|(' + winExcludedPathC
/** As xterm reads from DOM, space in that case is nonbreaking char ASCII code - 160,
replacing space with nonBreakningSpace or space ASCII code - 32. */
export const lineAndColumnClause = [
+ '(([^:\\s\\(\\)<>\'\"\\[\\]]*) ((\\d+))(:(\\d+)))', // (file path) 336:9 [see #140780]
'((\\S*)[\'"], line ((\\d+)( column (\\d+))?))', // "(file path)", line 45 [see #40468]
'((\\S*)[\'"],((\\d+)(:(\\d+))?))', // "(file path)",45 [see #78205]
'((\\S*) on line ((\\d+)(, column (\\d+))?))', // (file path) on line 8, column 13
- '((\\S*):line ((\\d+)(, column (\\d+))?))', // (file path):line 8, column 13
+ '((\\S*):\\s?line ((\\d+)(, col(umn)? (\\d+))?))', // (file path):line 8, column 13, (file path): line 8, col 13
'(([^\\s\\(\\)]*)(\\s?[\\(\\[](\\d+)(,\\s?(\\d+))?)[\\)\\]])', // (file path)(45), (file path) (45), (file path)(45,18), (file path) (45,18), (file path)(45, 18), (file path) (45, 18), also with []
'(([^:\\s\\(\\)<>\'\"\\[\\]]*)(:(\\d+))?(:(\\d+))?)' // (file path):336, (file path):336:9
].join('|').replace(/ /g, `[${'\u00A0'} ]`);
diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh
index b0c525a298d..a2f4afa1400 100755
--- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh
+++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh
@@ -39,13 +39,6 @@ if [[ "$PROMPT_COMMAND" =~ .*(' '.*\;)|(\;.*' ').* ]]; then
builtin return
fi
-# Disable shell integration if HISTCONTROL is set to erase duplicate entries as the exit code
-# reporting relies on the duplicates existing
-if [[ "$HISTCONTROL" =~ .*erasedups.* ]]; then
- builtin unset VSCODE_SHELL_INTEGRATION
- builtin return
-fi
-
if [ -z "$VSCODE_SHELL_INTEGRATION" ]; then
builtin return
fi
@@ -56,7 +49,7 @@ __vsc_original_PS2="$PS2"
__vsc_custom_PS1=""
__vsc_custom_PS2=""
__vsc_in_command_execution="1"
-__vsc_last_history_id=$(history 1 | awk '{print $1;}')
+__vsc_current_command=""
__vsc_prompt_start() {
builtin printf "\033]633;A\007"
@@ -72,6 +65,7 @@ __vsc_update_cwd() {
__vsc_command_output_start() {
builtin printf "\033]633;C\007"
+ builtin printf "\033]633;E;$__vsc_current_command\007"
}
__vsc_continuation_start() {
@@ -83,12 +77,10 @@ __vsc_continuation_end() {
}
__vsc_command_complete() {
- local __vsc_history_id=$(builtin history 1 | awk '{print $1;}')
- if [[ "$__vsc_history_id" == "$__vsc_last_history_id" ]]; then
+ if [ "$__vsc_current_command" = "" ]; then
builtin printf "\033]633;D\007"
else
builtin printf "\033]633;D;%s\007" "$__vsc_status"
- __vsc_last_history_id=$__vsc_history_id
fi
__vsc_update_cwd
}
@@ -113,22 +105,27 @@ __vsc_update_prompt() {
__vsc_precmd() {
__vsc_command_complete "$__vsc_status"
+ __vsc_current_command=""
__vsc_update_prompt
}
__vsc_preexec() {
- if [ "$__vsc_in_command_execution" = "0" ]; then
- __vsc_initialized=1
- __vsc_in_command_execution="1"
- __vsc_command_output_start
+ __vsc_initialized=1
+ if [[ ! "$BASH_COMMAND" =~ ^__vsc_prompt* ]]; then
+ __vsc_current_command=$BASH_COMMAND
+ else
+ __vsc_current_command=""
fi
+ __vsc_command_output_start
}
# Debug trapping/preexec inspired by starship (ISC)
if [[ -n "${bash_preexec_imported:-}" ]]; then
__vsc_preexec_only() {
- __vsc_status="$?"
- __vsc_preexec
+ if [ "$__vsc_in_command_execution" = "0" ]; then
+ __vsc_in_command_execution="1"
+ __vsc_preexec
+ fi
}
precmd_functions+=(__vsc_prompt_cmd)
preexec_functions+=(__vsc_preexec_only)
@@ -136,15 +133,19 @@ else
__vsc_dbg_trap="$(trap -p DEBUG | cut -d' ' -f3 | tr -d \')"
if [[ -z "$__vsc_dbg_trap" ]]; then
__vsc_preexec_only() {
- __vsc_status="$?"
- __vsc_preexec
+ if [ "$__vsc_in_command_execution" = "0" ]; then
+ __vsc_in_command_execution="1"
+ __vsc_preexec
+ fi
}
trap '__vsc_preexec_only "$_"' DEBUG
elif [[ "$__vsc_dbg_trap" != '__vsc_preexec "$_"' && "$__vsc_dbg_trap" != '__vsc_preexec_all "$_"' ]]; then
__vsc_preexec_all() {
- __vsc_status="$?"
- builtin eval ${__vsc_dbg_trap}
- __vsc_preexec
+ if [ "$__vsc_in_command_execution" = "0" ]; then
+ __vsc_in_command_execution="1"
+ builtin eval ${__vsc_dbg_trap}
+ __vsc_preexec
+ fi
}
trap '__vsc_preexec_all "$_"' DEBUG
fi
@@ -153,6 +154,7 @@ fi
__vsc_update_prompt
__vsc_prompt_cmd_original() {
+ __vsc_status="$?"
if [[ ${IFS+set} ]]; then
__vsc_original_ifs="$IFS"
fi
diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh
index a94e7c11c71..7db2583a817 100644
--- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh
+++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh
@@ -21,6 +21,10 @@ if [[ "$VSCODE_INJECTION" == "1" ]]; then
. $USER_ZDOTDIR/.zshrc
ZDOTDIR=$VSCODE_ZDOTDIR
fi
+
+ if [[ -f $USER_ZDOTDIR/.zsh_history ]]; then
+ HISTFILE=$USER_ZDOTDIR/.zsh_history
+ fi
fi
# Shell integration was disabled by the shell, exit without warning assuming either the shell has
@@ -29,9 +33,8 @@ if [ -z "$VSCODE_SHELL_INTEGRATION" ]; then
builtin return
fi
-__vsc_initialized="0"
__vsc_in_command_execution="1"
-__vsc_last_history_id=0
+__vsc_current_command=""
__vsc_prompt_start() {
builtin printf "\033]633;A\007"
@@ -47,6 +50,7 @@ __vsc_update_cwd() {
__vsc_command_output_start() {
builtin printf "\033]633;C\007"
+ builtin printf "\033]633;E;$__vsc_current_command\007"
}
__vsc_continuation_start() {
@@ -66,17 +70,10 @@ __vsc_right_prompt_end() {
}
__vsc_command_complete() {
- builtin local __vsc_history_id=$(builtin history | tail -n1 | awk '{print $1;}')
- # Don't write the command complete sequence for the first prompt without an associated command
- if [[ "$__vsc_initialized" == "1" ]]; then
- if [[ "$__vsc_history_id" == "$__vsc_last_history_id" ]]; then
- builtin printf "\033]633;D\007"
- else
- builtin printf "\033]633;D;%s\007" "$__vsc_status"
- __vsc_last_history_id=$__vsc_history_id
- fi
- else
+ if [[ "$__vsc_current_command" == "" ]]; then
builtin printf "\033]633;D\007"
+ else
+ builtin printf "\033]633;D;%s\007" "$__vsc_status"
fi
__vsc_update_cwd
}
@@ -108,6 +105,7 @@ __vsc_precmd() {
fi
__vsc_command_complete "$__vsc_status"
+ __vsc_current_command=""
# in command execution
if [ -n "$__vsc_in_command_execution" ]; then
@@ -121,8 +119,8 @@ __vsc_preexec() {
if [ -n "$RPROMPT" ]; then
RPROMPT="$__vsc_prior_rprompt"
fi
- __vsc_initialized="1"
__vsc_in_command_execution="1"
+ __vsc_current_command=$1
__vsc_command_output_start
}
add-zsh-hook precmd __vsc_precmd
diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1
index aceb31ab780..5d7d4436094 100644
--- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1
+++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1
@@ -72,3 +72,19 @@ if (Get-Module -Name PSReadLine) {
# Set IsWindows property
[Console]::Write("`e]633;P;IsWindows=$($IsWindows)`a")
+
+# Set always on key handlers which map to default VS Code keybindings
+function Set-MappedKeyHandler {
+ param ([string[]] $Chord, [string[]]$Sequence)
+ $Handler = $(Get-PSReadLineKeyHandler -Chord $Chord)
+ if ($Handler) {
+ Set-PSReadLineKeyHandler -Chord $Sequence -Function $Handler.Function
+ }
+}
+function Set-MappedKeyHandlers {
+ Set-MappedKeyHandler -Chord Ctrl+Spacebar -Sequence 'F12,a'
+ Set-MappedKeyHandler -Chord Alt+Spacebar -Sequence 'F12,b'
+ Set-MappedKeyHandler -Chord Shift+Enter -Sequence 'F12,c'
+ Set-MappedKeyHandler -Chord Shift+End -Sequence 'F12,d'
+}
+Set-MappedKeyHandlers
diff --git a/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts b/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts
index dbbcea77cf9..35454bcae4e 100644
--- a/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts
+++ b/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts
@@ -239,6 +239,20 @@ class RemoteTerminalBackend extends BaseTerminalBackend implements ITerminalBack
return undefined;
}
+ async attachToRevivedProcess(id: number): Promise<ITerminalChildProcess | undefined> {
+ if (!this._remoteTerminalChannel) {
+ throw new Error(`Cannot create remote terminal when there is no remote!`);
+ }
+
+ try {
+ const newId = await this._remoteTerminalChannel.getRevivedPtyNewId(id) ?? id;
+ return await this.attachToProcess(newId);
+ } catch (e) {
+ this._logService.trace(`Couldn't attach to process ${e.message}`);
+ }
+ return undefined;
+ }
+
async listProcesses(): Promise<IProcessDetails[]> {
const terms = this._remoteTerminalChannel ? await this._remoteTerminalChannel.listProcesses() : [];
return terms.map(termDto => {
diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts
index d01b958ae13..a62e8b54d38 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts
@@ -177,6 +177,33 @@ if (isWindows) {
});
}
+// Map certain keybindings in pwsh to unused keys which get handled by PSReadLine handlers in the
+// shell integration script. This allows keystrokes that cannot be sent via VT sequences to work.
+// See https://github.com/microsoft/terminal/issues/879#issuecomment-497775007
+registerSendSequenceKeybinding('\x1b[24~a', { // F12,a -> ctrl+space (MenuComplete)
+ when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, WindowsShellType.PowerShell), TerminalContextKeys.terminalShellIntegrationEnabled, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()),
+ primary: KeyMod.CtrlCmd | KeyCode.Space,
+ mac: { primary: KeyMod.WinCtrl | KeyCode.Space }
+});
+registerSendSequenceKeybinding('\x1b[24~b', { // F12,b -> alt+space (SetMark)
+ when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, WindowsShellType.PowerShell), TerminalContextKeys.terminalShellIntegrationEnabled, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()),
+ primary: KeyMod.Alt | KeyCode.Space
+});
+registerSendSequenceKeybinding('\x1b[24~c', { // F12,c -> shift+enter (AddLine)
+ when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, WindowsShellType.PowerShell), TerminalContextKeys.terminalShellIntegrationEnabled, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()),
+ primary: KeyMod.Shift | KeyCode.Enter
+});
+registerSendSequenceKeybinding('\x1b[24~d', { // F12,d -> shift+end (SelectLine) - HACK: \x1b[1;2F is supposed to work but it doesn't
+ when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, WindowsShellType.PowerShell), TerminalContextKeys.terminalShellIntegrationEnabled, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()),
+ mac: { primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.RightArrow }
+});
+
+// Always on pwsh keybindings
+registerSendSequenceKeybinding('\x1b[1;2H', { // Shift+home
+ when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, WindowsShellType.PowerShell)),
+ mac: { primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.LeftArrow }
+});
+
// send ctrl+c to the iPad when the terminal is focused and ctrl+c is pressed to kill the process (work around for #114009)
if (isIOS) {
registerSendSequenceKeybinding(String.fromCharCode('C'.charCodeAt(0) - CTRL_LETTER_OFFSET), { // ctrl+c
diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts
index ddaf6f211db..aa125ac3d78 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminal.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts
@@ -11,7 +11,7 @@ import { FindReplaceState } from 'vs/editor/contrib/find/browser/findState';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IKeyMods } from 'vs/platform/quickinput/common/quickInput';
import { ITerminalCapabilityStore, ITerminalCommand } from 'vs/platform/terminal/common/capabilities/capabilities';
-import { IExtensionTerminalProfile, IProcessPropertyMap, IShellIntegration, IShellLaunchConfig, ITerminalDimensions, ITerminalLaunchError, ITerminalProfile, ITerminalTabLayoutInfoById, ProcessPropertyType, TerminalIcon, TerminalLocation, TerminalShellType, TitleEventSource } from 'vs/platform/terminal/common/terminal';
+import { IExtensionTerminalProfile, IProcessPropertyMap, IShellIntegration, IShellLaunchConfig, ITerminalDimensions, ITerminalLaunchError, ITerminalProfile, ITerminalTabLayoutInfoById, ProcessPropertyType, TerminalExitReason, TerminalIcon, TerminalLocation, TerminalShellType, TitleEventSource } from 'vs/platform/terminal/common/terminal';
import { IGenericMarkProperties } from 'vs/platform/terminal/common/terminalProcess';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
@@ -473,6 +473,11 @@ export interface ITerminalInstance {
readonly persistentProcessId: number | undefined;
/**
+ * The id of a persistent process during the shutdown process
+ */
+ shutdownPersistentProcessId: number | undefined;
+
+ /**
* Whether the process should be persisted across reloads.
*/
readonly shouldPersist: boolean;
@@ -567,6 +572,8 @@ export interface ITerminalInstance {
readonly exitCode: number | undefined;
+ readonly exitReason: TerminalExitReason | undefined;
+
readonly areLinksReady: boolean;
/**
@@ -651,17 +658,17 @@ export interface ITerminalInstance {
/**
* Dispose the terminal instance, removing it from the panel/service and freeing up resources.
*
- * @param immediate Whether the kill should be immediate or not. Immediate should only be used
- * when VS Code is shutting down or in cases where the terminal dispose was user initiated.
- * The immediate===false exists to cover an edge case where the final output of the terminal can
- * get cut off. If immediate kill any terminal processes immediately.
+ * @param reason The reason why the terminal is being disposed
*/
- dispose(immediate?: boolean): void;
+ dispose(reason?: TerminalExitReason): void;
/**
- * Inform the process that the terminal is now detached.
+ * Informs the process that the terminal is now detached and
+ * then disposes the terminal.
+ *
+ * @param reason The reason why the terminal is being disposed
*/
- detachFromProcess(): Promise<void>;
+ detachProcessAndDispose(reason: TerminalExitReason): Promise<void>;
/**
* Check if anything is selected in terminal.
@@ -673,6 +680,12 @@ export interface ITerminalInstance {
*/
copySelection(asHtml?: boolean, command?: ITerminalCommand): Promise<void>;
+
+ /**
+ * Copies the ouput of the last command
+ */
+ copyLastCommandOutput(): Promise<void>;
+
/**
* Current selection in the terminal.
*/
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts
index ed78d4a79e8..5f5bbce74dd 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts
@@ -28,7 +28,7 @@ import { IListService } from 'vs/platform/list/browser/listService';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IPickOptions, IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
-import { ITerminalProfile, TerminalLocation, TerminalSettingId, TitleEventSource } from 'vs/platform/terminal/common/terminal';
+import { ITerminalProfile, TerminalExitReason, TerminalLocation, TerminalSettingId, TitleEventSource } from 'vs/platform/terminal/common/terminal';
import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands';
import { CLOSE_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
@@ -307,7 +307,7 @@ export function registerTerminalActions() {
constructor() {
super({
id: TerminalCommandId.RunRecentCommand,
- title: { value: localize('workbench.action.terminal.runRecentCommand', "Run Recent Command"), original: 'Run Recent Command' },
+ title: { value: localize('workbench.action.terminal.runRecentCommand', "Run Recent Command..."), original: 'Run Recent Command...' },
f1: true,
category,
precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated)
@@ -330,8 +330,22 @@ export function registerTerminalActions() {
registerAction2(class extends Action2 {
constructor() {
super({
+ id: TerminalCommandId.CopyLastCommand,
+ title: { value: localize('workbench.action.terminal.copyLastCommand', 'Copy Last Command'), original: 'Copy Last Command' },
+ f1: true,
+ category,
+ precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated)
+ });
+ }
+ async run(accessor: ServicesAccessor): Promise<void> {
+ await accessor.get(ITerminalService).activeInstance?.copyLastCommandOutput();
+ }
+ });
+ registerAction2(class extends Action2 {
+ constructor() {
+ super({
id: TerminalCommandId.GoToRecentDirectory,
- title: { value: localize('workbench.action.terminal.goToRecentDirectory', "Go to Recent Directory"), original: 'Go to Recent Directory' },
+ title: { value: localize('workbench.action.terminal.goToRecentDirectory', "Go to Recent Directory..."), original: 'Go to Recent Directory...' },
f1: true,
category,
precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated)
@@ -1062,7 +1076,7 @@ export function registerTerminalActions() {
}
async run(accessor: ServicesAccessor) {
const terminalService = accessor.get(ITerminalService);
- await terminalService.activeInstance?.detachFromProcess();
+ await terminalService.activeInstance?.detachProcessAndDispose(TerminalExitReason.User);
}
});
registerAction2(class extends Action2 {
@@ -1709,6 +1723,7 @@ export function registerTerminalActions() {
const themeService = accessor.get(IThemeService);
const groupService = accessor.get(ITerminalGroupService);
const notificationService = accessor.get(INotificationService);
+
const picks: ITerminalQuickPickItem[] = [];
if (groupService.instances.length <= 1) {
notificationService.warn(localize('workbench.action.terminal.join.insufficientTerminals', 'Insufficient terminals for the join action'));
@@ -1718,7 +1733,7 @@ export function registerTerminalActions() {
for (const terminal of otherInstances) {
const group = groupService.getGroupForInstance(terminal);
if (group?.terminalInstances.length === 1) {
- const iconId = getIconId(terminal);
+ const iconId = getIconId(accessor, terminal);
const label = `$(${iconId}): ${terminal.title}`;
const iconClasses: string[] = [];
const colorClass = getColorClass(terminal);
@@ -2112,10 +2127,11 @@ export function registerTerminalActions() {
title: { value: localize('workbench.action.terminal.sizeToContentWidth', "Toggle Size to Content Width"), original: 'Toggle Size to Content Width' },
f1: true,
category,
- precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.isOpen, TerminalContextKeys.focus),
+ precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.isOpen),
keybinding: {
primary: KeyMod.Alt | KeyCode.KeyZ,
- weight: KeybindingWeight.WorkbenchContrib
+ weight: KeybindingWeight.WorkbenchContrib,
+ when: TerminalContextKeys.focus
}
});
}
@@ -2123,12 +2139,13 @@ export function registerTerminalActions() {
await accessor.get(ITerminalService).doWithActiveInstance(t => t.toggleSizeToContentWidth());
}
});
+
registerAction2(class extends Action2 {
constructor() {
super({
id: TerminalCommandId.SizeToContentWidthInstance,
title: terminalStrings.toggleSizeToContentWidth,
- f1: true,
+ f1: false,
category,
precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.focus)
});
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts
index b80d8fae41c..55032336cd5 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts
@@ -9,11 +9,11 @@ import { dispose, toDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { EditorInputCapabilities, IEditorIdentifier, IUntypedEditorInput } from 'vs/workbench/common/editor';
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
-import { EditorInput } from 'vs/workbench/common/editor/editorInput';
+import { EditorInput, IEditorCloseHandler } from 'vs/workbench/common/editor/editorInput';
import { ITerminalInstance, ITerminalInstanceService, terminalEditorId } from 'vs/workbench/contrib/terminal/browser/terminal';
import { getColorClass, getUriClasses } from 'vs/workbench/contrib/terminal/browser/terminalIcon';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
-import { IShellLaunchConfig, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
+import { IShellLaunchConfig, TerminalExitReason, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { ConfirmOnKill } from 'vs/workbench/contrib/terminal/common/terminal';
@@ -23,21 +23,22 @@ import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/termin
import { ConfirmResult, IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { Emitter } from 'vs/base/common/event';
-export class TerminalEditorInput extends EditorInput {
-
- protected readonly _onDidRequestAttach = this._register(new Emitter<ITerminalInstance>());
- readonly onDidRequestAttach = this._onDidRequestAttach.event;
+export class TerminalEditorInput extends EditorInput implements IEditorCloseHandler {
static readonly ID = 'workbench.editors.terminal';
+ override readonly closeHandler = this;
+
private _isDetached = false;
private _isShuttingDown = false;
private _isReverted = false;
private _copyLaunchConfig?: IShellLaunchConfig;
private _terminalEditorFocusContextKey: IContextKey<boolean>;
-
private _group: IEditorGroup | undefined;
+ protected readonly _onDidRequestAttach = this._register(new Emitter<ITerminalInstance>());
+ readonly onDidRequestAttach = this._onDidRequestAttach.event;
+
setGroup(group: IEditorGroup | undefined) {
this._group = group;
}
@@ -64,13 +65,6 @@ export class TerminalEditorInput extends EditorInput {
}
this._terminalInstance = instance;
this._setupInstanceListeners();
-
- // Refresh dirty state when the confirm on kill setting is changed
- this._configurationService.onDidChangeConfiguration(e => {
- if (e.affectsConfiguration(TerminalSettingId.ConfirmOnKill)) {
- this._onDidChangeDirty.fire();
- }
- });
}
override copy(): EditorInput {
@@ -95,7 +89,7 @@ export class TerminalEditorInput extends EditorInput {
return this._isDetached ? undefined : this._terminalInstance;
}
- override isDirty(): boolean {
+ showConfirm(): boolean {
if (this._isReverted) {
return false;
}
@@ -106,7 +100,7 @@ export class TerminalEditorInput extends EditorInput {
return false;
}
- override async confirm(terminals?: ReadonlyArray<IEditorIdentifier>): Promise<ConfirmResult> {
+ async confirm(terminals?: ReadonlyArray<IEditorIdentifier>): Promise<ConfirmResult> {
const { choice } = await this._dialogService.show(
Severity.Warning,
localize('confirmDirtyTerminal.message', "Do you want to terminate running processes?"),
@@ -148,12 +142,6 @@ export class TerminalEditorInput extends EditorInput {
this._terminalEditorFocusContextKey = TerminalContextKeys.editorFocus.bindTo(_contextKeyService);
- // Refresh dirty state when the confirm on kill setting is changed
- this._configurationService.onDidChangeConfiguration(e => {
- if (e.affectsConfiguration(TerminalSettingId.ConfirmOnKill)) {
- this._onDidChangeDirty.fire();
- }
- });
if (_terminalInstance) {
this._setupInstanceListeners();
}
@@ -167,7 +155,9 @@ export class TerminalEditorInput extends EditorInput {
this._register(toDisposable(() => {
if (!this._isDetached && !this._isShuttingDown) {
- instance.dispose();
+ // Will be ignored if triggered by onExit or onDisposed terminal events
+ // as disposed was already called
+ instance.dispose(TerminalExitReason.User);
}
}));
@@ -178,7 +168,6 @@ export class TerminalEditorInput extends EditorInput {
instance.onIconChanged(() => this._onDidChangeLabel.fire()),
instance.onDidFocus(() => this._terminalEditorFocusContextKey.set(true)),
instance.onDidBlur(() => this._terminalEditorFocusContextKey.reset()),
- instance.onDidChangeHasChildProcesses(() => this._onDidChangeDirty.fire()),
instance.statusList.onDidChangePrimaryStatus(() => this._onDidChangeLabel.fire())
];
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts
index aec68ee5d91..74b83274dca 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts
@@ -279,7 +279,8 @@ export class TerminalEditorService extends Disposable implements ITerminalEditor
const inputKey = resource.path;
if ('pid' in deserializedInput) {
- const instance = this._terminalInstanceService.createInstance({ attachPersistentProcess: deserializedInput }, TerminalLocation.Editor);
+ const newDeserializedInput = { ...deserializedInput, findRevivedId: true };
+ const instance = this._terminalInstanceService.createInstance({ attachPersistentProcess: newDeserializedInput }, TerminalLocation.Editor);
instance.target = TerminalLocation.Editor;
const input = this._instantiationService.createInstance(TerminalEditorInput, resource, instance);
this._registerInstance(inputKey, input, instance);
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts b/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts
index b098cc3ece1..89acbedfa80 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts
@@ -3,14 +3,15 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { Codicon } from 'vs/base/common/codicons';
import { hash } from 'vs/base/common/hash';
import { URI } from 'vs/base/common/uri';
+import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IExtensionTerminalProfile, ITerminalProfile } from 'vs/platform/terminal/common/terminal';
import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry';
import { ColorScheme } from 'vs/platform/theme/common/theme';
import { IColorTheme, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal';
+import { ITerminalProfileResolverService } from 'vs/workbench/contrib/terminal/common/terminal';
import { ansiColorMap } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry';
@@ -116,9 +117,9 @@ export function getUriClasses(terminal: ITerminalInstance | IExtensionTerminalPr
return iconClasses;
}
-export function getIconId(terminal: ITerminalInstance | IExtensionTerminalProfile | ITerminalProfile): string {
+export function getIconId(accessor: ServicesAccessor, terminal: ITerminalInstance | IExtensionTerminalProfile | ITerminalProfile): string {
if (!terminal.icon || (terminal.icon instanceof Object && !('id' in terminal.icon))) {
- return Codicon.terminal.id;
+ return accessor.get(ITerminalProfileResolverService).getDefaultIcon().id;
}
return typeof terminal.icon === 'string' ? terminal.icon : terminal.icon.id;
}
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts
index 402e8cf18e8..1f8a4970e50 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts
@@ -46,8 +46,8 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ITerminalCommand, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities';
import { TerminalCapabilityStoreMultiplexer } from 'vs/platform/terminal/common/capabilities/terminalCapabilityStore';
-import { IProcessDataEvent, IProcessPropertyMap, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, PosixShellType, ProcessPropertyType, TerminalIcon, TerminalLocation, TerminalSettingId, TerminalShellType, TitleEventSource, WindowsShellType } from 'vs/platform/terminal/common/terminal';
-import { escapeNonWindowsPath } from 'vs/platform/terminal/common/terminalEnvironment';
+import { IProcessDataEvent, IProcessPropertyMap, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, PosixShellType, ProcessPropertyType, ShellIntegrationStatus, TerminalExitReason, TerminalIcon, TerminalLocation, TerminalSettingId, TerminalShellType, TitleEventSource, WindowsShellType } from 'vs/platform/terminal/common/terminal';
+import { escapeNonWindowsPath, collapseTildePath } from 'vs/platform/terminal/common/terminalEnvironment';
import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry';
import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
@@ -86,6 +86,7 @@ import type { IMarker, ITerminalAddon, Terminal as XTermTerminal } from 'xterm';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IGenericMarkProperties } from 'vs/platform/terminal/common/terminalProcess';
import { ICommandService } from 'vs/platform/commands/common/commands';
+import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry';
const enum Constants {
/**
@@ -155,6 +156,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
private readonly _processManager: ITerminalProcessManager;
private readonly _resource: URI;
+ private _shutdownPersistentProcessId: number | undefined;
// Enables disposal of the xterm onKey
// event when the CwdDetection capability
@@ -171,6 +173,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
private _isVisible: boolean;
private _isDisposed: boolean;
private _exitCode: number | undefined;
+ private _exitReason: TerminalExitReason | undefined;
private _skipTerminalCommands: string[];
private _shellType: TerminalShellType;
private _title: string = '';
@@ -276,6 +279,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
get areLinksReady(): boolean { return this._areLinksReady; }
get initialDataEvents(): string[] | undefined { return this._initialDataEvents; }
get exitCode(): number | undefined { return this._exitCode; }
+ get exitReason(): TerminalExitReason | undefined { return this._exitReason; }
get hadFocusOnExit(): boolean { return this._hadFocusOnExit; }
get isTitleSetByProcess(): boolean { return !!this._messageTitleDisposable; }
get shellLaunchConfig(): IShellLaunchConfig { return this._shellLaunchConfig; }
@@ -353,6 +357,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
private readonly _terminalHasFixedWidth: IContextKey<boolean>,
private readonly _terminalShellTypeContextKey: IContextKey<string>,
private readonly _terminalAltBufferActiveContextKey: IContextKey<boolean>,
+ private readonly _terminalInRunCommandPicker: IContextKey<boolean>,
+ private readonly _terminalShellIntegrationEnabledContextKey: IContextKey<boolean>,
private readonly _configHelper: TerminalConfigHelper,
private _shellLaunchConfig: IShellLaunchConfig,
resource: URI | undefined,
@@ -547,7 +553,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
private _getIcon(): TerminalIcon | undefined {
if (!this._icon) {
- this._icon = this._processManager.processState >= ProcessState.Launching ? Codicon.terminal : undefined;
+ this._icon = this._processManager.processState >= ProcessState.Launching
+ ? getIconRegistry().getIcon(this._configurationService.getValue(TerminalSettingId.TabsDefaultIcon))
+ : undefined;
}
return this._icon;
}
@@ -664,8 +672,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
return TerminalInstance._lastKnownCanvasDimensions;
}
- get persistentProcessId(): number | undefined { return this._processManager.persistentProcessId; }
- get shouldPersist(): boolean { return this._processManager.shouldPersist && !this.shellLaunchConfig.isTransient; }
+ set shutdownPersistentProcessId(shutdownPersistentProcessId: number | undefined) {
+ this._shutdownPersistentProcessId = shutdownPersistentProcessId;
+ }
+ get persistentProcessId(): number | undefined { return this._processManager.persistentProcessId ?? this._shutdownPersistentProcessId; }
+ get shouldPersist(): boolean { return (this._processManager.shouldPersist || this._shutdownPersistentProcessId !== undefined) && !this.shellLaunchConfig.isTransient; }
/**
* Create xterm.js instance and attach data listeners.
@@ -691,7 +702,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
// Write initial text, deferring onLineFeed listener when applicable to avoid firing
// onLineData events containing initialText
if (this._shellLaunchConfig.initialText) {
- this.xterm.raw.writeln(this._shellLaunchConfig.initialText, () => {
+ this._writeInitialText(this.xterm, () => {
lineDataEventAddon.onLineData(e => this._onLineData.fire(e));
});
} else {
@@ -814,7 +825,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this._linkManager.openRecentLink(type);
}
- async runRecent(type: 'command' | 'cwd'): Promise<void> {
+ async runRecent(type: 'command' | 'cwd', filterMode?: 'fuzzy' | 'contiguous', value?: string): Promise<void> {
if (!this.xterm) {
return;
}
@@ -844,7 +855,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
if (label.length === 0 || commandMap.has(label)) {
continue;
}
- let description = `${entry.cwd}`;
+ let description = collapseTildePath(entry.cwd, this._userHome, this._processManager?.os === OperatingSystem.Windows ? '\\' : '/');
if (entry.exitCode) {
// Since you cannot get the last command's exit code on pwsh, just whether it failed
// or not, -1 is treated specially as simply failed
@@ -873,7 +884,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
description,
id: entry.timestamp.toString(),
command: entry,
- buttons: entry.hasOutput ? buttons : undefined
+ buttons: entry.hasOutput() ? buttons : undefined
});
commandMap.add(label);
}
@@ -961,44 +972,67 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}
const outputProvider = this._instantiationService.createInstance(TerminalOutputProvider);
const quickPick = this._quickInputService.createQuickPick();
- quickPick.items = items;
+ const originalItems = items;
+ quickPick.items = [...originalItems];
quickPick.sortByLabel = false;
quickPick.placeholder = placeholder;
- return new Promise<void>(r => {
- quickPick.onDidTriggerItemButton(async e => {
- if (e.button === removeFromCommandHistoryButton) {
- if (type === 'command') {
- this._instantiationService.invokeFunction(getCommandHistory)?.remove(e.item.label);
- } else {
- this._instantiationService.invokeFunction(getDirectoryHistory)?.remove(e.item.label);
- }
- } else {
- const selectedCommand = (e.item as Item).command;
- const output = selectedCommand?.getOutput();
- if (output && selectedCommand?.command) {
- const textContent = await outputProvider.provideTextContent(URI.from(
- {
- scheme: TerminalOutputProvider.scheme,
- path: `${selectedCommand.command}... ${fromNow(selectedCommand.timestamp, true)}`,
- fragment: output,
- query: `terminal-output-${selectedCommand.timestamp}-${this.instanceId}`
- }));
- if (textContent) {
- await this._editorService.openEditor({
- resource: textContent.uri
- });
- }
- }
- }
+ quickPick.customButton = true;
+ quickPick.matchOnLabelMode = filterMode || 'contiguous';
+ if (filterMode === 'fuzzy') {
+ quickPick.customLabel = nls.localize('terminal.contiguousSearch', 'Use Contiguous Search');
+ quickPick.onDidCustom(() => {
quickPick.hide();
+ this.runRecent(type, 'contiguous', quickPick.value);
});
- quickPick.onDidAccept(() => {
- const result = quickPick.activeItems[0];
- this.sendText(type === 'cwd' ? `cd ${result.label}` : result.label, !quickPick.keyMods.alt);
+ } else {
+ quickPick.customLabel = nls.localize('terminal.fuzzySearch', 'Use Fuzzy Search');
+ quickPick.onDidCustom(() => {
quickPick.hide();
+ this.runRecent(type, 'fuzzy', quickPick.value);
});
+ }
+ quickPick.onDidTriggerItemButton(async e => {
+ if (e.button === removeFromCommandHistoryButton) {
+ if (type === 'command') {
+ this._instantiationService.invokeFunction(getCommandHistory)?.remove(e.item.label);
+ } else {
+ this._instantiationService.invokeFunction(getDirectoryHistory)?.remove(e.item.label);
+ }
+ } else {
+ const selectedCommand = (e.item as Item).command;
+ const output = selectedCommand?.getOutput();
+ if (output && selectedCommand?.command) {
+ const textContent = await outputProvider.provideTextContent(URI.from(
+ {
+ scheme: TerminalOutputProvider.scheme,
+ path: `${selectedCommand.command}... ${fromNow(selectedCommand.timestamp, true)}`,
+ fragment: output,
+ query: `terminal-output-${selectedCommand.timestamp}-${this.instanceId}`
+ }));
+ if (textContent) {
+ await this._editorService.openEditor({
+ resource: textContent.uri
+ });
+ }
+ }
+ }
+ quickPick.hide();
+ });
+ quickPick.onDidAccept(() => {
+ const result = quickPick.activeItems[0];
+ this.sendText(type === 'cwd' ? `cd ${result.label}` : result.label, !quickPick.keyMods.alt);
+ quickPick.hide();
+ });
+ if (value) {
+ quickPick.value = value;
+ }
+ return new Promise<void>(r => {
quickPick.show();
- quickPick.onDidHide(() => r());
+ this._terminalInRunCommandPicker.set(true);
+ quickPick.onDidHide(() => {
+ this._terminalInRunCommandPicker.set(false);
+ r();
+ });
});
}
@@ -1052,6 +1086,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
const screenElement = xterm.attachToElement(xtermElement);
xterm.onDidChangeFindResults((results) => this._onDidChangeFindResults.fire(results));
+ xterm.shellIntegration.onDidChangeStatus(() => {
+ if (this.hasFocus) {
+ this._setShellIntegrationContextKey();
+ } else {
+ this._terminalShellIntegrationEnabledContextKey.reset();
+ }
+ });
if (!xterm.raw.element || !xterm.raw.textarea) {
throw new Error('xterm elements not set after open');
@@ -1185,16 +1226,25 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
private _setFocus(focused?: boolean): void {
if (focused) {
this._terminalFocusContextKey.set(true);
+ this._setShellIntegrationContextKey();
this._onDidFocus.fire(this);
} else {
- this._terminalFocusContextKey.reset();
+ this.resetFocusContextKey();
this._onDidBlur.fire(this);
this._refreshSelectionContextKey();
}
}
+ private _setShellIntegrationContextKey(): void {
+ console.log('set', this.xterm?.shellIntegration.status === ShellIntegrationStatus.VSCode);
+ if (this.xterm) {
+ this._terminalShellIntegrationEnabledContextKey.set(this.xterm.shellIntegration.status === ShellIntegrationStatus.VSCode);
+ }
+ }
+
resetFocusContextKey(): void {
this._terminalFocusContextKey.reset();
+ this._terminalShellIntegrationEnabledContextKey.reset();
}
private _initDragAndDrop(container: HTMLElement) {
@@ -1235,6 +1285,21 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}
}
+ async copyLastCommandOutput(): Promise<void> {
+ const commands = this.capabilities.get(TerminalCapability.CommandDetection)?.commands;
+ if (!commands || commands.length === 0) {
+ return;
+ }
+ const command = commands[commands.length - 1];
+ if (!command?.hasOutput()) {
+ return;
+ }
+ const output = command.getOutput();
+ if (output) {
+ await this._clipboardService.writeText(output);
+ }
+ }
+
get selection(): string | undefined {
return this.xterm && this.hasSelection() ? this.xterm.raw.getSelection() : undefined;
}
@@ -1255,6 +1320,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}
const terminalFocused = !isFocused && (document.activeElement === this.xterm.raw.textarea || document.activeElement === this.xterm.raw.element);
this._terminalFocusContextKey.set(terminalFocused);
+ if (terminalFocused) {
+ this._setShellIntegrationContextKey();
+ } else {
+ this._terminalShellIntegrationEnabledContextKey.reset();
+ }
}
private _refreshAltBufferContextKey() {
@@ -1310,8 +1380,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
return confirmation.confirmed;
}
-
- override dispose(immediate?: boolean): void {
+ override dispose(reason?: TerminalExitReason): void {
this._logService.trace(`terminalInstance#dispose (instanceId: ${this.instanceId})`);
dispose(this._linkManager);
this._linkManager = undefined;
@@ -1335,7 +1404,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
// as 'blur' event in xterm.raw.textarea is not triggered on xterm.dispose()
// See https://github.com/microsoft/vscode/issues/138358
if (isFirefox) {
- this._terminalFocusContextKey.reset();
+ this.resetFocusContextKey();
this._terminalHasTextContextKey.reset();
this._onDidBlur.fire(this);
}
@@ -1345,7 +1414,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this._pressAnyKeyToCloseListener = undefined;
}
- this._processManager.dispose(immediate);
+ if (this._exitReason === undefined) {
+ this._exitReason = reason ?? TerminalExitReason.Unknown;
+ }
+
+ this._processManager.dispose();
// Process manager dispose/shutdown doesn't fire process exit, trigger with undefined if it
// hasn't happened yet
this._onProcessExit(undefined);
@@ -1357,11 +1430,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
super.dispose();
}
- async detachFromProcess(): Promise<void> {
+ async detachProcessAndDispose(reason: TerminalExitReason): Promise<void> {
// Detach the process and dispose the instance, without the instance dispose the terminal
// won't go away
await this._processManager.detachFromProcess();
- this.dispose();
+ this.dispose(reason);
}
focus(force?: boolean): void {
@@ -1416,7 +1489,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
async sendText(text: string, addNewLine: boolean): Promise<void> {
// Normalize line endings to 'enter' press.
text = text.replace(/\r?\n/g, '\r');
- if (addNewLine && text.substr(text.length - 1) !== '\r') {
+ if (addNewLine && text[text.length - 1] !== '\r') {
text += '\r';
}
@@ -1657,7 +1730,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
const parsedExitResult = parseExitResult(exitCodeOrError, this.shellLaunchConfig, this._processManager.processState, this._initialCwd);
- if (this._usedShellIntegrationInjection && (this._processManager.processState === ProcessState.KilledDuringLaunch || this._processManager.processState === ProcessState.KilledByProcess)) {
+ if (this._usedShellIntegrationInjection && this._processManager.processState === ProcessState.KilledDuringLaunch && parsedExitResult?.code !== 0) {
this._relaunchWithShellIntegrationDisabled(parsedExitResult?.message);
this._onExit.fire(exitCodeOrError);
return;
@@ -1697,7 +1770,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}
});
} else {
- this.dispose();
+ this.dispose(TerminalExitReason.Process);
if (exitMessage) {
const failedDuringLaunch = this._processManager.processState === ProcessState.KilledDuringLaunch;
if (failedDuringLaunch || this._configHelper.config.showExitAlert) {
@@ -1773,36 +1846,56 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
if (this._pressAnyKeyToCloseListener) {
this._pressAnyKeyToCloseListener.dispose();
this._pressAnyKeyToCloseListener = undefined;
- this.dispose();
+ this.dispose(TerminalExitReason.Process);
event.preventDefault();
}
});
}
}
+ private _writeInitialText(xterm: XtermTerminal, callback?: () => void): void {
+ if (!this._shellLaunchConfig.initialText) {
+ callback?.();
+ return;
+ }
+ const text = typeof this._shellLaunchConfig.initialText === 'string'
+ ? this._shellLaunchConfig.initialText
+ : this._shellLaunchConfig.initialText?.text;
+ if (typeof this._shellLaunchConfig.initialText === 'string') {
+ xterm.raw.writeln(text, callback);
+ } else {
+ if (this._shellLaunchConfig.initialText.trailingNewLine) {
+ xterm.raw.writeln(text, callback);
+ } else {
+ xterm.raw.write(text, callback);
+ }
+ }
+ }
+
async reuseTerminal(shell: IShellLaunchConfig, reset: boolean = false): Promise<void> {
// Unsubscribe any key listener we may have.
this._pressAnyKeyToCloseListener?.dispose();
this._pressAnyKeyToCloseListener = undefined;
- if (this.xterm) {
+ const xterm = this.xterm;
+ if (xterm) {
if (!reset) {
// Ensure new processes' output starts at start of new line
- await new Promise<void>(r => this.xterm!.raw.write('\n\x1b[G', r));
+ await new Promise<void>(r => xterm.raw.write('\n\x1b[G', r));
}
// Print initialText if specified
if (shell.initialText) {
- await new Promise<void>(r => this.xterm!.raw.writeln(shell.initialText!, r));
+ await new Promise<void>(r => this._writeInitialText(xterm, r));
}
// Clean up waitOnExit state
if (this._isExiting && this._shellLaunchConfig.waitOnExit) {
- this.xterm.raw.options.disableStdin = false;
+ xterm.raw.options.disableStdin = false;
this._isExiting = false;
}
if (reset) {
- this.xterm.clearDecorations();
+ xterm.clearDecorations();
}
}
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts
index c9da9557b27..a9feaab7aa1 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts
@@ -23,6 +23,8 @@ export class TerminalInstanceService extends Disposable implements ITerminalInst
private _terminalHasFixedWidth: IContextKey<boolean>;
private _terminalShellTypeContextKey: IContextKey<string>;
private _terminalAltBufferActiveContextKey: IContextKey<boolean>;
+ private _terminalInRunCommandPicker: IContextKey<boolean>;
+ private _terminalShellIntegrationEnabled: IContextKey<boolean>;
private _configHelper: TerminalConfigHelper;
private readonly _onDidCreateInstance = new Emitter<ITerminalInstance>();
@@ -37,6 +39,8 @@ export class TerminalInstanceService extends Disposable implements ITerminalInst
this._terminalHasFixedWidth = TerminalContextKeys.terminalHasFixedWidth.bindTo(this._contextKeyService);
this._terminalShellTypeContextKey = TerminalContextKeys.shellType.bindTo(this._contextKeyService);
this._terminalAltBufferActiveContextKey = TerminalContextKeys.altBufferActive.bindTo(this._contextKeyService);
+ this._terminalInRunCommandPicker = TerminalContextKeys.inTerminalRunCommandPicker.bindTo(this._contextKeyService);
+ this._terminalShellIntegrationEnabled = TerminalContextKeys.terminalShellIntegrationEnabled.bindTo(this._contextKeyService);
this._configHelper = _instantiationService.createInstance(TerminalConfigHelper);
}
@@ -49,6 +53,8 @@ export class TerminalInstanceService extends Disposable implements ITerminalInst
this._terminalHasFixedWidth,
this._terminalShellTypeContextKey,
this._terminalAltBufferActiveContextKey,
+ this._terminalInRunCommandPicker,
+ this._terminalShellIntegrationEnabled,
this._configHelper,
shellLaunchConfig,
resource
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts
index 5183542cb14..af2d55a2242 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts
@@ -417,6 +417,7 @@ export function setupTerminalMenus(): void {
order: 2,
when: ContextKeyExpr.and(
ContextKeyExpr.equals('view', TERMINAL_VIEW_ID),
+ ContextKeyExpr.notEquals(`config.${TerminalSettingId.TabsHideCondition}`, 'never'),
ContextKeyExpr.or(
ContextKeyExpr.not(`config.${TerminalSettingId.TabsEnabled}`),
ContextKeyExpr.and(
@@ -451,6 +452,7 @@ export function setupTerminalMenus(): void {
order: 3,
when: ContextKeyExpr.and(
ContextKeyExpr.equals('view', TERMINAL_VIEW_ID),
+ ContextKeyExpr.notEquals(`config.${TerminalSettingId.TabsHideCondition}`, 'never'),
ContextKeyExpr.or(
ContextKeyExpr.not(`config.${TerminalSettingId.TabsEnabled}`),
ContextKeyExpr.and(
@@ -750,11 +752,11 @@ export function getTerminalActionBarArgs(location: ITerminalLocationOptions, pro
shouldForwardArgs: true
};
if (isDefault) {
- dropdownActions.unshift(new MenuItemAction({ id: TerminalCommandId.NewWithProfile, title: localize('defaultTerminalProfile', "{0} (Default)", p.profileName), category: TerminalTabContextMenuGroup.Profile }, undefined, options, contextKeyService, commandService));
- submenuActions.unshift(new MenuItemAction({ id: TerminalCommandId.Split, title: localize('defaultTerminalProfile', "{0} (Default)", p.profileName), category: TerminalTabContextMenuGroup.Profile }, undefined, splitOptions, contextKeyService, commandService));
+ dropdownActions.unshift(new MenuItemAction({ id: TerminalCommandId.NewWithProfile, title: localize('defaultTerminalProfile', "{0} (Default)", p.profileName), category: TerminalTabContextMenuGroup.Profile }, undefined, options, undefined, contextKeyService, commandService));
+ submenuActions.unshift(new MenuItemAction({ id: TerminalCommandId.Split, title: localize('defaultTerminalProfile', "{0} (Default)", p.profileName), category: TerminalTabContextMenuGroup.Profile }, undefined, splitOptions, undefined, contextKeyService, commandService));
} else {
- dropdownActions.push(new MenuItemAction({ id: TerminalCommandId.NewWithProfile, title: p.profileName.replace(/[\n\r\t]/g, ''), category: TerminalTabContextMenuGroup.Profile }, undefined, options, contextKeyService, commandService));
- submenuActions.push(new MenuItemAction({ id: TerminalCommandId.Split, title: p.profileName.replace(/[\n\r\t]/g, ''), category: TerminalTabContextMenuGroup.Profile }, undefined, splitOptions, contextKeyService, commandService));
+ dropdownActions.push(new MenuItemAction({ id: TerminalCommandId.NewWithProfile, title: p.profileName.replace(/[\n\r\t]/g, ''), category: TerminalTabContextMenuGroup.Profile }, undefined, options, undefined, contextKeyService, commandService));
+ submenuActions.push(new MenuItemAction({ id: TerminalCommandId.Split, title: p.profileName.replace(/[\n\r\t]/g, ''), category: TerminalTabContextMenuGroup.Profile }, undefined, splitOptions, undefined, contextKeyService, commandService));
}
}
@@ -821,7 +823,8 @@ export function getTerminalActionBarArgs(location: ITerminalLocationOptions, pro
{
shouldForwardArgs: true,
arg: { location } as ICreateTerminalOptions,
- });
+ },
+ undefined);
const dropdownAction = new Action('refresh profiles', 'Launch Profile...', 'codicon-chevron-down', true);
return { primaryAction, dropdownAction, dropdownMenuActions: dropdownActions, className: `terminal-tab-actions-${terminalService.resolveLocation(location)}` };
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts
index 3e2725c70ff..ba6540e654c 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts
@@ -292,7 +292,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
}
} else {
if (shellLaunchConfig.attachPersistentProcess) {
- const result = await backend.attachToProcess(shellLaunchConfig.attachPersistentProcess.id);
+ const result = shellLaunchConfig.attachPersistentProcess.findRevivedId ? await backend.attachToRevivedProcess(shellLaunchConfig.attachPersistentProcess.id) : await backend.attachToProcess(shellLaunchConfig.attachPersistentProcess.id);
if (result) {
newProcess = result;
} else {
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts
index e715b730b66..e67654f2c70 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts
@@ -11,7 +11,7 @@ import { getUriClasses, getColorClass, getColorStyleElement } from 'vs/workbench
import { configureTerminalProfileIcon } from 'vs/workbench/contrib/terminal/browser/terminalIcons';
import * as nls from 'vs/nls';
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
-import { ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal';
+import { ITerminalProfileResolverService, ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal';
import { IQuickPickTerminalObject, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal';
import { IPickerQuickAccessItem } from 'vs/platform/quickinput/browser/pickerQuickAccess';
import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry';
@@ -22,6 +22,7 @@ type DefaultProfileName = string;
export class TerminalProfileQuickpick {
constructor(
@ITerminalProfileService private readonly _terminalProfileService: ITerminalProfileService,
+ @ITerminalProfileResolverService private readonly _terminalProfileResolverService: ITerminalProfileResolverService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@IQuickInputService private readonly _quickInputService: IQuickInputService,
@IThemeService private readonly _themeService: IThemeService
@@ -155,7 +156,7 @@ export class TerminalProfileQuickpick {
}
}
if (!icon || !getIconRegistry().getIcon(icon.id)) {
- icon = Codicon.terminal;
+ icon = this._terminalProfileResolverService.getDefaultIcon();
}
const uriClasses = getUriClasses(contributed, this._themeService.getColorTheme().type, true);
const colorClass = getColorClass(contributed);
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts
index 0849f6676b2..f920f87442f 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts
@@ -16,6 +16,7 @@ import { IShellLaunchConfig, ITerminalProfile, ITerminalProfileObject, TerminalI
import { IShellLaunchConfigResolveOptions, ITerminalProfileResolverService, ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal';
import * as path from 'vs/base/common/path';
import { Codicon } from 'vs/base/common/codicons';
+import { getIconRegistry, IIconRegistry } from 'vs/platform/theme/common/iconRegistry';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { debounce } from 'vs/base/common/decorators';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
@@ -51,6 +52,8 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro
private _primaryBackendOs: OperatingSystem | undefined;
+ private readonly _iconRegistry: IIconRegistry = getIconRegistry();
+
private _defaultProfileName: string | undefined;
get defaultProfileName(): string | undefined { return this._defaultProfileName; }
@@ -94,11 +97,11 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro
resolveIcon(shellLaunchConfig: IShellLaunchConfig, os: OperatingSystem): void {
if (shellLaunchConfig.icon) {
- shellLaunchConfig.icon = this._getCustomIcon(shellLaunchConfig.icon) || Codicon.terminal;
+ shellLaunchConfig.icon = this._getCustomIcon(shellLaunchConfig.icon) || this.getDefaultIcon();
return;
}
if (shellLaunchConfig.customPtyImplementation) {
- shellLaunchConfig.icon = Codicon.terminal;
+ shellLaunchConfig.icon = this.getDefaultIcon();
return;
}
if (shellLaunchConfig.executable) {
@@ -108,6 +111,13 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro
if (defaultProfile) {
shellLaunchConfig.icon = defaultProfile.icon;
}
+ if (!shellLaunchConfig.icon) {
+ shellLaunchConfig.icon = this.getDefaultIcon();
+ }
+ }
+
+ getDefaultIcon(): TerminalIcon & ThemeIcon {
+ return this._iconRegistry.getIcon(this._configurationService.getValue(TerminalSettingId.TabsDefaultIcon)) || Codicon.terminal;
}
async resolveShellLaunchConfig(shellLaunchConfig: IShellLaunchConfig, options: IShellLaunchConfigResolveOptions): Promise<void> {
@@ -135,7 +145,9 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro
// Verify the icon is valid, and fallback correctly to the generic terminal id if there is
// an issue
- shellLaunchConfig.icon = this._getCustomIcon(shellLaunchConfig.icon) || this._getCustomIcon(resolvedProfile.icon) || Codicon.terminal;
+ shellLaunchConfig.icon = this._getCustomIcon(shellLaunchConfig.icon)
+ || this._getCustomIcon(resolvedProfile.icon)
+ || this.getDefaultIcon();
// Override the name if specified
if (resolvedProfile.overrideName) {
@@ -143,7 +155,9 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro
}
// Apply the color
- shellLaunchConfig.color = shellLaunchConfig.color || resolvedProfile.color;
+ shellLaunchConfig.color = shellLaunchConfig.color
+ || resolvedProfile.color
+ || this._configurationService.getValue(TerminalSettingId.TabsDefaultColor);
// Resolve useShellEnvironment based on the setting if it's not set
if (shellLaunchConfig.useShellEnvironment === undefined) {
@@ -232,6 +246,7 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro
if (defaultProfileName && typeof defaultProfileName === 'string') {
return this._terminalProfileService.availableProfiles.find(e => e.profileName === defaultProfileName);
}
+
return undefined;
}
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts
index 60c49b4b34b..7bdc8975d82 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts
@@ -67,7 +67,7 @@ export class TerminalProfileService implements ITerminalProfileService {
// Wait up to 5 seconds for profiles to be ready so it's assured that we know the actual
// default terminal before launching the first terminal. This isn't expected to ever take
// this long.
- this._profilesReadyBarrier = new AutoOpenBarrier(5000);
+ this._profilesReadyBarrier = new AutoOpenBarrier(20000);
this.refreshAvailableProfiles();
this._setupConfigListener();
}
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts b/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts
index af7953a3268..ff5fbd96df8 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts
@@ -7,7 +7,7 @@ import { localize } from 'vs/nls';
import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
import { IPickerQuickAccessItem, PickerQuickAccessProvider, TriggerAction } from 'vs/platform/quickinput/browser/pickerQuickAccess';
import { matchesFuzzy } from 'vs/base/common/filters';
-import { ITerminalEditorService, ITerminalGroupService, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal';
+import { ITerminalEditorService, ITerminalGroupService, ITerminalInstance, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal';
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
@@ -16,6 +16,7 @@ import { getColorClass, getIconId, getUriClasses } from 'vs/workbench/contrib/te
import { terminalStrings } from 'vs/workbench/contrib/terminal/common/terminalStrings';
import { TerminalLocation } from 'vs/platform/terminal/common/terminal';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
+import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
let terminalPicks: Array<IPickerQuickAccessItem | IQuickPickSeparator> = [];
export class TerminalQuickAccessProvider extends PickerQuickAccessProvider<IPickerQuickAccessItem> {
@@ -24,10 +25,12 @@ export class TerminalQuickAccessProvider extends PickerQuickAccessProvider<IPick
constructor(
@IEditorService private readonly _editorService: IEditorService,
+ @ITerminalEditorService private readonly _terminalService: ITerminalService,
@ITerminalEditorService private readonly _terminalEditorService: ITerminalEditorService,
@ITerminalGroupService private readonly _terminalGroupService: ITerminalGroupService,
@ICommandService private readonly _commandService: ICommandService,
- @IThemeService private readonly _themeService: IThemeService
+ @IThemeService private readonly _themeService: IThemeService,
+ @IInstantiationService private readonly _instantiationService: IInstantiationService
) {
super(TerminalQuickAccessProvider.PREFIX, { canAcceptInBackground: true });
}
@@ -80,7 +83,7 @@ export class TerminalQuickAccessProvider extends PickerQuickAccessProvider<IPick
}
private _createPick(terminal: ITerminalInstance, terminalIndex: number, filter: string, groupInfo?: { groupIndex: number; groupSize: number }): IPickerQuickAccessItem | undefined {
- const iconId = getIconId(terminal);
+ const iconId = this._instantiationService.invokeFunction(getIconId, terminal);
const index = groupInfo
? (groupInfo.groupSize > 1
? `${groupInfo.groupIndex + 1}.${terminalIndex + 1}`
@@ -119,7 +122,7 @@ export class TerminalQuickAccessProvider extends PickerQuickAccessProvider<IPick
this._commandService.executeCommand(TerminalCommandId.Rename, terminal);
return TriggerAction.NO_ACTION;
case 1:
- terminal.dispose(true);
+ this._terminalService.safeDisposeTerminal(terminal);
return TriggerAction.REMOVE_ITEM;
}
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts
index 81d40e49f91..7ed18631566 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts
@@ -20,7 +20,8 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { INotificationService } from 'vs/platform/notification/common/notification';
-import { ICreateContributedTerminalProfileOptions, IShellLaunchConfig, ITerminalLaunchError, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalLocation, TerminalLocationString, TitleEventSource } from 'vs/platform/terminal/common/terminal';
+import { ICreateContributedTerminalProfileOptions, IShellLaunchConfig, ITerminalLaunchError, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalExitReason, TerminalLocation, TerminalLocationString, TitleEventSource } from 'vs/platform/terminal/common/terminal';
+import { formatMessageForTerminal } from 'vs/platform/terminal/common/terminalStrings';
import { iconForeground } from 'vs/platform/theme/common/colorRegistry';
import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry';
import { ColorScheme } from 'vs/platform/theme/common/theme';
@@ -38,7 +39,6 @@ import { getInstanceFromResource, getTerminalUri, parseTerminalUri } from 'vs/wo
import { TerminalViewPane } from 'vs/workbench/contrib/terminal/browser/terminalView';
import { IRemoteTerminalAttachTarget, IStartExtensionTerminalRequest, ITerminalBackend, ITerminalConfigHelper, ITerminalProcessExtHostProxy, ITerminalProfileService, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal';
import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey';
-import { formatMessageForTerminal } from 'vs/platform/terminal/common/terminalStrings';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
@@ -282,7 +282,7 @@ export class TerminalService implements ITerminalService {
} else {
this._terminalGroupService.getGroupForInstance(instanceToDetach)?.removeInstance(instanceToDetach);
}
- await instanceToDetach.detachFromProcess();
+ await instanceToDetach.detachProcessAndDispose(TerminalExitReason.User);
await this._primaryBackend?.acceptDetachInstanceReply(e.requestId, persistentProcessId);
} else {
// will get rejected without a persistentProcessId to attach to
@@ -371,7 +371,7 @@ export class TerminalService implements ITerminalService {
}
return new Promise<void>(r => {
instance.onExit(() => r());
- instance.dispose();
+ instance.dispose(TerminalExitReason.User);
});
}
@@ -615,14 +615,19 @@ export class TerminalService implements ITerminalService {
const shouldPersistTerminals = this._configHelper.config.enablePersistentSessions && e.reason === ShutdownReason.RELOAD;
if (shouldPersistTerminals) {
for (const instance of this.instances) {
- instance.detachFromProcess();
+ instance.detachProcessAndDispose(TerminalExitReason.Shutdown);
}
return;
}
+
// Force dispose of all terminal instances
+ const shouldPersistTerminalsForEvent = this._shouldReviveProcesses(e.reason);
for (const instance of this.instances) {
- instance.dispose();
+ if (shouldPersistTerminalsForEvent) {
+ instance.shutdownPersistentProcessId = instance.persistentProcessId;
+ }
+ instance.dispose(TerminalExitReason.Shutdown);
}
// Clear terminal layout info only when not persisting
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts
index d9fc17533ed..14890b1b9bf 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts
@@ -309,7 +309,7 @@ class TerminalTabsRenderer implements IListRenderer<ITerminalInstance, ITerminal
}
const shellIntegrationString = getShellIntegrationTooltip(instance, true, this._configurationService);
- const iconId = getIconId(instance);
+ const iconId = this._instantiationService.invokeFunction(getIconId, instance);
const hasActionbar = !this.shouldHideActionBar();
let label: string = '';
if (!hasText) {
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts
index f7fc381a88f..9dc9017d20f 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts
@@ -45,6 +45,7 @@ import { withNullAsUndefined } from 'vs/base/common/types';
import { getTerminalActionBarArgs } from 'vs/workbench/contrib/terminal/browser/terminalMenus';
import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey';
import { getShellIntegrationTooltip } from 'vs/workbench/contrib/terminal/browser/terminalTooltip';
+import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
export class TerminalViewPane extends ViewPane {
private _actions: IAction[] | undefined;
@@ -372,14 +373,15 @@ class SingleTerminalTabActionViewItem extends MenuEntryActionViewItem {
@IThemeService themeService: IThemeService,
@ITerminalService private readonly _terminalService: ITerminalService,
@ITerminalGroupService private readonly _terminalGroupService: ITerminalGroupService,
- @IContextMenuService private readonly _contextMenuService: IContextMenuService,
+ @IContextMenuService contextMenuService: IContextMenuService,
@ICommandService private readonly _commandService: ICommandService,
- @IConfigurationService configurationService: IConfigurationService
+ @IConfigurationService configurationService: IConfigurationService,
+ @IInstantiationService private readonly _instantiationService: IInstantiationService,
) {
super(new MenuItemAction(
{
id: action.id,
- title: getSingleTabLabel(_terminalGroupService.activeInstance, _terminalService.configHelper.config.tabs.separator),
+ title: _instantiationService.invokeFunction(getSingleTabLabel, _terminalGroupService.activeInstance, _terminalService.configHelper.config.tabs.separator),
tooltip: getSingleTabTooltip(_terminalGroupService.activeInstance, _terminalService.configHelper.config.tabs.separator, configurationService)
},
{
@@ -388,11 +390,12 @@ class SingleTerminalTabActionViewItem extends MenuEntryActionViewItem {
icon: Codicon.splitHorizontal
},
undefined,
+ undefined,
contextKeyService,
_commandService
), {
draggable: true
- }, keybindingService, notificationService, contextKeyService, themeService);
+ }, keybindingService, notificationService, contextKeyService, themeService, contextMenuService);
// Register listeners to update the tab
this._register(this._terminalService.onDidChangeInstancePrimaryStatus(e => this.updateLabel(e)));
@@ -473,7 +476,7 @@ class SingleTerminalTabActionViewItem extends MenuEntryActionViewItem {
}
}
label.style.color = colorStyle;
- dom.reset(label, ...renderLabelWithIcons(getSingleTabLabel(instance, this._terminalService.configHelper.config.tabs.separator, ThemeIcon.isThemeIcon(this._commandAction.item.icon) ? this._commandAction.item.icon : undefined)));
+ dom.reset(label, ...renderLabelWithIcons(this._instantiationService.invokeFunction(getSingleTabLabel, instance, this._terminalService.configHelper.config.tabs.separator, ThemeIcon.isThemeIcon(this._commandAction.item.icon) ? this._commandAction.item.icon : undefined)));
if (this._altCommand) {
label.classList.remove(this._altCommand);
@@ -515,13 +518,13 @@ class SingleTerminalTabActionViewItem extends MenuEntryActionViewItem {
}
}
-function getSingleTabLabel(instance: ITerminalInstance | undefined, separator: string, icon?: ThemeIcon) {
+function getSingleTabLabel(accessor: ServicesAccessor, instance: ITerminalInstance | undefined, separator: string, icon?: ThemeIcon) {
// Don't even show the icon if there is no title as the icon would shift around when the title
// is added
if (!instance || !instance.title) {
return '';
}
- const iconClass = ThemeIcon.isThemeIcon(instance.icon) ? instance.icon?.id : Codicon.terminal.id;
+ const iconClass = ThemeIcon.isThemeIcon(instance.icon) ? instance.icon.id : accessor.get(ITerminalProfileResolverService).getDefaultIcon();
const label = `$(${icon?.id || iconClass}) ${getSingleTabTitle(instance, separator)}`;
const primaryStatus = instance.statusList.primary;
diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/commandNavigationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/commandNavigationAddon.ts
index 08174e3b53c..83de46d909f 100644
--- a/src/vs/workbench/contrib/terminal/browser/xterm/commandNavigationAddon.ts
+++ b/src/vs/workbench/contrib/terminal/browser/xterm/commandNavigationAddon.ts
@@ -34,9 +34,9 @@ export class CommandNavigationAddon extends Disposable implements ICommandTracke
activate(terminal: Terminal): void {
this._terminal = terminal;
- this._terminal.onData(() => {
+ this._register(this._terminal.onData(() => {
this._currentMarker = Boundary.Bottom;
- });
+ }));
}
constructor(
diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts
index 0d033529f59..45b820a935c 100644
--- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts
+++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts
@@ -12,7 +12,7 @@ import { CommandInvalidationReason, ITerminalCapabilityStore, TerminalCapability
import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IHoverService } from 'vs/workbench/services/hover/browser/hover';
-import { IAction } from 'vs/base/common/actions';
+import { IAction, Separator } from 'vs/base/common/actions';
import { Emitter } from 'vs/base/common/event';
import { MarkdownString } from 'vs/base/common/htmlContent';
import { localize } from 'vs/nls';
@@ -353,24 +353,39 @@ export class DecorationAddon extends Disposable implements ITerminalAddon {
private async _getCommandActions(command: ITerminalCommand): Promise<IAction[]> {
const actions: IAction[] = [];
- if (command.hasOutput) {
+ if (command.command !== '') {
+ const labelRun = localize("terminal.rerunCommand", 'Rerun Command');
actions.push({
- class: 'copy-output', tooltip: 'Copy Output', dispose: () => { }, id: 'terminal.copyOutput', label: localize("terminal.copyOutput", 'Copy Output'), enabled: true,
- run: () => this._clipboardService.writeText(command.getOutput()!)
+ class: undefined, tooltip: labelRun, dispose: () => { }, id: 'terminal.rerunCommand', label: labelRun, enabled: true,
+ run: () => this._onDidRequestRunCommand.fire({ command })
});
+ const labelCopy = localize("terminal.copyCommand", 'Copy Command');
actions.push({
- class: 'copy-output', tooltip: 'Copy Output as HTML', dispose: () => { }, id: 'terminal.copyOutputAsHtml', label: localize("terminal.copyOutputAsHtml", 'Copy Output as HTML'), enabled: true,
- run: () => this._onDidRequestRunCommand.fire({ command, copyAsHtml: true })
+ class: undefined, tooltip: labelCopy, dispose: () => { }, id: 'terminal.copyCommand', label: labelCopy, enabled: true,
+ run: () => this._clipboardService.writeText(command.command)
});
}
- if (command.command !== '') {
+ if (command.hasOutput()) {
+ if (actions.length > 0) {
+ actions.push(new Separator());
+ }
+ const labelText = localize("terminal.copyOutput", 'Copy Output');
actions.push({
- class: 'rerun-command', tooltip: 'Rerun Command', dispose: () => { }, id: 'terminal.rerunCommand', label: localize("terminal.rerunCommand", 'Rerun Command'), enabled: true,
- run: () => this._onDidRequestRunCommand.fire({ command })
+ class: undefined, tooltip: labelText, dispose: () => { }, id: 'terminal.copyOutput', label: labelText, enabled: true,
+ run: () => this._clipboardService.writeText(command.getOutput()!)
+ });
+ const labelHtml = localize("terminal.copyOutputAsHtml", 'Copy Output as HTML');
+ actions.push({
+ class: undefined, tooltip: labelHtml, dispose: () => { }, id: 'terminal.copyOutputAsHtml', label: labelHtml, enabled: true,
+ run: () => this._onDidRequestRunCommand.fire({ command, copyAsHtml: true })
});
}
+ if (actions.length > 0) {
+ actions.push(new Separator());
+ }
+ const label = localize("terminal.learnShellIntegration", 'Learn About Shell Integration');
actions.push({
- class: 'how-does-this-work', tooltip: 'How does this work?', dispose: () => { }, id: 'terminal.howDoesThisWork', label: localize("terminal.howDoesThisWork", 'How does this work?'), enabled: true,
+ class: undefined, tooltip: label, dispose: () => { }, id: 'terminal.learnShellIntegration', label, enabled: true,
run: () => this._openerService.open('https://code.visualstudio.com/docs/editor/integrated-terminal#_shell-integration')
});
return actions;
diff --git a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts
index e8c7da68932..1b62d1e9cb3 100644
--- a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts
+++ b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts
@@ -299,6 +299,10 @@ export class RemoteTerminalChannelClient implements IPtyHostController {
return this._channel.call('$reviveTerminalProcesses', [state, dateTimeFormatLocate]);
}
+ getRevivedPtyNewId(id: number): Promise<number | undefined> {
+ return this._channel.call('$getRevivedPtyNewId', [id]);
+ }
+
serializeTerminalState(ids: number[]): Promise<string> {
return this._channel.call('$serializeTerminalState', [ids]);
}
diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts
index 829b45a66d0..e935775fcd3 100644
--- a/src/vs/workbench/contrib/terminal/common/terminal.ts
+++ b/src/vs/workbench/contrib/terminal/common/terminal.ts
@@ -15,6 +15,7 @@ import { URI } from 'vs/base/common/uri';
import { IGenericMarkProperties, IProcessDetails, ISerializedCommandDetectionCapability } from 'vs/platform/terminal/common/terminalProcess';
import { Registry } from 'vs/platform/registry/common/platform';
import { ITerminalCapabilityStore, IXtermMarker } from 'vs/platform/terminal/common/capabilities/capabilities';
+import { ThemeIcon } from 'vs/platform/theme/common/themeService';
export const TERMINAL_VIEW_ID = 'terminal';
@@ -54,6 +55,7 @@ export interface ITerminalProfileResolverService {
getDefaultProfile(options: IShellLaunchConfigResolveOptions): Promise<ITerminalProfile>;
getDefaultShell(options: IShellLaunchConfigResolveOptions): Promise<string>;
getDefaultShellArgs(options: IShellLaunchConfigResolveOptions): Promise<string | string[]>;
+ getDefaultIcon(): TerminalIcon & ThemeIcon;
getEnvironment(remoteAuthority: string | undefined): Promise<IProcessEnvironment>;
createProfileFromShellAndShellArgs(shell?: unknown, shellArgs?: unknown): Promise<ITerminalProfile | string>;
}
@@ -116,6 +118,7 @@ export interface ITerminalBackend {
onDidRequestDetach: Event<{ requestId: number; workspaceId: string; instanceId: number }>;
attachToProcess(id: number): Promise<ITerminalChildProcess | undefined>;
+ attachToRevivedProcess(id: number): Promise<ITerminalChildProcess | undefined>;
listProcesses(): Promise<IProcessDetails[]>;
getDefaultSystemShell(osOverride?: OperatingSystem): Promise<string>;
getProfiles(profiles: unknown, defaultProfile: unknown, includeDetectedProfiles?: boolean): Promise<ITerminalProfile[]>;
@@ -340,7 +343,7 @@ export interface ITerminalCommand {
cwd?: string;
exitCode?: number;
marker?: IXtermMarker;
- hasOutput: boolean;
+ hasOutput(): boolean;
getOutput(): string | undefined;
genericMarkProperties?: IGenericMarkProperties;
}
@@ -475,6 +478,7 @@ export const enum TerminalCommandId {
OpenFileLink = 'workbench.action.terminal.openFileLink',
OpenWebLink = 'workbench.action.terminal.openUrlLink',
RunRecentCommand = 'workbench.action.terminal.runRecentCommand',
+ CopyLastCommand = 'workbench.action.terminal.copyLastCommand',
GoToRecentDirectory = 'workbench.action.terminal.goToRecentDirectory',
CopySelection = 'workbench.action.terminal.copySelection',
CopySelectionAsHtml = 'workbench.action.terminal.copySelectionAsHtml',
@@ -573,6 +577,7 @@ export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [
TerminalCommandId.Clear,
TerminalCommandId.CopySelection,
TerminalCommandId.CopySelectionAsHtml,
+ TerminalCommandId.CopyLastCommand,
TerminalCommandId.DeleteToLineStart,
TerminalCommandId.DeleteWordLeft,
TerminalCommandId.DeleteWordRight,
diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts
index efe3aa79bd3..3c5513e7c90 100644
--- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts
+++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts
@@ -9,6 +9,8 @@ import { DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle, DEFAU
import { TerminalLocationString, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
import { isMacintosh, isWindows } from 'vs/base/common/platform';
import { Registry } from 'vs/platform/registry/common/platform';
+import { Codicon } from 'vs/base/common/codicons';
+import { terminalColorSchema, terminalIconSchema } from 'vs/platform/terminal/common/terminalPlatformConfiguration';
const terminalDescriptors = '\n- ' + [
'`\${cwd}`: ' + localize("cwd", "the terminal's current working directory"),
@@ -34,10 +36,19 @@ const terminalConfiguration: IConfigurationNode = {
type: 'object',
properties: {
[TerminalSettingId.SendKeybindingsToShell]: {
- markdownDescription: localize('terminal.integrated.sendKeybindingsToShell', "Dispatches most keybindings to the terminal instead of the workbench, overriding `#terminal.integrated.commandsToSkipShell#`, which can be used alternatively for fine tuning."),
+ markdownDescription: localize('terminal.integrated.sendKeybindingsToShell', "Dispatches most keybindings to the terminal instead of the workbench, overriding {0}, which can be used alternatively for fine tuning.", '`#terminal.integrated.commandsToSkipShell#`'),
type: 'boolean',
default: false
},
+ [TerminalSettingId.TabsDefaultColor]: {
+ description: localize('terminal.integrated.tabs.defaultColor', "A theme color ID to associate with terminal icons by default."),
+ ...terminalColorSchema
+ },
+ [TerminalSettingId.TabsDefaultIcon]: {
+ description: localize('terminal.integrated.tabs.defaultIcon', "A codicon ID to associate with terminal icons by default."),
+ ...terminalIconSchema,
+ default: Codicon.terminal.id,
+ },
[TerminalSettingId.TabsEnabled]: {
description: localize('terminal.integrated.tabs.enabled', 'Controls whether terminal tabs display as a list to the side of the terminal. When this is disabled a dropdown will display instead.'),
type: 'boolean',
@@ -106,17 +117,17 @@ const terminalConfiguration: IConfigurationNode = {
[TerminalSettingId.ShellIntegrationDecorationIconSuccess]: {
type: 'string',
default: 'primitive-dot',
- markdownDescription: localize('terminal.integrated.shellIntegration.decorationIconSuccess', "Controls the icon that will be used for each command in terminals with shell integration enabled that do not have an associated exit code. Set to `''` to hide the icon or disable decorations with `#terminal.integrated.shellIntegration.decorationsEnabled#`")
+ markdownDescription: localize('terminal.integrated.shellIntegration.decorationIconSuccess', "Controls the icon that will be used for each command in terminals with shell integration enabled that do not have an associated exit code. Set to {0} to hide the icon or disable decorations with {1}.", '`\'\'`', '`#terminal.integrated.shellIntegration.decorationsEnabled#`')
},
[TerminalSettingId.ShellIntegrationDecorationIconError]: {
type: 'string',
default: 'error-small',
- markdownDescription: localize('terminal.integrated.shellIntegration.decorationIconError', "Controls the icon that will be used for each command in terminals with shell integration enabled that do have an associated exit code. Set to `''` to hide the icon or disable decorations with `#terminal.integrated.shellIntegration.decorationsEnabled#`.")
+ markdownDescription: localize('terminal.integrated.shellIntegration.decorationIconError', "Controls the icon that will be used for each command in terminals with shell integration enabled that do have an associated exit code. Set to {0} to hide the icon or disable decorations with {1}.", '`\'\'`', '`#terminal.integrated.shellIntegration.decorationsEnabled#`')
},
[TerminalSettingId.ShellIntegrationDecorationIcon]: {
type: 'string',
default: 'circle-outline',
- markdownDescription: localize('terminal.integrated.shellIntegration.decorationIcon', "Controls the icon that will be used for skipped/empty commands. Set to `''` to hide the icon or disable decorations with `#terminal.integrated.shellIntegration.decorationsEnabled#`")
+ markdownDescription: localize('terminal.integrated.shellIntegration.decorationIcon', "Controls the icon that will be used for skipped/empty commands. Set to {0} to hide the icon or disable decorations with {1}.", '`\'\'`', '`#terminal.integrated.shellIntegration.decorationsEnabled#`')
},
[TerminalSettingId.TabsFocusMode]: {
type: 'string',
@@ -139,7 +150,7 @@ const terminalConfiguration: IConfigurationNode = {
default: false
},
[TerminalSettingId.AltClickMovesCursor]: {
- markdownDescription: localize('terminal.integrated.altClickMovesCursor', "If enabled, alt/option + click will reposition the prompt cursor to underneath the mouse when `#editor.multiCursorModifier#` is set to `'alt'` (the default value). This may not work reliably depending on your shell."),
+ markdownDescription: localize('terminal.integrated.altClickMovesCursor', "If enabled, alt/option + click will reposition the prompt cursor to underneath the mouse when {0} is set to {1} (the default value). This may not work reliably depending on your shell.", '`#editor.multiCursorModifier#`', '`\'alt\'`'),
type: 'boolean',
default: true
},
@@ -159,7 +170,7 @@ const terminalConfiguration: IConfigurationNode = {
default: true
},
[TerminalSettingId.FontFamily]: {
- markdownDescription: localize('terminal.integrated.fontFamily', "Controls the font family of the terminal, this defaults to `#editor.fontFamily#`'s value."),
+ markdownDescription: localize('terminal.integrated.fontFamily', "Controls the font family of the terminal, this defaults to {0}'s value.", '`#editor.fontFamily#`'),
type: 'string'
},
// TODO: Support font ligatures
@@ -254,7 +265,7 @@ const terminalConfiguration: IConfigurationNode = {
default: TerminalCursorStyle.BLOCK
},
[TerminalSettingId.CursorWidth]: {
- markdownDescription: localize('terminal.integrated.cursorWidth', "Controls the width of the cursor when `#terminal.integrated.cursorStyle#` is set to `line`."),
+ markdownDescription: localize('terminal.integrated.cursorWidth', "Controls the width of the cursor when {0} is set to {1}.", '`#terminal.integrated.cursorStyle#`', '`line`'),
type: 'number',
default: 1
},
@@ -354,7 +365,8 @@ const terminalConfiguration: IConfigurationNode = {
'terminal.integrated.commandsToSkipShell',
"A set of command IDs whose keybindings will not be sent to the shell but instead always be handled by VS Code. This allows keybindings that would normally be consumed by the shell to act instead the same as when the terminal is not focused, for example `Ctrl+P` to launch Quick Open.\n\n&nbsp;\n\nMany commands are skipped by default. To override a default and pass that command's keybinding to the shell instead, add the command prefixed with the `-` character. For example add `-workbench.action.quickOpen` to allow `Ctrl+P` to reach the shell.\n\n&nbsp;\n\nThe following list of default skipped commands is truncated when viewed in Settings Editor. To see the full list, {1} and search for the first command from the list below.\n\n&nbsp;\n\nDefault Skipped Commands:\n\n{0}",
DEFAULT_COMMANDS_TO_SKIP_SHELL.sort().map(command => `- ${command}`).join('\n'),
- `[${localize('openDefaultSettingsJson', "open the default settings JSON")}](command:workbench.action.openRawDefaultSettings '${localize('openDefaultSettingsJson.capitalized', "Open Default Settings (JSON)")}')`
+ `[${localize('openDefaultSettingsJson', "open the default settings JSON")}](command:workbench.action.openRawDefaultSettings '${localize('openDefaultSettingsJson.capitalized', "Open Default Settings (JSON)")}')`,
+
),
type: 'array',
items: {
@@ -363,7 +375,7 @@ const terminalConfiguration: IConfigurationNode = {
default: []
},
[TerminalSettingId.AllowChords]: {
- markdownDescription: localize('terminal.integrated.allowChords', "Whether or not to allow chord keybindings in the terminal. Note that when this is true and the keystroke results in a chord it will bypass `#terminal.integrated.commandsToSkipShell#`, setting this to false is particularly useful when you want ctrl+k to go to your shell (not VS Code)."),
+ markdownDescription: localize('terminal.integrated.allowChords', "Whether or not to allow chord keybindings in the terminal. Note that when this is true and the keystroke results in a chord it will bypass {0}, setting this to false is particularly useful when you want ctrl+k to go to your shell (not VS Code).", '`#terminal.integrated.commandsToSkipShell#`'),
type: 'boolean',
default: true
},
@@ -464,7 +476,7 @@ const terminalConfiguration: IConfigurationNode = {
default: 30,
},
[TerminalSettingId.LocalEchoEnabled]: {
- markdownDescription: localize('terminal.integrated.localEchoEnabled', "When local echo should be enabled. This will override `#terminal.integrated.localEchoLatencyThreshold#`"),
+ markdownDescription: localize('terminal.integrated.localEchoEnabled', "When local echo should be enabled. This will override {0}", '`#terminal.integrated.localEchoLatencyThreshold#`'),
type: 'string',
enum: ['on', 'off', 'auto'],
enumDescriptions: [
@@ -536,7 +548,7 @@ const terminalConfiguration: IConfigurationNode = {
restricted: true,
markdownDescription: localize('terminal.integrated.shellIntegration.enabled', "Enable features like enhanced command tracking and current working directory detection. \n\nShell integration works by injecting the shell with a startup script. The script gives VS Code insight into what is happening within the terminal.\n\nSupported shells:\n\n- Linux/macOS: bash, pwsh, zsh\n - Windows: pwsh\n\nThis setting applies only when terminals are created, so you will need to restart your terminals for it to take effect.\n\n Note that the script injection may not work if you have custom arguments defined in the terminal profile, a [complex bash `PROMPT_COMMAND`](https://code.visualstudio.com/docs/editor/integrated-terminal#_complex-bash-promptcommand), or other unsupported setup."),
type: 'boolean',
- default: false
+ default: true
},
[TerminalSettingId.ShellIntegrationDecorationsEnabled]: {
restricted: true,
diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts
index 72f1792fc71..b97b332ea27 100644
--- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts
+++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts
@@ -31,6 +31,8 @@ export const enum TerminalContextKeyStrings {
TabsSingularSelection = 'terminalTabsSingularSelection',
SplitTerminal = 'terminalSplitTerminal',
ShellType = 'terminalShellType',
+ InTerminalRunCommandPicker = 'inTerminalRunCommandPicker',
+ TerminalShellIntegrationEnabled = 'terminalShellIntegrationEnabled'
}
export namespace TerminalContextKeys {
@@ -119,4 +121,10 @@ export namespace TerminalContextKeys {
/** Whether the focused tab's terminal is a split terminal. */
export const splitTerminal = new RawContextKey<boolean>(TerminalContextKeyStrings.SplitTerminal, false, localize('isSplitTerminalContextKey', "Whether the focused tab's terminal is a split terminal."));
+
+ /** Whether the terminal run command picker is currently open. */
+ export const inTerminalRunCommandPicker = new RawContextKey<boolean>(TerminalContextKeyStrings.InTerminalRunCommandPicker, false, localize('inTerminalRunCommandPickerContextKey', "Whether the terminal run command picker is currently open."));
+
+ /** Whether shell integration is enabled in the active terminal. This only considers full VS Code shell integration. */
+ export const terminalShellIntegrationEnabled = new RawContextKey<boolean>(TerminalContextKeyStrings.TerminalShellIntegrationEnabled, false, localize('terminalShellIntegrationEnabled', "Whether shell integration is enabled in the active terminal"));
}
diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts
index b2f01c3e853..d61acd4c4a4 100644
--- a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts
+++ b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts
@@ -168,6 +168,16 @@ class LocalTerminalBackend extends BaseTerminalBackend implements ITerminalBacke
return undefined;
}
+ async attachToRevivedProcess(id: number): Promise<ITerminalChildProcess | undefined> {
+ try {
+ const newId = await this._localPtyService.getRevivedPtyNewId(id) ?? id;
+ return await this.attachToProcess(newId);
+ } catch (e) {
+ this._logService.trace(`Couldn't attach to process ${e.message}`);
+ }
+ return undefined;
+ }
+
async listProcesses(): Promise<IProcessDetails[]> {
return this._localPtyService.listProcesses();
}
diff --git a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts
index dcbdef59e2c..225dd72bbdc 100644
--- a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts
+++ b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts
@@ -97,10 +97,21 @@ suite('Workbench - TerminalLinkOpeners', () => {
capabilities.add(TerminalCapability.CommandDetection, commandDetection);
});
- test('should open single exact match against cwd when searching if it exists', async () => {
+ test('should open single exact match against cwd when searching if it exists when command detection cwd is available', async () => {
localFileOpener = instantiationService.createInstance(TerminalLocalFileLinkOpener, OperatingSystem.Linux);
const localFolderOpener = instantiationService.createInstance(TerminalLocalFolderInWorkspaceLinkOpener);
opener = instantiationService.createInstance(TerminalSearchLinkOpener, capabilities, Promise.resolve('/initial/cwd'), localFileOpener, localFolderOpener, OperatingSystem.Linux);
+ // Set a fake detected command starting as line 0 to establish the cwd
+ commandDetection.setCommands([{
+ command: '',
+ cwd: '/initial/cwd',
+ timestamp: 0,
+ getOutput() { return undefined; },
+ marker: {
+ line: 0
+ } as Partial<IXtermMarker> as any,
+ hasOutput() { return true; }
+ }]);
fileService.setFiles([
URI.from({ scheme: Schemas.file, path: '/initial/cwd/foo/bar.txt' }),
URI.from({ scheme: Schemas.file, path: '/initial/cwd/foo2/bar.txt' })
@@ -116,6 +127,44 @@ suite('Workbench - TerminalLinkOpeners', () => {
});
});
+ test('should open single exact match against cwd for paths containing a separator when searching if it exists, even when command detection isn\'t available', async () => {
+ localFileOpener = instantiationService.createInstance(TerminalLocalFileLinkOpener, OperatingSystem.Linux);
+ const localFolderOpener = instantiationService.createInstance(TerminalLocalFolderInWorkspaceLinkOpener);
+ opener = instantiationService.createInstance(TerminalSearchLinkOpener, capabilities, Promise.resolve('/initial/cwd'), localFileOpener, localFolderOpener, OperatingSystem.Linux);
+ fileService.setFiles([
+ URI.from({ scheme: Schemas.file, path: '/initial/cwd/foo/bar.txt' }),
+ URI.from({ scheme: Schemas.file, path: '/initial/cwd/foo2/bar.txt' })
+ ]);
+ await opener.open({
+ text: 'foo/bar.txt',
+ bufferRange: { start: { x: 1, y: 1 }, end: { x: 8, y: 1 } },
+ type: TerminalBuiltinLinkType.Search
+ });
+ deepStrictEqual(activationResult, {
+ link: 'file:///initial/cwd/foo/bar.txt',
+ source: 'editor'
+ });
+ });
+
+ test('should not open single exact match for paths not containing a when command detection isn\'t available', async () => {
+ localFileOpener = instantiationService.createInstance(TerminalLocalFileLinkOpener, OperatingSystem.Linux);
+ const localFolderOpener = instantiationService.createInstance(TerminalLocalFolderInWorkspaceLinkOpener);
+ opener = instantiationService.createInstance(TerminalSearchLinkOpener, capabilities, Promise.resolve('/initial/cwd'), localFileOpener, localFolderOpener, OperatingSystem.Linux);
+ fileService.setFiles([
+ URI.from({ scheme: Schemas.file, path: '/initial/cwd/foo/bar.txt' }),
+ URI.from({ scheme: Schemas.file, path: '/initial/cwd/foo2/bar.txt' })
+ ]);
+ await opener.open({
+ text: 'bar.txt',
+ bufferRange: { start: { x: 1, y: 1 }, end: { x: 8, y: 1 } },
+ type: TerminalBuiltinLinkType.Search
+ });
+ deepStrictEqual(activationResult, {
+ link: 'bar.txt',
+ source: 'search'
+ });
+ });
+
suite('macOS/Linux', () => {
setup(() => {
localFileOpener = instantiationService.createInstance(TerminalLocalFileLinkOpener, OperatingSystem.Linux);
@@ -139,7 +188,7 @@ suite('Workbench - TerminalLinkOpeners', () => {
marker: {
line: 0
} as Partial<IXtermMarker> as any,
- hasOutput: true
+ hasOutput() { return true; }
}]);
await opener.open({
text: 'file.txt',
@@ -188,7 +237,7 @@ suite('Workbench - TerminalLinkOpeners', () => {
marker: {
line: 0
} as Partial<IXtermMarker> as any,
- hasOutput: true
+ hasOutput() { return true; }
}]);
await opener.open({
text: 'file.txt',
diff --git a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLocalLinkDetector.test.ts b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLocalLinkDetector.test.ts
index 8ac3607d6e8..93ff1ce3687 100644
--- a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLocalLinkDetector.test.ts
+++ b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLocalLinkDetector.test.ts
@@ -58,6 +58,8 @@ const supportedLinkFormats: LinkFormatInfo[] = [
{ urlFormat: '{0} on line {1}, column {2}', line: '5', column: '3' },
{ urlFormat: '{0}:line {1}', line: '5' },
{ urlFormat: '{0}:line {1}, column {2}', line: '5', column: '3' },
+ { urlFormat: '{0}: line {1}', line: '5' },
+ { urlFormat: '{0}: line {1}, col {2}', line: '5', column: '3' },
{ urlFormat: '{0}({1})', line: '5' },
{ urlFormat: '{0} ({1})', line: '5' },
{ urlFormat: '{0}({1},{2})', line: '5', column: '3' },
@@ -66,6 +68,7 @@ const supportedLinkFormats: LinkFormatInfo[] = [
{ urlFormat: '{0} ({1}, {2})', line: '5', column: '3' },
{ urlFormat: '{0}:{1}', line: '5' },
{ urlFormat: '{0}:{1}:{2}', line: '5', column: '3' },
+ { urlFormat: '{0} {1}:{2}', line: '5', column: '3' },
{ urlFormat: '{0}[{1}]', line: '5' },
{ urlFormat: '{0} [{1}]', line: '5' },
{ urlFormat: '{0}[{1},{2}]', line: '5', column: '3' },
diff --git a/src/vs/workbench/contrib/terminal/test/browser/xterm/decorationAddon.test.ts b/src/vs/workbench/contrib/terminal/test/browser/xterm/decorationAddon.test.ts
index 80802218586..3ea312e1e1e 100644
--- a/src/vs/workbench/contrib/terminal/test/browser/xterm/decorationAddon.test.ts
+++ b/src/vs/workbench/contrib/terminal/test/browser/xterm/decorationAddon.test.ts
@@ -56,21 +56,21 @@ suite('DecorationAddon', () => {
suite('registerDecoration', async () => {
test('should throw when command has no marker', async () => {
- throws(() => decorationAddon.registerCommandDecoration({ command: 'cd src', timestamp: Date.now(), hasOutput: false } as ITerminalCommand));
+ throws(() => decorationAddon.registerCommandDecoration({ command: 'cd src', timestamp: Date.now(), hasOutput: () => false } as ITerminalCommand));
});
test('should return undefined when marker has been disposed of', async () => {
const marker = xterm.registerMarker(1);
marker?.dispose();
- strictEqual(decorationAddon.registerCommandDecoration({ command: 'cd src', marker, timestamp: Date.now(), hasOutput: false } as ITerminalCommand), undefined);
+ strictEqual(decorationAddon.registerCommandDecoration({ command: 'cd src', marker, timestamp: Date.now(), hasOutput: () => false } as ITerminalCommand), undefined);
});
test('should return undefined when command is just empty chars', async () => {
const marker = xterm.registerMarker(1);
marker?.dispose();
- strictEqual(decorationAddon.registerCommandDecoration({ command: ' ', marker, timestamp: Date.now(), hasOutput: false } as ITerminalCommand), undefined);
+ strictEqual(decorationAddon.registerCommandDecoration({ command: ' ', marker, timestamp: Date.now(), hasOutput: () => false } as ITerminalCommand), undefined);
});
test('should return decoration when marker has not been disposed of', async () => {
const marker = xterm.registerMarker(2);
- notEqual(decorationAddon.registerCommandDecoration({ command: 'cd src', marker, timestamp: Date.now(), hasOutput: false } as ITerminalCommand), undefined);
+ notEqual(decorationAddon.registerCommandDecoration({ command: 'cd src', marker, timestamp: Date.now(), hasOutput: () => false } as ITerminalCommand), undefined);
});
});
});
diff --git a/src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts b/src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts
index a35b293a2a9..99bc8e0b095 100644
--- a/src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts
+++ b/src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts
@@ -80,7 +80,7 @@ const defaultTerminalConfig: Partial<ITerminalConfiguration> = {
scrollback: 1000,
fastScrollSensitivity: 2,
mouseWheelScrollSensitivity: 1,
- unicodeVersion: '11'
+ unicodeVersion: '6'
};
suite('XtermTerminal', () => {
diff --git a/src/vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper.ts b/src/vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper.ts
index 0f0d26be6b4..6a74365ede4 100644
--- a/src/vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper.ts
+++ b/src/vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper.ts
@@ -6,11 +6,11 @@
import { IIdentityProvider } from 'vs/base/browser/ui/list/list';
import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree';
import { ITreeElement } from 'vs/base/browser/ui/tree/tree';
-import { IActionableTestTreeElement, TestExplorerTreeElement, TestItemTreeElement } from 'vs/workbench/contrib/testing/browser/explorerProjections/index';
+import { IActionableTestTreeElement, TestExplorerTreeElement, TestItemTreeElement, TestTreeErrorMessage } from 'vs/workbench/contrib/testing/browser/explorerProjections/index';
-export const testIdentityProvider: IIdentityProvider<TestItemTreeElement> = {
+export const testIdentityProvider: IIdentityProvider<TestExplorerTreeElement> = {
getId(element) {
- return element.treeId + '\0' + element.test.expand;
+ return element.treeId + '\0' + (element instanceof TestTreeErrorMessage ? 'error' : element.test.expand);
}
};
diff --git a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts
index db687ec08b6..556f7f4e65d 100644
--- a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts
+++ b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts
@@ -406,7 +406,7 @@ export class CancelTestRunAction extends Action2 {
constructor() {
super({
id: TestCommandId.CancelTestRunAction,
- title: localize('testing.cancelRun', "Cancel Test Run"),
+ title: { value: localize('testing.cancelRun', "Cancel Test Run"), original: 'Cancel Test Run' },
icon: icons.testingCancelIcon,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
@@ -443,7 +443,7 @@ export class TestingViewAsListAction extends ViewAction<TestingExplorerView> {
super({
id: TestCommandId.TestingViewAsListAction,
viewId: Testing.ExplorerViewId,
- title: localize('testing.viewAsList', "View as List"),
+ title: { value: localize('testing.viewAsList', "View as List"), original: 'View as List' },
toggled: TestingContextKeys.viewMode.isEqualTo(TestExplorerViewMode.List),
menu: {
id: MenuId.ViewTitle,
@@ -467,7 +467,7 @@ export class TestingViewAsTreeAction extends ViewAction<TestingExplorerView> {
super({
id: TestCommandId.TestingViewAsTreeAction,
viewId: Testing.ExplorerViewId,
- title: localize('testing.viewAsTree', "View as Tree"),
+ title: { value: localize('testing.viewAsTree', "View as Tree"), original: 'View as Tree' },
toggled: TestingContextKeys.viewMode.isEqualTo(TestExplorerViewMode.Tree),
menu: {
id: MenuId.ViewTitle,
@@ -492,7 +492,7 @@ export class TestingSortByStatusAction extends ViewAction<TestingExplorerView> {
super({
id: TestCommandId.TestingSortByStatusAction,
viewId: Testing.ExplorerViewId,
- title: localize('testing.sortByStatus', "Sort by Status"),
+ title: { value: localize('testing.sortByStatus', "Sort by Status"), original: 'Sort by Status' },
toggled: TestingContextKeys.viewSorting.isEqualTo(TestExplorerViewSorting.ByStatus),
menu: {
id: MenuId.ViewTitle,
@@ -516,7 +516,7 @@ export class TestingSortByLocationAction extends ViewAction<TestingExplorerView>
super({
id: TestCommandId.TestingSortByLocationAction,
viewId: Testing.ExplorerViewId,
- title: localize('testing.sortByLocation', "Sort by Location"),
+ title: { value: localize('testing.sortByLocation', "Sort by Location"), original: 'Sort by Location' },
toggled: TestingContextKeys.viewSorting.isEqualTo(TestExplorerViewSorting.ByLocation),
menu: {
id: MenuId.ViewTitle,
@@ -540,7 +540,7 @@ export class TestingSortByDurationAction extends ViewAction<TestingExplorerView>
super({
id: TestCommandId.TestingSortByDurationAction,
viewId: Testing.ExplorerViewId,
- title: localize('testing.sortByDuration', "Sort by Duration"),
+ title: { value: localize('testing.sortByDuration', "Sort by Duration"), original: 'Sort by Duration' },
toggled: TestingContextKeys.viewSorting.isEqualTo(TestExplorerViewSorting.ByDuration),
menu: {
id: MenuId.ViewTitle,
@@ -563,7 +563,7 @@ export class ShowMostRecentOutputAction extends Action2 {
constructor() {
super({
id: TestCommandId.ShowMostRecentOutputAction,
- title: localize('testing.showMostRecentOutput', "Show Output"),
+ title: { value: localize('testing.showMostRecentOutput', "Show Output"), original: 'Show Output' },
category,
icon: Codicon.terminal,
keybinding: {
@@ -594,7 +594,7 @@ export class CollapseAllAction extends ViewAction<TestingExplorerView> {
super({
id: TestCommandId.CollapseAllAction,
viewId: Testing.ExplorerViewId,
- title: localize('testing.collapseAll', "Collapse All Tests"),
+ title: { value: localize('testing.collapseAll', "Collapse All Tests"), original: 'Collapse All Tests' },
icon: Codicon.collapseAll,
menu: {
id: MenuId.ViewTitle,
@@ -617,7 +617,7 @@ export class ClearTestResultsAction extends Action2 {
constructor() {
super({
id: TestCommandId.ClearTestResultsAction,
- title: localize('testing.clearResults', "Clear All Results"),
+ title: { value: localize('testing.clearResults', "Clear All Results"), original: 'Clear All Results' },
category,
icon: Codicon.trash,
menu: [{
@@ -646,7 +646,7 @@ export class GoToTest extends Action2 {
constructor() {
super({
id: TestCommandId.GoToTest,
- title: localize('testing.editFocusedTest', "Go to Test"),
+ title: { value: localize('testing.editFocusedTest', "Go to Test"), original: 'Go to Test' },
icon: Codicon.goToFile,
menu: testItemInlineAndInContext(ActionOrder.GoToTest, TestingContextKeys.testItemHasUri.isEqualTo(true)),
keybinding: {
@@ -742,7 +742,7 @@ export class RunAtCursor extends ExecuteTestAtCursor {
constructor() {
super({
id: TestCommandId.RunAtCursor,
- title: localize('testing.runAtCursor', "Run Test at Cursor"),
+ title: { value: localize('testing.runAtCursor', "Run Test at Cursor"), original: 'Run Test at Cursor' },
category,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
@@ -757,7 +757,7 @@ export class DebugAtCursor extends ExecuteTestAtCursor {
constructor() {
super({
id: TestCommandId.DebugAtCursor,
- title: localize('testing.debugAtCursor', "Debug Test at Cursor"),
+ title: { value: localize('testing.debugAtCursor', "Debug Test at Cursor"), original: 'Debug Test at Cursor' },
category,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
@@ -824,7 +824,7 @@ export class RunCurrentFile extends ExecuteTestsInCurrentFile {
constructor() {
super({
id: TestCommandId.RunCurrentFile,
- title: localize('testing.runCurrentFile', "Run Tests in Current File"),
+ title: { value: localize('testing.runCurrentFile', "Run Tests in Current File"), original: 'Run Tests in Current File' },
category,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
@@ -840,7 +840,7 @@ export class DebugCurrentFile extends ExecuteTestsInCurrentFile {
constructor() {
super({
id: TestCommandId.DebugCurrentFile,
- title: localize('testing.debugCurrentFile', "Debug Tests in Current File"),
+ title: { value: localize('testing.debugCurrentFile', "Debug Tests in Current File"), original: 'Debug Tests in Current File' },
category,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
@@ -948,7 +948,7 @@ export class ReRunFailedTests extends RunOrDebugFailedTests {
constructor() {
super({
id: TestCommandId.ReRunFailedTests,
- title: localize('testing.reRunFailTests', "Rerun Failed Tests"),
+ title: { value: localize('testing.reRunFailTests', "Rerun Failed Tests"), original: 'Rerun Failed Tests' },
category,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
@@ -969,7 +969,7 @@ export class DebugFailedTests extends RunOrDebugFailedTests {
constructor() {
super({
id: TestCommandId.DebugFailedTests,
- title: localize('testing.debugFailTests', "Debug Failed Tests"),
+ title: { value: localize('testing.debugFailTests', "Debug Failed Tests"), original: 'Debug Failed Tests' },
category,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
@@ -990,7 +990,7 @@ export class ReRunLastRun extends RunOrDebugLastRun {
constructor() {
super({
id: TestCommandId.ReRunLastRun,
- title: localize('testing.reRunLastRun', "Rerun Last Run"),
+ title: { value: localize('testing.reRunLastRun', "Rerun Last Run"), original: 'Rerun Last Run' },
category,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
@@ -1011,7 +1011,7 @@ export class DebugLastRun extends RunOrDebugLastRun {
constructor() {
super({
id: TestCommandId.DebugLastRun,
- title: localize('testing.debugLastRun', "Debug Last Run"),
+ title: { value: localize('testing.debugLastRun', "Debug Last Run"), original: 'Debug Last Run' },
category,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
@@ -1032,7 +1032,7 @@ export class SearchForTestExtension extends Action2 {
constructor() {
super({
id: TestCommandId.SearchForTestExtension,
- title: localize('testing.searchForTestExtension', "Search for Test Extension"),
+ title: { value: localize('testing.searchForTestExtension', "Search for Test Extension"), original: 'Search for Test Extension' },
});
}
@@ -1048,7 +1048,7 @@ export class OpenOutputPeek extends Action2 {
constructor() {
super({
id: TestCommandId.OpenOutputPeek,
- title: localize('testing.openOutputPeek', "Peek Output"),
+ title: { value: localize('testing.openOutputPeek', "Peek Output"), original: 'Peek Output' },
category,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
@@ -1070,7 +1070,7 @@ export class ToggleInlineTestOutput extends Action2 {
constructor() {
super({
id: TestCommandId.ToggleInlineTestOutput,
- title: localize('testing.toggleInlineTestOutput', "Toggle Inline Test Output"),
+ title: { value: localize('testing.toggleInlineTestOutput', "Toggle Inline Test Output"), original: 'Toggle Inline Test Output' },
category,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
@@ -1119,7 +1119,7 @@ export class RefreshTestsAction extends Action2 {
constructor() {
super({
id: TestCommandId.RefreshTestsAction,
- title: localize('testing.refreshTests', "Refresh Tests"),
+ title: { value: localize('testing.refreshTests', "Refresh Tests"), original: 'Refresh Tests' },
category,
icon: icons.testingRefreshTests,
keybinding: {
@@ -1155,7 +1155,7 @@ export class CancelTestRefreshAction extends Action2 {
constructor() {
super({
id: TestCommandId.CancelTestRefreshAction,
- title: localize('testing.cancelTestRefresh', "Cancel Test Refresh"),
+ title: { value: localize('testing.cancelTestRefresh', "Cancel Test Refresh"), original: 'Cancel Test Refresh' },
category,
icon: icons.testingCancelRefreshTests,
menu: refreshMenus(true),
diff --git a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts
index 3605c75ec80..f6580294960 100644
--- a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts
+++ b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts
@@ -87,7 +87,6 @@ viewsRegistry.registerViews([{
name: localize('testExplorer', "Test Explorer"),
ctorDescriptor: new SyncDescriptor(TestingExplorerView),
canToggleVisibility: true,
- workspace: true,
canMoveView: true,
weight: 80,
order: -999,
diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts
index 4d1af363aa7..e9d136fdc9f 100644
--- a/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts
+++ b/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts
@@ -35,7 +35,7 @@ const testFilterDescriptions: { [K in TestFilterTerm]: string } = {
export class TestingExplorerFilter extends BaseActionViewItem {
private input!: SuggestEnabledInputWithHistory;
private wrapper!: HTMLDivElement;
- private readonly history: StoredValue<string[]> = this.instantiationService.createInstance(StoredValue, {
+ private readonly history: StoredValue<{ values: string[]; lastValue: string } | string[]> = this.instantiationService.createInstance(StoredValue, {
key: 'testing.filterHistory2',
scope: StorageScope.WORKSPACE,
target: StorageTarget.USER
@@ -65,9 +65,12 @@ export class TestingExplorerFilter extends BaseActionViewItem {
const wrapper = this.wrapper = dom.$('.testing-filter-wrapper');
container.appendChild(wrapper);
- const history = this.history.get([]);
- if (history.length) {
- this.state.setText(history[history.length - 1]);
+ let history = this.history.get({ lastValue: '', values: [] });
+ if (history instanceof Array) {
+ history = { lastValue: '', values: history };
+ }
+ if (history.lastValue) {
+ this.state.setText(history.lastValue);
}
const input = this.input = this._register(this.instantiationService.createInstance(ContextScopedSuggestEnabledInputWithHistory, {
@@ -94,7 +97,7 @@ export class TestingExplorerFilter extends BaseActionViewItem {
value: this.state.text.value,
placeholderText: localize('testExplorerFilter', "Filter (e.g. text, !exclude, @tag)"),
},
- history
+ history: history.values
}));
this._register(attachSuggestEnabledInputBoxStyler(input, this.themeService));
@@ -145,12 +148,7 @@ export class TestingExplorerFilter extends BaseActionViewItem {
* Persists changes to the input history.
*/
public saveState() {
- const history = this.input.getHistory();
- if (history.length) {
- this.history.store(history);
- } else {
- this.history.delete();
- }
+ this.history.store({ lastValue: this.input.getValue(), values: this.input.getHistory() });
}
/**
@@ -252,7 +250,7 @@ registerAction2(class extends Action2 {
constructor() {
super({
id: TestCommandId.FilterAction,
- title: localize('filter', "Filter"),
+ title: { value: localize('filter', "Filter"), original: 'Filter' },
});
}
async run(): Promise<void> { }
diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts
index 0c639a29c53..66ffbef2595 100644
--- a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts
+++ b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts
@@ -337,7 +337,7 @@ export class TestingExplorerView extends ViewPane {
icon: group === TestRunProfileBitset.Run
? icons.testingRunAllIcon
: icons.testingDebugAllIcon,
- }, undefined, undefined);
+ }, undefined, undefined, undefined);
const dropdownAction = new Action('selectRunConfig', 'Select Configuration...', 'codicon-chevron-down', true);
diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts
index c3452de68dd..988ad294015 100644
--- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts
+++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts
@@ -1666,7 +1666,7 @@ export class GoToNextMessageAction extends EditorAction2 {
super({
id: GoToNextMessageAction.ID,
f1: true,
- title: localize('testing.goToNextMessage', "Go to Next Test Failure"),
+ title: { value: localize('testing.goToNextMessage', "Go to Next Test Failure"), original: 'Go to Next Test Failure' },
icon: Codicon.arrowDown,
category: CATEGORIES.Test,
keybinding: {
@@ -1696,7 +1696,7 @@ export class GoToPreviousMessageAction extends EditorAction2 {
super({
id: GoToPreviousMessageAction.ID,
f1: true,
- title: localize('testing.goToPreviousMessage', "Go to Previous Test Failure"),
+ title: { value: localize('testing.goToPreviousMessage', "Go to Previous Test Failure"), original: 'Go to Previous Test Failure' },
icon: Codicon.arrowUp,
category: CATEGORIES.Test,
keybinding: {
@@ -1726,7 +1726,7 @@ export class OpenMessageInEditorAction extends EditorAction2 {
super({
id: OpenMessageInEditorAction.ID,
f1: false,
- title: localize('testing.openMessageInEditor', "Open in Editor"),
+ title: { value: localize('testing.openMessageInEditor', "Open in Editor"), original: 'Open in Editor' },
icon: Codicon.linkExternal,
category: CATEGORIES.Test,
menu: [{ id: MenuId.TestPeekTitle }],
@@ -1744,7 +1744,7 @@ export class ToggleTestingPeekHistory extends EditorAction2 {
super({
id: ToggleTestingPeekHistory.ID,
f1: true,
- title: localize('testing.toggleTestingPeekHistory', "Toggle Test History in Peek"),
+ title: { value: localize('testing.toggleTestingPeekHistory', "Toggle Test History in Peek"), original: 'Toggle Test History in Peek' },
icon: Codicon.history,
category: CATEGORIES.Test,
menu: [{
diff --git a/src/vs/workbench/contrib/update/browser/update.contribution.ts b/src/vs/workbench/contrib/update/browser/update.contribution.ts
index 5d22660c689..1ff72278d33 100644
--- a/src/vs/workbench/contrib/update/browser/update.contribution.ts
+++ b/src/vs/workbench/contrib/update/browser/update.contribution.ts
@@ -34,8 +34,8 @@ class DownloadUpdateAction extends Action2 {
constructor() {
super({
id: 'update.downloadUpdate',
- title: localize('downloadUpdate', "Download Update"),
- category: product.nameShort,
+ title: { value: localize('downloadUpdate', "Download Update"), original: 'Download Update' },
+ category: { value: product.nameShort, original: product.nameShort },
f1: true,
precondition: CONTEXT_UPDATE_STATE.isEqualTo(StateType.AvailableForDownload)
});
@@ -50,8 +50,8 @@ class InstallUpdateAction extends Action2 {
constructor() {
super({
id: 'update.installUpdate',
- title: localize('installUpdate', "Install Update"),
- category: product.nameShort,
+ title: { value: localize('installUpdate', "Install Update"), original: 'Install Update' },
+ category: { value: product.nameShort, original: product.nameShort },
f1: true,
precondition: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Downloaded)
});
@@ -66,8 +66,8 @@ class RestartToUpdateAction extends Action2 {
constructor() {
super({
id: 'update.restartToUpdate',
- title: localize('restartToUpdate', "Restart to Update"),
- category: product.nameShort,
+ title: { value: localize('restartToUpdate', "Restart to Update"), original: 'Restart to Update' },
+ category: { value: product.nameShort, original: product.nameShort },
f1: true,
precondition: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Ready)
});
diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts
index 8a75af540d3..0c2b7990a49 100644
--- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts
+++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts
@@ -6,7 +6,6 @@
import { Codicon } from 'vs/base/common/codicons';
import { Event } from 'vs/base/common/event';
import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
-import { isWeb } from 'vs/base/common/platform';
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { localize } from 'vs/nls';
import { Action2, ISubmenuItem, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions';
@@ -15,6 +14,7 @@ import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from '
import { IProductService } from 'vs/platform/product/common/productService';
import { Registry } from 'vs/platform/registry/common/platform';
import { registerColor } from 'vs/platform/theme/common/colorRegistry';
+import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
import { themeColorFromId } from 'vs/platform/theme/common/themeService';
import { IUserDataProfile, IUserDataProfilesService, PROFILES_ENABLEMENT_CONFIG } from 'vs/platform/userDataProfile/common/userDataProfile';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
@@ -25,6 +25,8 @@ import { IUserDataProfileManagementService, IUserDataProfileService, ManageProfi
const CONTEXT_CURRENT_PROFILE = new RawContextKey<string>('currentUserDataProfile', '');
+export const userDataProfilesIcon = registerIcon('settingsProfiles-icon', Codicon.settings, localize('settingsProfilesIcon', 'Icon for Settings Profiles.'));
+
export class UserDataProfilesWorkbenchContribution extends Disposable implements IWorkbenchContribution {
private readonly currentProfileContext: IContextKey<string>;
@@ -53,7 +55,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
}
private registerConfiguration(): void {
- if (!isWeb && this.productService.quality !== 'stable') {
+ if (this.productService.quality !== 'stable') {
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).registerConfiguration({
...workbenchConfigurationNodeBase,
'properties': {
@@ -61,7 +63,8 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
'type': 'boolean',
'default': false,
'description': localize('workbench.experimental.settingsProfiles.enabled', "Controls whether to enable the Settings Profiles preview feature."),
- scope: ConfigurationScope.APPLICATION
+ scope: ConfigurationScope.APPLICATION,
+ ignoreSync: true
}
}
});
@@ -115,7 +118,6 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
id: `workbench.profiles.actions.profileEntry.${profile.id}`,
title: profile.name,
toggled: ContextKeyExpr.equals(CONTEXT_CURRENT_PROFILE.key, profile.id),
- precondition: ContextKeyExpr.notEquals(CONTEXT_CURRENT_PROFILE.key, profile.id),
menu: [
{
id: ManageProfilesSubMenu,
@@ -126,19 +128,21 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
});
}
async run(accessor: ServicesAccessor) {
- return that.userDataProfileManagementService.switchProfile(profile);
+ if (that.userDataProfileService.currentProfile.id !== profile.id) {
+ return that.userDataProfileManagementService.switchProfile(profile);
+ }
}
});
}
private profileStatusAccessor: IStatusbarEntryAccessor | undefined;
private updateStatus(): void {
- if (this.userDataProfilesService.profiles.length) {
+ if (this.userDataProfilesService.profiles.length > 1) {
const statusBarEntry: IStatusbarEntry = {
name: PROFILES_CATEGORY,
command: 'workbench.profiles.actions.switchProfile',
ariaLabel: localize('currentProfile', "Current Settings Profile is {0}", this.userDataProfileService.currentProfile.name),
- text: `$(${Codicon.multipleWindows.id}) ${this.userDataProfileService.currentProfile.name!}`,
+ text: `$(${userDataProfilesIcon.id}) ${this.userDataProfileService.currentProfile.name!}`,
tooltip: localize('profileTooltip', "{0}: {1}", PROFILES_CATEGORY, this.userDataProfileService.currentProfile.name),
color: themeColorFromId(STATUS_BAR_SETTINGS_PROFILE_FOREGROUND),
backgroundColor: themeColorFromId(STATUS_BAR_SETTINGS_PROFILE_BACKGROUND)
diff --git a/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts b/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts
index e796f1f897e..01a3ef3d346 100644
--- a/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts
+++ b/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts
@@ -7,8 +7,8 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { joinPath } from 'vs/base/common/resources';
import { localize } from 'vs/nls';
-import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
-import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
+import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
+import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IFileService } from 'vs/platform/files/common/files';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { INotificationService } from 'vs/platform/notification/common/notification';
@@ -19,6 +19,7 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile
import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
import { CATEGORIES } from 'vs/workbench/common/actions';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
+import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
registerAction2(class CreateFromCurrentProfileAction extends Action2 {
constructor() {
@@ -116,12 +117,17 @@ registerAction2(class RemoveProfileAction extends Action2 {
const userDataProfileService = accessor.get(IUserDataProfileService);
const userDataProfilesService = accessor.get(IUserDataProfilesService);
const userDataProfileManagementService = accessor.get(IUserDataProfileManagementService);
+ const notificationService = accessor.get(INotificationService);
const profiles = userDataProfilesService.profiles.filter(p => p.id !== userDataProfileService.currentProfile.id && !p.isDefault);
if (profiles.length) {
const pick = await quickInputService.pick(profiles.map(profile => ({ label: profile.name, profile })), { placeHolder: localize('pick profile', "Select Settings Profile") });
if (pick) {
- await userDataProfileManagementService.removeProfile(pick.profile);
+ try {
+ await userDataProfileManagementService.removeProfile(pick.profile);
+ } catch (error) {
+ notificationService.error(error);
+ }
}
}
}
@@ -196,14 +202,14 @@ registerAction2(class ExportProfileAction extends Action2 {
original: 'Export Settings Profile...'
},
category: PROFILES_CATEGORY,
- f1: true,
- precondition: PROFILES_ENABLEMENT_CONTEXT,
menu: [
{
id: ManageProfilesSubMenu,
group: '3_import_export_profiles',
when: PROFILES_ENABLEMENT_CONTEXT,
order: 1
+ }, {
+ id: MenuId.CommandPalette
}
]
});
@@ -241,14 +247,14 @@ registerAction2(class ImportProfileAction extends Action2 {
original: 'Import Settings Profile...'
},
category: PROFILES_CATEGORY,
- f1: true,
- precondition: PROFILES_ENABLEMENT_CONTEXT,
menu: [
{
id: ManageProfilesSubMenu,
group: '3_import_export_profiles',
when: PROFILES_ENABLEMENT_CONTEXT,
order: 2
+ }, {
+ id: MenuId.CommandPalette
}
]
});
@@ -260,6 +266,19 @@ registerAction2(class ImportProfileAction extends Action2 {
const fileService = accessor.get(IFileService);
const requestService = accessor.get(IRequestService);
const userDataProfileImportExportService = accessor.get(IUserDataProfileImportExportService);
+ const dialogService = accessor.get(IDialogService);
+ const contextKeyService = accessor.get(IContextKeyService);
+
+ const isSettingProfilesEnabled = contextKeyService.contextMatchesRules(PROFILES_ENABLEMENT_CONTEXT);
+
+ if (!isSettingProfilesEnabled) {
+ if (!(await dialogService.confirm({
+ title: localize('import profile title', "Import Settings from a Profile"),
+ message: localize('confiirmation message', "This will replace your current settings. Are you sure you want to continue?"),
+ })).confirmed) {
+ return;
+ }
+ }
const disposables = new DisposableStore();
const quickPick = disposables.add(quickInputService.createQuickPick());
@@ -278,7 +297,11 @@ registerAction2(class ImportProfileAction extends Action2 {
quickPick.hide();
const profile = quickPick.selectedItems[0].description ? await this.getProfileFromURL(quickPick.value, requestService) : await this.getProfileFromFileSystem(fileDialogService, fileService);
if (profile) {
- await userDataProfileImportExportService.importProfile(profile);
+ if (isSettingProfilesEnabled) {
+ await userDataProfileImportExportService.importProfile(profile);
+ } else {
+ await userDataProfileImportExportService.setProfile(profile);
+ }
}
}));
disposables.add(quickPick.onDidHide(() => disposables.dispose()));
diff --git a/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html b/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html
index ec2b7281a65..6469cf31e74 100644
--- a/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html
+++ b/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html
@@ -919,14 +919,21 @@
sandboxRules.add('allow-forms');
}
newFrame.setAttribute('sandbox', Array.from(sandboxRules).join(' '));
- if (!isFirefox) {
- newFrame.setAttribute('allow', options.allowScripts ? 'clipboard-read; clipboard-write;' : '');
+
+ const allowRules = ['cross-origin-isolated;']
+ if(!isFirefox && options.allowScripts) {
+ allowRules.push('clipboard-read;','clipboard-write;')
}
+ newFrame.setAttribute('allow', allowRules.join(' '));
// We should just be able to use srcdoc, but I wasn't
// seeing the service worker applying properly.
// Fake load an empty on the correct origin and then write real html
// into it to get around this.
- newFrame.src = `./fake.html?id=${ID}`;
+ const fakeUrlParams = new URLSearchParams({id: ID});
+ if(globalThis.crossOriginIsolated) {
+ fakeUrlParams.set('vscode-coi', '3') /*COOP+COEP*/
+ }
+ newFrame.src = `./fake.html?${fakeUrlParams.toString()}`;
newFrame.style.cssText = 'display: block; margin: 0; overflow: hidden; position: absolute; width: 100%; height: 100%; visibility: hidden';
document.body.appendChild(newFrame);
diff --git a/src/vs/workbench/contrib/webview/browser/pre/index.html b/src/vs/workbench/contrib/webview/browser/pre/index.html
index 326a076c677..965b90ace22 100644
--- a/src/vs/workbench/contrib/webview/browser/pre/index.html
+++ b/src/vs/workbench/contrib/webview/browser/pre/index.html
@@ -5,7 +5,7 @@
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy"
- content="default-src 'none'; script-src 'sha256-v9xEHcwDE5dc/lU7HYs5bG3LpPWGmQe0w/Vz6kmdd60=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
+ content="default-src 'none'; script-src 'sha256-vGloSX/Mg/JYMjFOA5bYxbKTao1iYLW/tlq9ME/cEOo=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
<!-- Disable pinch zooming -->
<meta name="viewport"
@@ -920,14 +920,21 @@
sandboxRules.add('allow-forms');
}
newFrame.setAttribute('sandbox', Array.from(sandboxRules).join(' '));
- if (!isFirefox) {
- newFrame.setAttribute('allow', options.allowScripts ? 'clipboard-read; clipboard-write;' : '');
+
+ const allowRules = ['cross-origin-isolated;']
+ if(!isFirefox && options.allowScripts) {
+ allowRules.push('clipboard-read;','clipboard-write;')
}
+ newFrame.setAttribute('allow', allowRules.join(' '));
// We should just be able to use srcdoc, but I wasn't
// seeing the service worker applying properly.
// Fake load an empty on the correct origin and then write real html
// into it to get around this.
- newFrame.src = `./fake.html?id=${ID}`;
+ const fakeUrlParams = new URLSearchParams({id: ID});
+ if(globalThis.crossOriginIsolated) {
+ fakeUrlParams.set('vscode-coi', '3') /*COOP+COEP*/
+ }
+ newFrame.src = `./fake.html?${fakeUrlParams.toString()}`;
newFrame.style.cssText = 'display: block; margin: 0; overflow: hidden; position: absolute; width: 100%; height: 100%; visibility: hidden';
document.body.appendChild(newFrame);
diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts
index 77aaf6683f9..fcc0777e76a 100644
--- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts
+++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts
@@ -474,9 +474,14 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD
element.name = this.id;
element.className = `webview ${options.customClasses || ''}`;
element.sandbox.add('allow-scripts', 'allow-same-origin', 'allow-forms', 'allow-pointer-lock', 'allow-downloads');
+
+ const allowRules = ['cross-origin-isolated;'];
if (!isFirefox) {
- element.setAttribute('allow', 'clipboard-read; clipboard-write;');
+ allowRules.push('clipboard-read;', 'clipboard-write;');
+ element.setAttribute('allow', 'clipboard-read; clipboard-write; cross-origin-isolated;');
}
+ element.setAttribute('allow', allowRules.join(' '));
+
element.style.border = 'none';
element.style.width = '100%';
element.style.height = '100%';
@@ -508,6 +513,10 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD
params.purpose = options.purpose;
}
+ if (globalThis.crossOriginIsolated) {
+ params['vscode-coi'] = '3'; /*COOP+COEP*/
+ }
+
const queryString = new URLSearchParams(params).toString();
// Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1754872
diff --git a/src/vs/workbench/contrib/webview/electron-sandbox/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-sandbox/webviewElement.ts
index 692eeb591ba..c119358978c 100644
--- a/src/vs/workbench/contrib/webview/electron-sandbox/webviewElement.ts
+++ b/src/vs/workbench/contrib/webview/electron-sandbox/webviewElement.ts
@@ -90,6 +90,13 @@ export class ElectronWebviewElement extends WebviewElement {
}
}
+ override dispose(): void {
+ // Make sure keyboard handler knows it closed (#71800)
+ this._webviewKeyboardHandler.didBlur();
+
+ super.dispose();
+ }
+
protected override webviewContentEndpoint(iframeId: string): string {
return `${Schemas.vscodeWebview}://${iframeId}`;
}
diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts
index f119f79c69d..497dfb1165c 100644
--- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts
+++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts
@@ -70,6 +70,7 @@ import { Codicon } from 'vs/base/common/codicons';
import { restoreWalkthroughsConfigurationKey, RestoreWalkthroughsConfigurationValue } from 'vs/workbench/contrib/welcomeGettingStarted/browser/startupPage';
import { GettingStartedDetailsRenderer } from 'vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedDetailsRenderer';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
+import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
const SLIDE_TRANSITION_TIME_MS = 250;
const configurationKey = 'workbench.startupEditor';
@@ -968,7 +969,7 @@ export class GettingStartedPage extends EditorPane {
if (category.isFeatured) {
reset(featuredBadge, $('.featured', {}, $('span.featured-icon.codicon.codicon-star-empty')));
- reset(descriptionContent, category.description);
+ reset(descriptionContent, ...renderLabelWithIcons(category.description));
}
return $('button.getting-started-category' + (category.isFeatured ? '.featured' : ''),
@@ -1237,7 +1238,7 @@ export class GettingStartedPage extends EditorPane {
this.iconWidgetFor(category),
$('.category-description-container', {},
$('h2.category-title.max-lines-3', { 'x-category-title-for': category.id }, category.title),
- $('.category-description.description.max-lines-3', { 'x-category-description-for': category.id }, category.description)));
+ $('.category-description.description.max-lines-3', { 'x-category-description-for': category.id }, ...renderLabelWithIcons(category.description))));
const stepListContainer = $('.step-list-container');
diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts
index 74b541681a6..dcbddfc4097 100644
--- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts
+++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts
@@ -691,8 +691,8 @@ registerAction2(class extends Action2 {
constructor() {
super({
id: 'resetGettingStartedProgress',
- category: 'Developer',
- title: 'Reset Welcome Page Walkthrough Progress',
+ category: { original: 'Developer', value: localize('developer', "Developer") },
+ title: { original: 'Reset Welcome Page Walkthrough Progress', value: localize('resetWelcomePageWalkthroughProgress', "Reset Welcome Page Walkthrough Progress") },
f1: true
});
}
diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css b/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css
index eb269e524fe..60016371f6d 100644
--- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css
+++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css
@@ -284,6 +284,11 @@
margin-left: 28px;
}
+.monaco-workbench .part.editor>.content .gettingStartedContainer .gettingStartedSlide .getting-started-category .description-content > .codicon {
+ padding-right: 1px;
+ font-size: 16px;
+}
+
.monaco-workbench .part.editor>.content .gettingStartedContainer .gettingStartedSlide .getting-started-category .description-content:not(:empty){
margin-bottom: 8px;
}
@@ -368,7 +373,7 @@
flex: 150px 1 1000
}
-.monaco-workbench .part.editor>.content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-category .codicon {
+.monaco-workbench .part.editor>.content .gettingStartedContainer .gettingStartedSlideDetails .gettingStartedDetailsContent>.getting-started-category>.codicon-getting-started-setup {
margin-right: 8px;
font-size: 28px;
}
diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts
index 15bcde982e2..f105e0ce308 100644
--- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts
+++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts
@@ -237,7 +237,13 @@ import { ModifierKeyEmitter } from 'vs/base/browser/dom';
'scope': ConfigurationScope.APPLICATION,
'description': localize('window.clickThroughInactive', "If enabled, clicking on an inactive window will both activate the window and trigger the element under the mouse if it is clickable. If disabled, clicking anywhere on an inactive window will activate it only and a second click is required on the element."),
'included': isMacintosh
- }
+ },
+ 'window.experimental.useSandbox': {
+ type: 'boolean',
+ description: localize('experimentalUseSandbox', "Experimental: When enabled, the window will have sandbox mode enabled via Electron API."),
+ default: false,
+ ignoreSync: true
+ },
}
});
diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts
index 86706f54945..4986c87d42a 100644
--- a/src/vs/workbench/electron-sandbox/desktop.main.ts
+++ b/src/vs/workbench/electron-sandbox/desktop.main.ts
@@ -56,6 +56,7 @@ import { PolicyChannelClient } from 'vs/platform/policy/common/policyIpc';
import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy';
import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService';
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
+import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals';
export class DesktopMain extends Disposable {
@@ -148,12 +149,9 @@ export class DesktopMain extends Disposable {
//
// NOTE: Please do NOT register services here. Use `registerSingleton()`
// from `workbench.common.main.ts` if the service is shared between
- // desktop and web or `workbench.sandbox.main.ts` if the service
+ // desktop and web or `workbench.desktop.main.ts` if the service
// is desktop only.
//
- // DO NOT add services to `workbench.desktop.main.ts`, always add
- // to `workbench.sandbox.main.ts` to support our Electron sandbox
- //
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
@@ -187,6 +185,9 @@ export class DesktopMain extends Disposable {
if (logService.getLevel() === LogLevel.Trace) {
logService.trace('workbench#open(): with configuration', safeStringify(this.configuration));
}
+ if (process.sandboxed) {
+ logService.info('Electron sandbox mode is enabled!');
+ }
// Shared Process
const sharedProcessService = new SharedProcessService(this.configuration.windowId, logService);
@@ -205,12 +206,9 @@ export class DesktopMain extends Disposable {
//
// NOTE: Please do NOT register services here. Use `registerSingleton()`
// from `workbench.common.main.ts` if the service is shared between
- // desktop and web or `workbench.sandbox.main.ts` if the service
+ // desktop and web or `workbench.desktop.main.ts` if the service
// is desktop only.
//
- // DO NOT add services to `workbench.desktop.main.ts`, always add
- // to `workbench.sandbox.main.ts` to support our Electron sandbox
- //
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
@@ -241,21 +239,18 @@ export class DesktopMain extends Disposable {
serviceCollection.set(IUriIdentityService, uriIdentityService);
// User Data Profiles
- const userDataProfilesService = new UserDataProfilesNativeService(this.configuration.profiles.all, mainProcessService, environmentService, fileService, logService);
+ const userDataProfilesService = new UserDataProfilesNativeService(this.configuration.profiles.all, mainProcessService, environmentService);
serviceCollection.set(IUserDataProfilesService, userDataProfilesService);
- const userDataProfileService = new UserDataProfileService(reviveProfile(this.configuration.profiles.current, userDataProfilesService.profilesHome.scheme));
+ const userDataProfileService = new UserDataProfileService(reviveProfile(this.configuration.profiles.current, userDataProfilesService.profilesHome.scheme), userDataProfilesService);
serviceCollection.set(IUserDataProfileService, userDataProfileService);
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
// NOTE: Please do NOT register services here. Use `registerSingleton()`
// from `workbench.common.main.ts` if the service is shared between
- // desktop and web or `workbench.sandbox.main.ts` if the service
+ // desktop and web or `workbench.desktop.main.ts` if the service
// is desktop only.
//
- // DO NOT add services to `workbench.desktop.main.ts`, always add
- // to `workbench.sandbox.main.ts` to support our Electron sandbox
- //
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
@@ -306,12 +301,9 @@ export class DesktopMain extends Disposable {
//
// NOTE: Please do NOT register services here. Use `registerSingleton()`
// from `workbench.common.main.ts` if the service is shared between
- // desktop and web or `workbench.sandbox.main.ts` if the service
+ // desktop and web or `workbench.desktop.main.ts` if the service
// is desktop only.
//
- // DO NOT add services to `workbench.desktop.main.ts`, always add
- // to `workbench.sandbox.main.ts` to support our Electron sandbox
- //
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts
index 571cd3a5f0f..af2e33d72d2 100644
--- a/src/vs/workbench/electron-sandbox/window.ts
+++ b/src/vs/workbench/electron-sandbox/window.ts
@@ -68,8 +68,6 @@ import { dirname } from 'vs/base/common/resources';
export class NativeWindow extends Disposable {
- private static REMEMBER_PROXY_CREDENTIALS_KEY = 'window.rememberProxyCredentials';
-
private touchBarMenu: IMenu | undefined;
private readonly touchBarDisposables = this._register(new DisposableStore());
private lastInstalledTouchedBar: ICommandAction[][] | undefined;
@@ -215,7 +213,8 @@ export class NativeWindow extends Disposable {
// Proxy Login Dialog
ipcRenderer.on('vscode:openProxyAuthenticationDialog', async (event: unknown, payload: { authInfo: AuthInfo; username?: string; password?: string; replyChannel: string }) => {
- const rememberCredentials = this.storageService.getBoolean(NativeWindow.REMEMBER_PROXY_CREDENTIALS_KEY, StorageScope.APPLICATION);
+ const rememberCredentialsKey = 'window.rememberProxyCredentials';
+ const rememberCredentials = this.storageService.getBoolean(rememberCredentialsKey, StorageScope.APPLICATION);
const result = await this.dialogService.input(Severity.Warning, localize('proxyAuthRequired', "Proxy Authentication Required"),
[
localize({ key: 'loginButton', comment: ['&& denotes a mnemonic'] }, "&&Log In"),
@@ -245,9 +244,9 @@ export class NativeWindow extends Disposable {
// Update state based on checkbox
if (result.checkboxChecked) {
- this.storageService.store(NativeWindow.REMEMBER_PROXY_CREDENTIALS_KEY, true, StorageScope.APPLICATION, StorageTarget.MACHINE);
+ this.storageService.store(rememberCredentialsKey, true, StorageScope.APPLICATION, StorageTarget.MACHINE);
} else {
- this.storageService.remove(NativeWindow.REMEMBER_PROXY_CREDENTIALS_KEY, StorageScope.APPLICATION);
+ this.storageService.remove(rememberCredentialsKey, StorageScope.APPLICATION);
}
// Reply back to main side with credentials
@@ -286,7 +285,7 @@ export class NativeWindow extends Disposable {
const file = EditorResourceAccessor.getOriginalUri(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY, filterByScheme: Schemas.file });
// Represented Filename
- this.updateRepresentedFilename(file?.fsPath);
+ this.nativeHostService.setRepresentedFilename(file?.fsPath ?? '');
// Custom title menu
this.provideCustomTitleContextMenu(file?.fsPath);
@@ -573,10 +572,6 @@ export class NativeWindow extends Disposable {
}
}
- private updateRepresentedFilename(filePath: string | undefined): void {
- this.nativeHostService.setRepresentedFilename(filePath ? filePath : '');
- }
-
private provideCustomTitleContextMenu(filePath: string | undefined): void {
// Clear old menu
diff --git a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts
index c78225da320..001cf0f3815 100644
--- a/src/vs/workbench/services/actions/common/menusExtensionPoint.ts
+++ b/src/vs/workbench/services/actions/common/menusExtensionPoint.ts
@@ -7,7 +7,6 @@ import { localize } from 'vs/nls';
import { isFalsyOrWhitespace } from 'vs/base/common/strings';
import * as resources from 'vs/base/common/resources';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
-import { forEach } from 'vs/base/common/collections';
import { IExtensionPointUser, ExtensionMessageCollector, ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { MenuId, MenuRegistry, IMenuItem, ISubmenuItem } from 'vs/platform/actions/common/actions';
@@ -686,50 +685,51 @@ submenusExtensionPoint.setHandler(extensions => {
for (const extension of extensions) {
const { value, collector } = extension;
- forEach(value, entry => {
- if (!schema.isValidSubmenu(entry.value, collector)) {
+ for (const [, submenuInfo] of Object.entries(value)) {
+
+ if (!schema.isValidSubmenu(submenuInfo, collector)) {
return;
}
- if (!entry.value.id) {
- collector.warn(localize('submenuId.invalid.id', "`{0}` is not a valid submenu identifier", entry.value.id));
+ if (!submenuInfo.id) {
+ collector.warn(localize('submenuId.invalid.id', "`{0}` is not a valid submenu identifier", submenuInfo.id));
return;
}
- if (_submenus.has(entry.value.id)) {
- collector.info(localize('submenuId.duplicate.id', "The `{0}` submenu was already previously registered.", entry.value.id));
+ if (_submenus.has(submenuInfo.id)) {
+ collector.info(localize('submenuId.duplicate.id', "The `{0}` submenu was already previously registered.", submenuInfo.id));
return;
}
- if (!entry.value.label) {
- collector.warn(localize('submenuId.invalid.label', "`{0}` is not a valid submenu label", entry.value.label));
+ if (!submenuInfo.label) {
+ collector.warn(localize('submenuId.invalid.label', "`{0}` is not a valid submenu label", submenuInfo.label));
return;
}
let absoluteIcon: { dark: URI; light?: URI } | ThemeIcon | undefined;
- if (entry.value.icon) {
- if (typeof entry.value.icon === 'string') {
- absoluteIcon = ThemeIcon.fromString(entry.value.icon) || { dark: resources.joinPath(extension.description.extensionLocation, entry.value.icon) };
+ if (submenuInfo.icon) {
+ if (typeof submenuInfo.icon === 'string') {
+ absoluteIcon = ThemeIcon.fromString(submenuInfo.icon) || { dark: resources.joinPath(extension.description.extensionLocation, submenuInfo.icon) };
} else {
absoluteIcon = {
- dark: resources.joinPath(extension.description.extensionLocation, entry.value.icon.dark),
- light: resources.joinPath(extension.description.extensionLocation, entry.value.icon.light)
+ dark: resources.joinPath(extension.description.extensionLocation, submenuInfo.icon.dark),
+ light: resources.joinPath(extension.description.extensionLocation, submenuInfo.icon.light)
};
}
}
const item: IRegisteredSubmenu = {
- id: new MenuId(`api:${entry.value.id}`),
- label: entry.value.label,
+ id: MenuId.for(`api:${submenuInfo.id}`),
+ label: submenuInfo.label,
icon: absoluteIcon
};
- _submenus.set(entry.value.id, item);
- });
+ _submenus.set(submenuInfo.id, item);
+ }
}
});
const _apiMenusByKey = new Map(Iterable.map(Iterable.from(apiMenus), menu => ([menu.key, menu])));
const _menuRegistrations = new DisposableStore();
-const _submenuMenuItems = new Map<number /* menu id */, Set<number /* submenu id */>>();
+const _submenuMenuItems = new Map<string /* menu id */, Set<string /* submenu id */>>();
const menusExtensionPoint = ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: (schema.IUserFriendlyMenuItem | schema.IUserFriendlySubmenuItem)[] }>({
extensionPoint: 'menus',
@@ -748,19 +748,19 @@ menusExtensionPoint.setHandler(extensions => {
for (const extension of extensions) {
const { value, collector } = extension;
- forEach(value, entry => {
- if (!schema.isValidItems(entry.value, collector)) {
- return;
+ for (const entry of Object.entries(value)) {
+ if (!schema.isValidItems(entry[1], collector)) {
+ continue;
}
- let menu = _apiMenusByKey.get(entry.key);
+ let menu = _apiMenusByKey.get(entry[0]);
if (!menu) {
- const submenu = _submenus.get(entry.key);
+ const submenu = _submenus.get(entry[0]);
if (submenu) {
menu = {
- key: entry.key,
+ key: entry[0],
id: submenu.id,
description: ''
};
@@ -768,16 +768,16 @@ menusExtensionPoint.setHandler(extensions => {
}
if (!menu) {
- collector.info(localize('menuId.invalid', "`{0}` is not a valid menu identifier", entry.key));
- return;
+ collector.info(localize('menuId.invalid', "`{0}` is not a valid menu identifier", entry[0]));
+ continue;
}
if (menu.proposed && !isProposedApiEnabled(extension.description, menu.proposed)) {
- collector.error(localize('proposedAPI.invalid', "{0} is a proposed menu identifier. It requires 'package.json#enabledApiProposals: [\"{1}\"]' and is only available when running out of dev or with the following command line switch: --enable-proposed-api {2}", entry.key, menu.proposed, extension.description.identifier.value));
- return;
+ collector.error(localize('proposedAPI.invalid', "{0} is a proposed menu identifier. It requires 'package.json#enabledApiProposals: [\"{1}\"]' and is only available when running out of dev or with the following command line switch: --enable-proposed-api {2}", entry[0], menu.proposed, extension.description.identifier.value));
+ continue;
}
- for (const menuItem of entry.value) {
+ for (const menuItem of entry[1]) {
let item: IMenuItem | ISubmenuItem;
if (schema.isMenuItem(menuItem)) {
@@ -817,7 +817,7 @@ menusExtensionPoint.setHandler(extensions => {
}
if (submenuRegistrations.has(submenu.id.id)) {
- collector.warn(localize('submenuItem.duplicate', "The `{0}` submenu was already contributed to the `{1}` menu.", menuItem.submenu, entry.key));
+ collector.warn(localize('submenuItem.duplicate', "The `{0}` submenu was already contributed to the `{1}` menu.", menuItem.submenu, entry[0]));
continue;
}
@@ -839,7 +839,7 @@ menusExtensionPoint.setHandler(extensions => {
item.when = ContextKeyExpr.deserialize(menuItem.when);
items.push({ id: menu.id, item });
}
- });
+ }
}
_menuRegistrations.add(MenuRegistry.appendMenuItems(items));
diff --git a/src/vs/workbench/services/clipboard/electron-sandbox/clipboardService.ts b/src/vs/workbench/services/clipboard/electron-sandbox/clipboardService.ts
index 8e3f25df564..326d09c3a37 100644
--- a/src/vs/workbench/services/clipboard/electron-sandbox/clipboardService.ts
+++ b/src/vs/workbench/services/clipboard/electron-sandbox/clipboardService.ts
@@ -56,11 +56,11 @@ export class NativeClipboardService implements IClipboardService {
return this.nativeHostService.hasClipboard(NativeClipboardService.FILE_FORMAT);
}
- private resourcesToBuffer(resources: URI[]): Uint8Array {
- return VSBuffer.fromString(resources.map(r => r.toString()).join('\n')).buffer;
+ private resourcesToBuffer(resources: URI[]): VSBuffer {
+ return VSBuffer.fromString(resources.map(r => r.toString()).join('\n'));
}
- private bufferToResources(buffer: Uint8Array): URI[] {
+ private bufferToResources(buffer: VSBuffer): URI[] {
if (!buffer) {
return [];
}
diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts
index 72a1946338d..bba9911a158 100644
--- a/src/vs/workbench/services/configuration/browser/configurationService.ts
+++ b/src/vs/workbench/services/configuration/browser/configurationService.ts
@@ -26,7 +26,7 @@ import { JSONEditingService } from 'vs/workbench/services/configuration/common/j
import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema';
import { mark } from 'vs/base/common/performance';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
-import { IFileService } from 'vs/platform/files/common/files';
+import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
@@ -35,7 +35,7 @@ import { toErrorMessage } from 'vs/base/common/errorMessage';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust';
import { delta, distinct } from 'vs/base/common/arrays';
-import { forEach, IStringDictionary } from 'vs/base/common/collections';
+import { IStringDictionary } from 'vs/base/common/collections';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService';
import { isUndefined } from 'vs/base/common/types';
@@ -43,6 +43,8 @@ import { localize } from 'vs/nls';
import { DidChangeUserDataProfileEvent, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy';
import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
+import { updateIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge';
+import { VSBuffer } from 'vs/base/common/buffer';
function getLocalUserConfigurationScopes(userDataProfile: IUserDataProfile, hasRemote: boolean): ConfigurationScope[] | undefined {
return userDataProfile.isDefault
@@ -711,20 +713,44 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat
}
private onUserDataProfileChanged(e: DidChangeUserDataProfileEvent): void {
- const promises: Promise<ConfigurationModel>[] = [];
- promises.push(this.localUserConfiguration.reset(e.profile.settingsResource, e.profile.tasksResource, getLocalUserConfigurationScopes(e.profile, !!this.remoteUserConfiguration)));
- if (e.previous.isDefault !== e.profile.isDefault) {
- this.createApplicationConfiguration();
- if (this.applicationConfiguration) {
- promises.push(this.reloadApplicationConfiguration(true));
- }
- }
e.join((async () => {
+ if (e.preserveData) {
+ await Promise.all([
+ this.copyProfileSettings(e.previous.settingsResource, e.profile.settingsResource),
+ this.fileService.copy(e.previous.tasksResource, e.profile.tasksResource)
+ ]);
+ }
+ const promises: Promise<ConfigurationModel>[] = [];
+ promises.push(this.localUserConfiguration.reset(e.profile.settingsResource, e.profile.tasksResource, getLocalUserConfigurationScopes(e.profile, !!this.remoteUserConfiguration)));
+ if (e.previous.isDefault !== e.profile.isDefault) {
+ this.createApplicationConfiguration();
+ if (this.applicationConfiguration) {
+ promises.push(this.reloadApplicationConfiguration(true));
+ }
+ }
const [localUser, application] = await Promise.all(promises);
await this.loadConfiguration(application ?? this._configuration.applicationConfiguration, localUser, this._configuration.remoteUserConfiguration);
})());
}
+ private async copyProfileSettings(from: URI, to: URI): Promise<void> {
+ let fromContent: string | undefined;
+ try {
+ fromContent = (await this.fileService.readFile(from)).value.toString();
+ } catch (error) {
+ if ((<FileOperationError>error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) {
+ throw error;
+ }
+ }
+ if (!fromContent) {
+ return;
+ }
+ const allSettings = Registry.as<IConfigurationRegistry>(Extensions.Configuration).getConfigurationProperties();
+ const applicationSettings = Object.keys(allSettings).filter(key => allSettings[key]?.scope === ConfigurationScope.APPLICATION);
+ const toContent = updateIgnoredSettings(fromContent, '{}', applicationSettings, {});
+ await this.fileService.writeFile(to, VSBuffer.fromString(toContent));
+ }
+
private onDefaultConfigurationChanged(configurationModel: ConfigurationModel, properties?: string[]): void {
if (this.workspace) {
const previousData = this._configuration.toData();
@@ -1212,7 +1238,7 @@ class RegisterConfigurationSchemasContribution extends Disposable implements IWo
}
const result: IStringDictionary<IConfigurationPropertySchema> = {};
- forEach(properties, ({ key, value }) => {
+ Object.entries(properties).forEach(([key, value]) => {
if (!value.restricted) {
result[key] = value;
}
diff --git a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts
index 243add76058..867ff2532bf 100644
--- a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts
+++ b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts
@@ -110,13 +110,14 @@ suite('ConfigurationEditingService', () => {
environmentService = TestEnvironmentService;
environmentService.policyFile = joinPath(workspaceFolder, 'policies.json');
instantiationService.stub(IEnvironmentService, environmentService);
- const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, logService));
- userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile);
+ const uriIdentityService = new UriIdentityService(fileService);
+ const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService));
+ userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService);
const remoteAgentService = disposables.add(instantiationService.createInstance(RemoteAgentService, null));
disposables.add(fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, logService))));
instantiationService.stub(IFileService, fileService);
instantiationService.stub(IRemoteAgentService, remoteAgentService);
- workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new FilePolicyService(environmentService.policyFile, fileService, logService)));
+ workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, uriIdentityService, new NullLogService(), new FilePolicyService(environmentService.policyFile, fileService, logService)));
await workspaceService.initialize({
id: hash(workspaceFolder.toString()).toString(16),
uri: workspaceFolder
diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts
index 3a1d3a6ca2b..fb05bed96f1 100644
--- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts
+++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts
@@ -50,6 +50,7 @@ import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService';
import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler';
import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService';
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
+import { TasksSchemaProperties } from 'vs/workbench/contrib/tasks/common/tasks';
function convertToWorkspacePayload(folder: URI): ISingleFolderWorkspaceIdentifier {
return {
@@ -85,8 +86,9 @@ suite('WorkspaceContextService - Folder', () => {
const environmentService = TestEnvironmentService;
fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService())));
- const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, logService);
- testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile), userDataProfilesService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), new UriIdentityService(fileService), new NullLogService(), new NullPolicyService()));
+ const uriIdentityService = new UriIdentityService(fileService);
+ const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService);
+ testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService()));
await (<WorkspaceService>testObject).initialize(convertToWorkspacePayload(folder));
});
@@ -126,8 +128,9 @@ suite('WorkspaceContextService - Folder', () => {
const environmentService = TestEnvironmentService;
fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService())));
- const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, logService);
- const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile), userDataProfilesService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), new UriIdentityService(fileService), new NullLogService(), new NullPolicyService()));
+ const uriIdentityService = new UriIdentityService(fileService);
+ const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService);
+ const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService()));
await (<WorkspaceService>testObject).initialize(convertToWorkspacePayload(folder));
const actual = testObject.getWorkspaceFolder(joinPath(folder, 'a'));
@@ -147,8 +150,9 @@ suite('WorkspaceContextService - Folder', () => {
const environmentService = TestEnvironmentService;
fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService())));
- const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, logService);
- const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile), userDataProfilesService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), new UriIdentityService(fileService), new NullLogService(), new NullPolicyService()));
+ const uriIdentityService = new UriIdentityService(fileService);
+ const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService);
+ const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService()));
await (<WorkspaceService>testObject).initialize(convertToWorkspacePayload(folder));
@@ -195,8 +199,9 @@ suite('WorkspaceContextService - Workspace', () => {
const remoteAgentService = disposables.add(instantiationService.createInstance(RemoteAgentService, null));
instantiationService.stub(IRemoteAgentService, remoteAgentService);
fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService())));
- const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, logService));
- testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile), userDataProfilesService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService()));
+ const uriIdentityService = new UriIdentityService(fileService);
+ const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService));
+ testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, remoteAgentService, uriIdentityService, new NullLogService(), new NullPolicyService()));
instantiationService.stub(IWorkspaceContextService, testObject);
instantiationService.stub(IConfigurationService, testObject);
@@ -254,8 +259,9 @@ suite('WorkspaceContextService - Workspace Editing', () => {
const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null);
instantiationService.stub(IRemoteAgentService, remoteAgentService);
fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService())));
- const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, logService));
- testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile), userDataProfilesService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService()));
+ const uriIdentityService = new UriIdentityService(fileService);
+ const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService));
+ testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, remoteAgentService, uriIdentityService, new NullLogService(), new NullPolicyService()));
instantiationService.stub(IFileService, fileService);
instantiationService.stub(IWorkspaceContextService, testObject);
@@ -498,9 +504,10 @@ suite('WorkspaceService - Initialization', () => {
const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null);
instantiationService.stub(IRemoteAgentService, remoteAgentService);
fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService())));
- const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, logService));
- userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile));
- testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService()));
+ const uriIdentityService = new UriIdentityService(fileService);
+ const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService));
+ userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService));
+ testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, uriIdentityService, new NullLogService(), new NullPolicyService()));
instantiationService.stub(IFileService, fileService);
instantiationService.stub(IWorkspaceContextService, testObject);
instantiationService.stub(IConfigurationService, testObject);
@@ -758,9 +765,10 @@ suite('WorkspaceConfigurationService - Folder', () => {
const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null);
instantiationService.stub(IRemoteAgentService, remoteAgentService);
fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService())));
- const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, logService));
- userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile));
- workspaceService = testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new FilePolicyService(environmentService.policyFile, fileService, logService)));
+ const uriIdentityService = new UriIdentityService(fileService);
+ const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService));
+ userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService));
+ workspaceService = testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, uriIdentityService, new NullLogService(), new FilePolicyService(environmentService.policyFile, fileService, logService)));
instantiationService.stub(IFileService, fileService);
instantiationService.stub(IWorkspaceContextService, testObject);
instantiationService.stub(IConfigurationService, testObject);
@@ -1096,7 +1104,7 @@ suite('WorkspaceConfigurationService - Folder', () => {
test('update workspace configuration', () => {
return testObject.updateValue('tasks.service.testSetting', 'value', ConfigurationTarget.WORKSPACE)
- .then(() => assert.strictEqual(testObject.getValue('tasks.service.testSetting'), 'value'));
+ .then(() => assert.strictEqual(testObject.getValue(TasksSchemaProperties.ServiceTestSetting), 'value'));
});
test('update resource configuration', () => {
@@ -1150,7 +1158,7 @@ suite('WorkspaceConfigurationService - Folder', () => {
test('update tasks configuration', () => {
return testObject.updateValue('tasks', { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] }, ConfigurationTarget.WORKSPACE)
- .then(() => assert.deepStrictEqual(testObject.getValue('tasks'), { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] }));
+ .then(() => assert.deepStrictEqual(testObject.getValue(TasksSchemaProperties.Tasks), { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] }));
});
test('update user configuration should trigger change event before promise is resolve', () => {
@@ -1424,9 +1432,10 @@ suite('WorkspaceConfigurationService - Profiles', () => {
const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null);
instantiationService.stub(IRemoteAgentService, remoteAgentService);
fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService())));
- const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, logService));
- userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(toUserDataProfile('custom', joinPath(environmentService.userRoamingDataHome, 'profiles', 'temp'))));
- workspaceService = testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new FilePolicyService(environmentService.policyFile, fileService, logService)));
+ const uriIdentityService = new UriIdentityService(fileService);
+ const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService));
+ userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(toUserDataProfile('custom', joinPath(environmentService.userRoamingDataHome, 'profiles', 'temp')), userDataProfilesService));
+ workspaceService = testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, uriIdentityService, new NullLogService(), new FilePolicyService(environmentService.policyFile, fileService, logService)));
instantiationService.stub(IFileService, fileService);
instantiationService.stub(IWorkspaceContextService, testObject);
instantiationService.stub(IConfigurationService, testObject);
@@ -1612,9 +1621,10 @@ suite('WorkspaceConfigurationService-Multiroot', () => {
const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null);
instantiationService.stub(IRemoteAgentService, remoteAgentService);
fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService())));
- const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, logService));
- userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile));
- const workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService()));
+ const uriIdentityService = new UriIdentityService(fileService);
+ const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService));
+ userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService));
+ const workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, uriIdentityService, new NullLogService(), new NullPolicyService()));
instantiationService.stub(IFileService, fileService);
instantiationService.stub(IWorkspaceContextService, workspaceService);
@@ -1994,7 +2004,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => {
};
await jsonEditingServce.write((workspaceContextService.getWorkspace().configuration!), [{ path: ['tasks'], value: expectedTasksConfiguration }], true);
await testObject.reloadConfiguration();
- const actual = testObject.getValue('tasks');
+ const actual = testObject.getValue(TasksSchemaProperties.Tasks);
assert.deepStrictEqual(actual, expectedTasksConfiguration);
});
@@ -2119,7 +2129,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => {
test('update tasks configuration in a folder', async () => {
const workspace = workspaceContextService.getWorkspace();
await testObject.updateValue('tasks', { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] }, { resource: workspace.folders[0].uri }, ConfigurationTarget.WORKSPACE_FOLDER);
- assert.deepStrictEqual(testObject.getValue('tasks', { resource: workspace.folders[0].uri }), { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] });
+ assert.deepStrictEqual(testObject.getValue(TasksSchemaProperties.Tasks, { resource: workspace.folders[0].uri }), { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] });
});
test('update launch configuration in a workspace', async () => {
@@ -2132,7 +2142,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => {
const workspace = workspaceContextService.getWorkspace();
const tasks = { 'version': '2.0.0', tasks: [{ 'label': 'myTask' }] };
await testObject.updateValue('tasks', tasks, { resource: workspace.folders[0].uri }, ConfigurationTarget.WORKSPACE, true);
- assert.deepStrictEqual(testObject.getValue('tasks'), tasks);
+ assert.deepStrictEqual(testObject.getValue(TasksSchemaProperties.Tasks), tasks);
});
test('configuration of newly added folder is available on configuration change event', async () => {
@@ -2275,9 +2285,10 @@ suite('WorkspaceConfigurationService - Remote Folder', () => {
const remoteAgentService = instantiationService.stub(IRemoteAgentService, <Partial<IRemoteAgentService>>{ getEnvironment: () => remoteEnvironmentPromise });
fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService())));
const configurationCache: IConfigurationCache = { read: () => Promise.resolve(''), write: () => Promise.resolve(), remove: () => Promise.resolve(), needsCaching: () => false };
- const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, logService));
- userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile));
- testObject = disposables.add(new WorkspaceService({ configurationCache, remoteAuthority }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService()));
+ const uriIdentityService = new UriIdentityService(fileService);
+ const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService));
+ userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService));
+ testObject = disposables.add(new WorkspaceService({ configurationCache, remoteAuthority }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, uriIdentityService, new NullLogService(), new NullPolicyService()));
instantiationService.stub(IWorkspaceContextService, testObject);
instantiationService.stub(IConfigurationService, testObject);
instantiationService.stub(IEnvironmentService, environmentService);
diff --git a/src/vs/workbench/services/configurationResolver/browser/baseConfigurationResolverService.ts b/src/vs/workbench/services/configurationResolver/browser/baseConfigurationResolverService.ts
index 059d8e46456..07053d26b4d 100644
--- a/src/vs/workbench/services/configurationResolver/browser/baseConfigurationResolverService.ts
+++ b/src/vs/workbench/services/configurationResolver/browser/baseConfigurationResolverService.ts
@@ -7,7 +7,7 @@ import * as nls from 'vs/nls';
import * as Types from 'vs/base/common/types';
import { Schemas } from 'vs/base/common/network';
import { SideBySideEditor, EditorResourceAccessor } from 'vs/workbench/common/editor';
-import { IStringDictionary, forEach } from 'vs/base/common/collections';
+import { IStringDictionary } from 'vs/base/common/collections';
import { IConfigurationService, IConfigurationOverrides, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IWorkspaceFolder, IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
@@ -157,9 +157,9 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR
if (!newMapping) {
return false;
}
- forEach(newMapping, (entry) => {
- fullMapping.set(entry.key, entry.value);
- });
+ for (const [key, value] of Object.entries(newMapping)) {
+ fullMapping.set(key, value);
+ }
return true;
}
@@ -256,20 +256,21 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR
}
}
}
- this._contributedVariables.forEach((value, contributed: string) => {
+ for (const contributed of this._contributedVariables.keys()) {
if ((variables.indexOf(contributed) < 0) && (object.indexOf('${' + contributed + '}') >= 0)) {
variables.push(contributed);
}
- });
+ }
} else if (Types.isArray(object)) {
- object.forEach(value => {
+ for (const value of object) {
this.findVariables(value, variables);
- });
+
+ }
} else if (object) {
- Object.keys(object).forEach(key => {
- const value = object[key];
+ for (const value of Object.values(object)) {
this.findVariables(value, variables);
- });
+
+ }
}
}
@@ -315,11 +316,11 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR
missingAttribute('description');
}
if (Types.isArray(info.options)) {
- info.options.forEach(pickOption => {
+ for (const pickOption of info.options) {
if (!Types.isString(pickOption) && !Types.isString(pickOption.value)) {
missingAttribute('value');
}
- });
+ }
} else {
missingAttribute('options');
}
@@ -327,7 +328,7 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR
value: string;
}
const picks = new Array<PickStringItem>();
- info.options.forEach(pickOption => {
+ for (const pickOption of info.options) {
const value = Types.isString(pickOption) ? pickOption : pickOption.value;
const label = Types.isString(pickOption) ? undefined : pickOption.label;
@@ -343,7 +344,7 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR
} else {
picks.push(item);
}
- });
+ }
const pickOptions: IPickOptions<PickStringItem> = { placeHolder: info.description, matchOnDetail: true, ignoreFocusLost: true };
return this.quickInputService.pick(picks, pickOptions, undefined).then(resolvedInput => {
if (resolvedInput) {
diff --git a/src/vs/workbench/services/editor/browser/codeEditorService.ts b/src/vs/workbench/services/editor/browser/codeEditorService.ts
index 0aaf3980f4f..492e5077def 100644
--- a/src/vs/workbench/services/editor/browser/codeEditorService.ts
+++ b/src/vs/workbench/services/editor/browser/codeEditorService.ts
@@ -24,6 +24,9 @@ export class CodeEditorService extends AbstractCodeEditorService {
@IConfigurationService private readonly configurationService: IConfigurationService,
) {
super(themeService);
+
+ this.registerCodeEditorOpenHandler(this.doOpenCodeEditor.bind(this));
+ this.registerCodeEditorOpenHandler(this.doOpenCodeEditorFromDiff.bind(this));
}
getActiveCodeEditor(): ICodeEditor | null {
@@ -44,7 +47,7 @@ export class CodeEditorService extends AbstractCodeEditorService {
return null;
}
- async openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null> {
+ private async doOpenCodeEditorFromDiff(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null> {
// Special case: If the active editor is a diff editor and the request to open originates and
// targets the modified side of it, we just apply the request there to prevent opening the modified
@@ -66,10 +69,10 @@ export class CodeEditorService extends AbstractCodeEditorService {
return targetEditor;
}
- // Open using our normal editor service
- return this.doOpenCodeEditor(input, source, sideBySide);
+ return null;
}
+ // Open using our normal editor service
private async doOpenCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null> {
// Special case: we want to detect the request to open an editor that
diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts
index aae30a8d46e..2e19d0d0cbf 100644
--- a/src/vs/workbench/services/editor/common/editorGroupsService.ts
+++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts
@@ -47,7 +47,7 @@ export const enum GroupsArrangement {
* Make the current active group consume the maximum
* amount of space possible.
*/
- MINIMIZE_OTHERS,
+ MAXIMIZE,
/**
* Size all groups evenly.
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 e4da27e5ebc..f193e7d69b0 100644
--- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts
+++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts
@@ -1661,7 +1661,7 @@ suite('EditorService', () => {
editor = await service.openEditor(input2, { pinned: true, activation: EditorActivation.ACTIVATE }, sideGroup);
assert.strictEqual(part.activeGroup, sideGroup);
- part.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS);
+ part.arrangeGroups(GroupsArrangement.MAXIMIZE);
editor = await service.openEditor(input1, { pinned: true, preserveFocus: true, activation: EditorActivation.RESTORE }, rootGroup);
assert.strictEqual(part.activeGroup, sideGroup);
});
@@ -1681,13 +1681,13 @@ suite('EditorService', () => {
assert.strictEqual(part.activeGroup, sideGroup);
assert.notStrictEqual(rootGroup, sideGroup);
- part.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS, part.activeGroup);
+ part.arrangeGroups(GroupsArrangement.MAXIMIZE, part.activeGroup);
await rootGroup.closeEditor(input2);
assert.strictEqual(part.activeGroup, sideGroup);
- assert.strictEqual(rootGroup.isMinimized, true);
- assert.strictEqual(part.activeGroup.isMinimized, false);
+ assert(!part.isGroupMaximized(rootGroup));
+ assert(part.isGroupMaximized(part.activeGroup));
});
test('active editor change / visible editor change events', async function () {
diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts
index 631b3db439e..82a2541576a 100644
--- a/src/vs/workbench/services/environment/browser/environmentService.ts
+++ b/src/vs/workbench/services/environment/browser/environmentService.ts
@@ -84,6 +84,9 @@ export class BrowserWorkbenchEnvironmentService implements IBrowserWorkbenchEnvi
get userDataSyncLogResource(): URI { return joinPath(this.logsHome, 'userDataSync.log'); }
@memoize
+ get editSessionsLogResource(): URI { return joinPath(this.logsHome, 'editSessions.log'); }
+
+ @memoize
get sync(): 'on' | 'off' | undefined { return undefined; }
@memoize
diff --git a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts
index faf1645bb6d..bc5e140d42a 100644
--- a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts
+++ b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts
@@ -39,7 +39,10 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag
import { IProductService } from 'vs/platform/product/common/productService';
import { validateExtensionManifest } from 'vs/platform/extensions/common/extensionValidator';
import Severity from 'vs/base/common/severity';
-import { IStringDictionary, forEach } from 'vs/base/common/collections';
+import { IStringDictionary } from 'vs/base/common/collections';
+import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
+import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
+import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
type GalleryExtensionInfo = { readonly id: string; preRelease?: boolean; migrateStorageFrom?: string };
type ExtensionInfo = { readonly id: string; preRelease: boolean };
@@ -83,7 +86,6 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
private readonly systemExtensionsCacheResource: URI | undefined = undefined;
private readonly customBuiltinExtensionsCacheResource: URI | undefined = undefined;
- private readonly installedExtensionsResource: URI | undefined = undefined;
private readonly resourcesAccessQueueMap = new ResourceMap<Queue<IWebExtension[]>>();
constructor(
@@ -97,11 +99,12 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
@IExtensionStorageService private readonly extensionStorageService: IExtensionStorageService,
@IStorageService private readonly storageService: IStorageService,
@IProductService private readonly productService: IProductService,
+ @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,
+ @IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
@ILifecycleService lifecycleService: ILifecycleService,
) {
super();
if (isWeb) {
- this.installedExtensionsResource = joinPath(environmentService.userRoamingDataHome, 'extensions.json');
this.systemExtensionsCacheResource = joinPath(environmentService.userRoamingDataHome, 'systemExtensionsCache.json');
this.customBuiltinExtensionsCacheResource = joinPath(environmentService.userRoamingDataHome, 'customBuiltinExtensionsCache.json');
this.registerActions();
@@ -369,7 +372,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
return this.readSystemExtensions();
}
- async scanUserExtensions(scanOptions?: ScanOptions): Promise<IScannedExtension[]> {
+ async scanUserExtensions(profileLocation?: URI, scanOptions?: ScanOptions): Promise<IScannedExtension[]> {
const extensions = new Map<string, IScannedExtension>();
// Custom builtin extensions defined through `additionalBuiltinExtensions` API
@@ -379,7 +382,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
}
// User Installed extensions
- const installedExtensions = await this.scanInstalledExtensions(scanOptions);
+ const installedExtensions = await this.scanInstalledExtensions(profileLocation, scanOptions);
for (const extension of installedExtensions) {
extensions.set(extension.identifier.id.toLowerCase(), extension);
}
@@ -408,17 +411,17 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
return result;
}
- async scanExistingExtension(extensionLocation: URI, extensionType: ExtensionType): Promise<IScannedExtension | null> {
+ async scanExistingExtension(extensionLocation: URI, extensionType: ExtensionType, profileLocation?: URI): Promise<IScannedExtension | null> {
if (extensionType === ExtensionType.System) {
const systemExtensions = await this.scanSystemExtensions();
return systemExtensions.find(e => e.location.toString() === extensionLocation.toString()) || null;
}
- const userExtensions = await this.scanUserExtensions();
+ const userExtensions = await this.scanUserExtensions(profileLocation);
return userExtensions.find(e => e.location.toString() === extensionLocation.toString()) || null;
}
- async scanMetadata(extensionLocation: URI): Promise<Metadata | undefined> {
- const extension = await this.scanExistingExtension(extensionLocation, ExtensionType.User);
+ async scanMetadata(extensionLocation: URI, profileLocation?: URI): Promise<Metadata | undefined> {
+ const extension = await this.scanExistingExtension(extensionLocation, ExtensionType.User, profileLocation);
return extension?.metadata;
}
@@ -435,21 +438,35 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
return null;
}
- async addExtensionFromGallery(galleryExtension: IGalleryExtension, metadata?: Metadata): Promise<IExtension> {
+ async addExtensionFromGallery(galleryExtension: IGalleryExtension, metadata: Metadata, profileLocation?: URI): Promise<IScannedExtension> {
const webExtension = await this.toWebExtensionFromGallery(galleryExtension, metadata);
- return this.addWebExtension(webExtension);
+ return this.addWebExtension(webExtension, profileLocation);
}
- async addExtension(location: URI, metadata?: Metadata): Promise<IExtension> {
+ async addExtension(location: URI, metadata: Metadata, profileLocation?: URI): Promise<IScannedExtension> {
const webExtension = await this.toWebExtension(location, undefined, undefined, undefined, undefined, undefined, metadata);
- return this.addWebExtension(webExtension);
+ return this.addWebExtension(webExtension, profileLocation);
}
- async removeExtension(identifier: IExtensionIdentifier, version?: string): Promise<void> {
- await this.writeInstalledExtensions(installedExtensions => installedExtensions.filter(extension => !(areSameExtensions(extension.identifier, identifier) && (version ? extension.version === version : true))));
+ async removeExtension(extension: IScannedExtension, profileLocation?: URI): Promise<void> {
+ await this.writeInstalledExtensions(profileLocation, installedExtensions => installedExtensions.filter(installedExtension => !areSameExtensions(installedExtension.identifier, extension.identifier)));
}
- private async addWebExtension(webExtension: IWebExtension): Promise<IScannedExtension> {
+ async copyExtensions(fromProfileLocation: URI, toProfileLocation: URI, filter: (extension: IScannedExtension) => boolean): Promise<void> {
+ const extensionsToCopy: IWebExtension[] = [];
+ const fromWebExtensions = await this.readInstalledExtensions(fromProfileLocation);
+ await Promise.all(fromWebExtensions.map(async webExtension => {
+ const scannedExtension = await this.toScannedExtension(webExtension, false);
+ if (filter(scannedExtension)) {
+ extensionsToCopy.push(webExtension);
+ }
+ }));
+ if (extensionsToCopy.length) {
+ await this.addToInstalledExtensions(extensionsToCopy, toProfileLocation);
+ }
+ }
+
+ private async addWebExtension(webExtension: IWebExtension, profileLocation?: URI): Promise<IScannedExtension> {
const isSystem = !!(await this.scanSystemExtensions()).find(e => areSameExtensions(e.identifier, webExtension.identifier));
const isBuiltin = !!webExtension.metadata?.isBuiltin;
const extension = await this.toScannedExtension(webExtension, isBuiltin);
@@ -473,30 +490,40 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
return customBuiltinExtensions;
});
- const installedExtensions = await this.readInstalledExtensions();
+ const installedExtensions = await this.readInstalledExtensions(profileLocation);
// Also add to installed extensions if it is installed to update its version
if (installedExtensions.some(e => areSameExtensions(e.identifier, webExtension.identifier))) {
- await this.addToInstalledExtensions(webExtension);
+ await this.addToInstalledExtensions([webExtension], profileLocation);
}
return extension;
}
// Add to installed extensions
- await this.addToInstalledExtensions(webExtension);
+ await this.addToInstalledExtensions([webExtension], profileLocation);
return extension;
}
- private async addToInstalledExtensions(webExtension: IWebExtension): Promise<void> {
- await this.writeInstalledExtensions(installedExtensions => {
+ private async addToInstalledExtensions(webExtensions: IWebExtension[], profileLocation?: URI): Promise<void> {
+ await this.writeInstalledExtensions(profileLocation, installedExtensions => {
// Remove the existing extension to avoid duplicates
- installedExtensions = installedExtensions.filter(e => !areSameExtensions(e.identifier, webExtension.identifier));
- installedExtensions.push(webExtension);
+ installedExtensions = installedExtensions.filter(installedExtension => webExtensions.some(extension => !areSameExtensions(installedExtension.identifier, extension.identifier)));
+ installedExtensions.push(...webExtensions);
return installedExtensions;
});
}
- private async scanInstalledExtensions(scanOptions?: ScanOptions): Promise<IScannedExtension[]> {
- const installedExtensions = await this.readInstalledExtensions();
+ private async scanInstalledExtensions(profileLocation?: URI, scanOptions?: ScanOptions): Promise<IScannedExtension[]> {
+ let installedExtensions = await this.readInstalledExtensions(profileLocation);
+
+ // If current profile is not a default profile, then add the application extensions to the list
+ if (this.userDataProfilesService.defaultProfile.extensionsResource && !this.uriIdentityService.extUri.isEqual(profileLocation, this.userDataProfilesService.defaultProfile.extensionsResource)) {
+ // Remove application extensions from the non default profile
+ installedExtensions = installedExtensions.filter(i => !i.metadata?.isApplicationScoped);
+ // Add application extensions from the default profile to the list
+ const defaultProfileExtensions = await this.readInstalledExtensions(this.userDataProfilesService.defaultProfile.extensionsResource);
+ installedExtensions.push(...defaultProfileExtensions.filter(i => i.metadata?.isApplicationScoped));
+ }
+
installedExtensions.sort((a, b) => a.identifier.id < b.identifier.id ? -1 : a.identifier.id > b.identifier.id ? 1 : semver.rcompare(a.version, b.version));
const result = new Map<string, IScannedExtension>();
for (const webExtension of installedExtensions) {
@@ -670,17 +697,12 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
return manifest;
}
- private async readInstalledExtensions(): Promise<IWebExtension[]> {
- await this.migratePackageNLSUris();
- return this.withWebExtensions(this.installedExtensionsResource);
- }
-
// TODO: @TylerLeonhardt/@Sandy081: Delete after 6 months
private _migratePackageNLSUrisPromise: Promise<void> | undefined;
private migratePackageNLSUris(): Promise<void> {
if (!this._migratePackageNLSUrisPromise) {
this._migratePackageNLSUrisPromise = (async () => {
- const webExtensions = await this.withWebExtensions(this.installedExtensionsResource);
+ const webExtensions = await this.withWebExtensions(this.userDataProfilesService.defaultProfile.extensionsResource);
if (webExtensions.some(e => !e.packageNLSUris && e.packageNLSUri)) {
const migratedExtensions = await Promise.all(webExtensions.map(async e => {
if (!e.packageNLSUris && e.packageNLSUri) {
@@ -691,15 +713,22 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
}
return e;
}));
- await this.withWebExtensions(this.installedExtensionsResource, () => migratedExtensions);
+ await this.withWebExtensions(this.userDataProfilesService.defaultProfile.extensionsResource, () => migratedExtensions);
}
})();
}
return this._migratePackageNLSUrisPromise;
}
- private writeInstalledExtensions(updateFn: (extensions: IWebExtension[]) => IWebExtension[]): Promise<IWebExtension[]> {
- return this.withWebExtensions(this.installedExtensionsResource, updateFn);
+ private async readInstalledExtensions(profileLocation?: URI): Promise<IWebExtension[]> {
+ if (this.uriIdentityService.extUri.isEqual(profileLocation, this.userDataProfilesService.defaultProfile.extensionsResource)) {
+ await this.migratePackageNLSUris();
+ }
+ return this.withWebExtensions(profileLocation);
+ }
+
+ private writeInstalledExtensions(profileLocation: URI | undefined, updateFn: (extensions: IWebExtension[]) => IWebExtension[]): Promise<IWebExtension[]> {
+ return this.withWebExtensions(profileLocation, updateFn);
}
private readCustomBuiltinExtensionsCache(): Promise<IWebExtension[]> {
@@ -737,7 +766,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
let packageNLSUris: Map<string, URI> | undefined;
if (e.packageNLSUris) {
packageNLSUris = new Map<string, URI>();
- forEach<UriComponents>(e.packageNLSUris, (entry) => packageNLSUris!.set(entry.key, URI.revive(entry.value)));
+ Object.entries(e.packageNLSUris).forEach(([key, value]) => packageNLSUris!.set(key, URI.revive(value)));
}
webExtensions.push({
@@ -797,7 +826,6 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
}
private registerActions(): void {
- const that = this;
this._register(registerAction2(class extends Action2 {
constructor() {
super({
@@ -809,7 +837,9 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
});
}
run(serviceAccessor: ServicesAccessor): void {
- serviceAccessor.get(IEditorService).openEditor({ resource: that.installedExtensionsResource });
+ const editorService = serviceAccessor.get(IEditorService);
+ const userDataProfileService = serviceAccessor.get(IUserDataProfileService);
+ editorService.openEditor({ resource: userDataProfileService.currentProfile.extensionsResource });
}
}));
}
diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts
index 5e3c46b1a5b..1cad71d1c3a 100644
--- a/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts
+++ b/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts
@@ -6,15 +6,14 @@
import { Event } from 'vs/base/common/event';
import { createDecorator, refineServiceDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IExtension, ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions';
-import { IExtensionManagementService, IGalleryExtension, IExtensionIdentifier, ILocalExtension, InstallOptions, InstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionResult, Metadata, InstallVSIXOptions, UninstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement';
+import { IExtensionManagementService, IGalleryExtension, ILocalExtension, InstallOptions, InstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionResult, Metadata, InstallVSIXOptions, UninstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement';
import { URI } from 'vs/base/common/uri';
import { FileAccess } from 'vs/base/common/network';
export type DidChangeProfileExtensionsEvent = { readonly added: ILocalExtension[]; readonly removed: ILocalExtension[] };
export interface IProfileAwareExtensionManagementService extends IExtensionManagementService {
- onDidChangeProfileExtensions: Event<DidChangeProfileExtensionsEvent>;
- switchExtensionsProfile(extensionsProfileResource: URI | undefined): Promise<void>;
+ readonly onDidChangeProfileExtensions: Event<DidChangeProfileExtensionsEvent>;
}
export interface IExtensionManagementServer {
@@ -159,14 +158,15 @@ export interface IWebExtensionsScannerService {
readonly _serviceBrand: undefined;
scanSystemExtensions(): Promise<IExtension[]>;
- scanUserExtensions(options?: ScanOptions): Promise<IScannedExtension[]>;
+ scanUserExtensions(profileLocation: URI | undefined, options?: ScanOptions): Promise<IScannedExtension[]>;
scanExtensionsUnderDevelopment(): Promise<IExtension[]>;
- scanExistingExtension(extensionLocation: URI, extensionType: ExtensionType): Promise<IScannedExtension | null>;
+ scanExistingExtension(extensionLocation: URI, extensionType: ExtensionType, profileLocation: URI | undefined): Promise<IScannedExtension | null>;
- addExtension(location: URI, metadata?: Metadata): Promise<IExtension>;
- addExtensionFromGallery(galleryExtension: IGalleryExtension, metadata?: Metadata): Promise<IExtension>;
- removeExtension(identifier: IExtensionIdentifier, version?: string): Promise<void>;
+ addExtension(location: URI, metadata: Metadata, profileLocation: URI | undefined): Promise<IScannedExtension>;
+ addExtensionFromGallery(galleryExtension: IGalleryExtension, metadata: Metadata, profileLocation: URI | undefined): Promise<IScannedExtension>;
+ removeExtension(extension: IScannedExtension, profileLocation: URI | undefined): Promise<void>;
+ copyExtensions(fromProfileLocation: URI, toProfileLocation: URI, filter: (extension: IScannedExtension) => boolean): Promise<void>;
- scanMetadata(extensionLocation: URI): Promise<Metadata | undefined>;
+ scanMetadata(extensionLocation: URI, profileLocation: URI | undefined): Promise<Metadata | undefined>;
scanExtensionManifest(extensionLocation: URI): Promise<IExtensionManifest | null>;
}
diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts
index 233e0bc56c2..b16029ca565 100644
--- a/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts
+++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts
@@ -7,6 +7,7 @@ import { localize } from 'vs/nls';
import { ExtensionInstallLocation, IExtensionManagementServer, IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { Schemas } from 'vs/base/common/network';
+import { Event } from 'vs/base/common/event';
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';
@@ -14,7 +15,7 @@ import { isWeb } from 'vs/base/common/platform';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { WebExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/webExtensionManagementService';
import { IExtension } from 'vs/platform/extensions/common/extensions';
-import { NativeProfileAwareExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/profileAwareExtensionManagementService';
+import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/common/extensionManagementIpc';
export class ExtensionManagementServerService implements IExtensionManagementServerService {
@@ -31,7 +32,9 @@ export class ExtensionManagementServerService implements IExtensionManagementSer
) {
const remoteAgentConnection = remoteAgentService.getConnection();
if (remoteAgentConnection) {
- const extensionManagementService = instantiationService.createInstance(NativeProfileAwareExtensionManagementService, remoteAgentConnection.getChannel<IChannel>('extensions'), undefined);
+ const extensionManagementService = new class extends ExtensionManagementChannelClient {
+ readonly onDidChangeProfileExtensions = Event.None;
+ }(remoteAgentConnection.getChannel<IChannel>('extensions'));
this.remoteExtensionManagementServer = {
id: 'remote',
extensionManagementService,
diff --git a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts
index 147936d4468..a35c50b1cd2 100644
--- a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts
+++ b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts
@@ -3,10 +3,10 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { ExtensionType, IExtension, IExtensionIdentifier, IExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions';
-import { IExtensionManagementService, ILocalExtension, IGalleryExtension, IGalleryMetadata, InstallOperation, IExtensionGalleryService, InstallOptions, Metadata, UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
+import { ExtensionIdentifier, ExtensionType, IExtension, IExtensionIdentifier, IExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions';
+import { ILocalExtension, IGalleryExtension, IGalleryMetadata, InstallOperation, IExtensionGalleryService, Metadata, ServerInstallOptions, ServerUninstallOptions, IServerExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { URI } from 'vs/base/common/uri';
-import { Event } from 'vs/base/common/event';
+import { Emitter } from 'vs/base/common/event';
import { areSameExtensions, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IProfileAwareExtensionManagementService, IScannedExtension, IWebExtensionsScannerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { ILogService } from 'vs/platform/log/common/log';
@@ -16,15 +16,17 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService';
import { IProductService } from 'vs/platform/product/common/productService';
import { isBoolean, isUndefined } from 'vs/base/common/types';
-import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService';
+import { DidChangeUserDataProfileEvent, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
+import { delta } from 'vs/base/common/arrays';
+import { compare } from 'vs/base/common/strings';
import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
-import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
-export class WebExtensionManagementService extends AbstractExtensionManagementService implements IExtensionManagementService, IProfileAwareExtensionManagementService {
+export class WebExtensionManagementService extends AbstractExtensionManagementService implements IProfileAwareExtensionManagementService, IServerExtensionManagementService {
declare readonly _serviceBrand: undefined;
- readonly onDidChangeProfileExtensions = Event.None;
+ private readonly _onDidChangeProfileExtensions = this._register(new Emitter<{ readonly added: ILocalExtension[]; readonly removed: ILocalExtension[] }>());
+ readonly onDidChangeProfileExtensions = this._onDidChangeProfileExtensions.event;
constructor(
@IExtensionGalleryService extensionGalleryService: IExtensionGalleryService,
@@ -32,12 +34,12 @@ export class WebExtensionManagementService extends AbstractExtensionManagementSe
@ILogService logService: ILogService,
@IWebExtensionsScannerService private readonly webExtensionsScannerService: IWebExtensionsScannerService,
@IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService,
- @IExtensionsProfileScannerService extensionsProfileScannerService: IExtensionsProfileScannerService,
+ @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService,
@IProductService productService: IProductService,
@IUserDataProfilesService userDataProfilesService: IUserDataProfilesService,
- @IUriIdentityService uriIdentityService: IUriIdentityService,
) {
- super(userDataProfilesService, uriIdentityService, extensionGalleryService, extensionsProfileScannerService, telemetryService, logService, productService);
+ super(extensionGalleryService, telemetryService, logService, productService, userDataProfilesService);
+ this._register(userDataProfileService.onDidChangeCurrentProfile(e => e.join(this.whenProfileChanged(e))));
}
async getTargetPlatform(): Promise<TargetPlatform> {
@@ -61,13 +63,13 @@ export class WebExtensionManagementService extends AbstractExtensionManagementSe
extensions.push(...systemExtensions);
}
if (type === undefined || type === ExtensionType.User) {
- const userExtensions = await this.webExtensionsScannerService.scanUserExtensions();
+ const userExtensions = await this.webExtensionsScannerService.scanUserExtensions(this.userDataProfileService.currentProfile.extensionsResource);
extensions.push(...userExtensions);
}
return Promise.all(extensions.map(e => toLocalExtension(e)));
}
- async install(location: URI, options: InstallOptions = {}): Promise<ILocalExtension> {
+ async install(location: URI, options: ServerInstallOptions = {}): Promise<ILocalExtension> {
this.logService.trace('ExtensionManagementService#install', location.toString());
const manifest = await this.webExtensionsScannerService.scanExtensionManifest(location);
if (!manifest) {
@@ -77,7 +79,7 @@ export class WebExtensionManagementService extends AbstractExtensionManagementSe
}
getMetadata(extension: ILocalExtension): Promise<Metadata | undefined> {
- return this.webExtensionsScannerService.scanMetadata(extension.location);
+ return this.webExtensionsScannerService.scanMetadata(extension.location, this.userDataProfileService.currentProfile.extensionsResource);
}
protected override async getCompatibleVersion(extension: IGalleryExtension, sameVersion: boolean, includePreRelease: boolean): Promise<IGalleryExtension | null> {
@@ -100,13 +102,17 @@ export class WebExtensionManagementService extends AbstractExtensionManagementSe
return local;
}
- async switchExtensionsProfile(extensionsProfileResource: URI | undefined): Promise<void> { }
-
- protected createDefaultInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallOptions): IInstallExtensionTask {
+ protected doCreateInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: ServerInstallOptions): IInstallExtensionTask {
+ if (!options.profileLocation) {
+ options = { ...options, profileLocation: this.userDataProfileService.currentProfile.extensionsResource };
+ }
return new InstallExtensionTask(manifest, extension, options, this.webExtensionsScannerService);
}
- protected createDefaultUninstallExtensionTask(extension: ILocalExtension, options: UninstallOptions): IUninstallExtensionTask {
+ protected doCreateUninstallExtensionTask(extension: ILocalExtension, options: ServerUninstallOptions): IUninstallExtensionTask {
+ if (!options.profileLocation) {
+ options = { ...options, profileLocation: this.userDataProfileService.currentProfile.extensionsResource };
+ }
return new UninstallExtensionTask(extension, options, this.webExtensionsScannerService);
}
@@ -114,6 +120,24 @@ export class WebExtensionManagementService extends AbstractExtensionManagementSe
unzip(zipLocation: URI): Promise<IExtensionIdentifier> { throw new Error('unsupported'); }
getManifest(vsix: URI): Promise<IExtensionManifest> { throw new Error('unsupported'); }
updateExtensionScope(): Promise<ILocalExtension> { throw new Error('unsupported'); }
+
+ private async whenProfileChanged(e: DidChangeUserDataProfileEvent): Promise<void> {
+ const previousProfileLocation = e.previous.extensionsResource;
+ const currentProfileLocation = e.profile.extensionsResource;
+ if (!previousProfileLocation || !currentProfileLocation) {
+ throw new Error('This should not happen');
+ }
+ if (e.preserveData) {
+ await this.webExtensionsScannerService.copyExtensions(previousProfileLocation, currentProfileLocation, e => !e.metadata?.isApplicationScoped);
+ } else {
+ const oldExtensions = await this.webExtensionsScannerService.scanUserExtensions(previousProfileLocation);
+ const newExtensions = await this.webExtensionsScannerService.scanUserExtensions(currentProfileLocation);
+ const { added, removed } = delta(oldExtensions, newExtensions, (a, b) => compare(`${ExtensionIdentifier.toKey(a.identifier.id)}@${a.manifest.version}`, `${ExtensionIdentifier.toKey(b.identifier.id)}@${b.manifest.version}`));
+ if (added.length || removed.length) {
+ this._onDidChangeProfileExtensions.fire({ added: added.map(e => toLocalExtension(e)), removed: removed.map(e => toLocalExtension(e)) });
+ }
+ }
+ }
}
function toLocalExtension(extension: IExtension): ILocalExtension {
@@ -133,7 +157,7 @@ function toLocalExtension(extension: IExtension): ILocalExtension {
};
}
-function getMetadata(options?: InstallOptions, existingExtension?: IExtension): Metadata {
+function getMetadata(options?: ServerInstallOptions, existingExtension?: IExtension): Metadata {
const metadata: Metadata = { ...((<IScannedExtension>existingExtension)?.metadata || {}) };
metadata.isMachineScoped = options?.isMachineScoped || metadata.isMachineScoped;
return metadata;
@@ -150,7 +174,7 @@ class InstallExtensionTask extends AbstractExtensionTask<{ local: ILocalExtensio
constructor(
manifest: IExtensionManifest,
private readonly extension: URI | IGalleryExtension,
- private readonly options: InstallOptions,
+ private readonly options: ServerInstallOptions,
private readonly webExtensionsScannerService: IWebExtensionsScannerService,
) {
super();
@@ -159,7 +183,7 @@ class InstallExtensionTask extends AbstractExtensionTask<{ local: ILocalExtensio
}
protected async doRun(token: CancellationToken): Promise<{ local: ILocalExtension; metadata: Metadata }> {
- const userExtensions = await this.webExtensionsScannerService.scanUserExtensions();
+ const userExtensions = await this.webExtensionsScannerService.scanUserExtensions(this.options.profileLocation);
const existingExtension = userExtensions.find(e => areSameExtensions(e.identifier, this.identifier));
if (existingExtension) {
this._operation = InstallOperation.Update;
@@ -181,8 +205,8 @@ class InstallExtensionTask extends AbstractExtensionTask<{ local: ILocalExtensio
: metadata?.preRelease /* Respect the existing pre-release flag if it was set */);
}
- const scannedExtension = URI.isUri(this.extension) ? await this.webExtensionsScannerService.addExtension(this.extension, metadata)
- : await this.webExtensionsScannerService.addExtensionFromGallery(this.extension, metadata);
+ const scannedExtension = URI.isUri(this.extension) ? await this.webExtensionsScannerService.addExtension(this.extension, metadata, this.options.profileLocation)
+ : await this.webExtensionsScannerService.addExtensionFromGallery(this.extension, metadata, this.options.profileLocation);
return { local: toLocalExtension(scannedExtension), metadata };
}
}
@@ -191,13 +215,13 @@ class UninstallExtensionTask extends AbstractExtensionTask<void> implements IUni
constructor(
readonly extension: ILocalExtension,
- options: UninstallOptions,
+ private readonly options: ServerUninstallOptions,
private readonly webExtensionsScannerService: IWebExtensionsScannerService,
) {
super();
}
protected doRun(token: CancellationToken): Promise<void> {
- return this.webExtensionsScannerService.removeExtension(this.extension.identifier);
+ return this.webExtensionsScannerService.removeExtension(this.extension, this.options.profileLocation);
}
}
diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService.ts
index 762987822ee..b6831100888 100644
--- a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService.ts
+++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService.ts
@@ -15,7 +15,7 @@ import { ILabelService } from 'vs/platform/label/common/label';
import { IExtension } from 'vs/platform/extensions/common/extensions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
-import { NativeProfileAwareExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/profileAwareExtensionManagementService';
+import { NativeExtensionManagementService } from 'vs/workbench/services/extensionManagement/electron-sandbox/nativeExtensionManagementService';
import { Disposable } from 'vs/base/common/lifecycle';
import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
@@ -36,14 +36,8 @@ export class ExtensionManagementServerService extends Disposable implements IExt
@IInstantiationService instantiationService: IInstantiationService,
) {
super();
- const localExtensionManagementService = this._register(instantiationService.createInstance(NativeProfileAwareExtensionManagementService, sharedProcessService.getChannel('extensions'), userDataProfileService.currentProfile.extensionsResource));
+ const localExtensionManagementService = this._register(instantiationService.createInstance(NativeExtensionManagementService, sharedProcessService.getChannel('extensions')));
this.localExtensionManagementServer = { extensionManagementService: localExtensionManagementService, id: 'local', label: localize('local', "Local") };
- this._register(userDataProfilesService.onDidChangeProfiles(e => {
- if (userDataProfileService.currentProfile.isDefault) {
- localExtensionManagementService.extensionsProfileResource = userDataProfilesService.defaultProfile.extensionsResource;
- }
- }));
- this._register(userDataProfileService.onDidChangeCurrentProfile(e => e.join(localExtensionManagementService.switchExtensionsProfile(e.profile.extensionsResource))));
const remoteAgentConnection = remoteAgentService.getConnection();
if (remoteAgentConnection) {
const extensionManagementService = instantiationService.createInstance(NativeRemoteExtensionManagementService, remoteAgentConnection.getChannel<IChannel>('extensions'), this.localExtensionManagementServer);
diff --git a/src/vs/workbench/services/extensionManagement/common/profileAwareExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/nativeExtensionManagementService.ts
index b8a639d4e9e..cdd2bdad7db 100644
--- a/src/vs/workbench/services/extensionManagement/common/profileAwareExtensionManagementService.ts
+++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/nativeExtensionManagementService.ts
@@ -7,15 +7,19 @@ import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { IProfileAwareExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/common/extensionManagementIpc';
import { URI } from 'vs/base/common/uri';
-import { IGalleryExtension, ILocalExtension, InstallOptions, InstallVSIXOptions, UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
+import { IGalleryExtension, ILocalExtension, InstallOptions, InstallVSIXOptions, Metadata, UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionIdentifier, ExtensionType } from 'vs/platform/extensions/common/extensions';
import { Emitter, Event } from 'vs/base/common/event';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { delta } from 'vs/base/common/arrays';
import { compare } from 'vs/base/common/strings';
import { DisposableStore } from 'vs/base/common/lifecycle';
+import { DidChangeUserDataProfileEvent, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
+import { EXTENSIONS_RESOURCE_NAME } from 'vs/platform/userDataProfile/common/userDataProfile';
+import { joinPath } from 'vs/base/common/resources';
+import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService';
-export class NativeProfileAwareExtensionManagementService extends ExtensionManagementChannelClient implements IProfileAwareExtensionManagementService {
+export class NativeExtensionManagementService extends ExtensionManagementChannelClient implements IProfileAwareExtensionManagementService {
private readonly disposables = this._register(new DisposableStore());
@@ -31,42 +35,50 @@ export class NativeProfileAwareExtensionManagementService extends ExtensionManag
private readonly _onDidChangeProfileExtensions = this._register(new Emitter<{ readonly added: ILocalExtension[]; readonly removed: ILocalExtension[] }>());
readonly onDidChangeProfileExtensions = this._onDidChangeProfileExtensions.event;
- constructor(channel: IChannel, public extensionsProfileResource: URI | undefined,
+ constructor(
+ channel: IChannel,
+ @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService,
+ @IExtensionsProfileScannerService private readonly extensionsProfileScannerService: IExtensionsProfileScannerService,
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
) {
super(channel);
+ this._register(userDataProfileService.onDidChangeCurrentProfile(e => e.join(this.whenProfileChanged(e))));
}
private filterEvent({ profileLocation, applicationScoped }: { profileLocation?: URI; applicationScoped?: boolean }): boolean {
- return applicationScoped || this.uriIdentityService.extUri.isEqual(this.extensionsProfileResource, profileLocation);
+ return applicationScoped || this.uriIdentityService.extUri.isEqual(this.userDataProfileService.currentProfile.extensionsResource, profileLocation);
}
override install(vsix: URI, options?: InstallVSIXOptions): Promise<ILocalExtension> {
- return super.install(vsix, { ...options, profileLocation: this.extensionsProfileResource });
+ return super.install(vsix, { ...options, profileLocation: this.userDataProfileService.currentProfile.extensionsResource });
}
override installFromGallery(extension: IGalleryExtension, installOptions?: InstallOptions): Promise<ILocalExtension> {
- return super.installFromGallery(extension, { ...installOptions, profileLocation: this.extensionsProfileResource });
+ return super.installFromGallery(extension, { ...installOptions, profileLocation: this.userDataProfileService.currentProfile.extensionsResource });
}
override uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise<void> {
- return super.uninstall(extension, { ...options, profileLocation: this.extensionsProfileResource });
+ return super.uninstall(extension, { ...options, profileLocation: this.userDataProfileService.currentProfile.extensionsResource });
}
override getInstalled(type: ExtensionType | null = null): Promise<ILocalExtension[]> {
- return super.getInstalled(type, this.extensionsProfileResource);
+ return super.getInstalled(type, this.userDataProfileService.currentProfile.extensionsResource);
}
- async switchExtensionsProfile(extensionsProfileResource: URI | undefined): Promise<void> {
- if (this.uriIdentityService.extUri.isEqual(extensionsProfileResource, this.extensionsProfileResource)) {
- return;
- }
- const oldExtensions = await this.getInstalled(ExtensionType.User);
- this.extensionsProfileResource = extensionsProfileResource;
- const newExtensions = await this.getInstalled(ExtensionType.User);
- const { added, removed } = delta(oldExtensions, newExtensions, (a, b) => compare(`${ExtensionIdentifier.toKey(a.identifier.id)}@${a.manifest.version}`, `${ExtensionIdentifier.toKey(b.identifier.id)}@${b.manifest.version}`));
- if (added.length || removed.length) {
- this._onDidChangeProfileExtensions.fire({ added, removed });
+ private async whenProfileChanged(e: DidChangeUserDataProfileEvent): Promise<void> {
+ const previousExtensionsResource = e.previous.extensionsResource ?? joinPath(e.previous.location, EXTENSIONS_RESOURCE_NAME);
+ const oldExtensions = await super.getInstalled(ExtensionType.User, previousExtensionsResource);
+ if (e.preserveData) {
+ const extensions: [ILocalExtension, Metadata | undefined][] = await Promise.all(oldExtensions
+ .filter(e => !e.isApplicationScoped) /* remove application scoped extensions */
+ .map(async e => ([e, await this.getMetadata(e)])));
+ await this.extensionsProfileScannerService.addExtensionsToProfile(extensions, e.profile.extensionsResource!);
+ } else {
+ const newExtensions = await this.getInstalled(ExtensionType.User);
+ const { added, removed } = delta(oldExtensions, newExtensions, (a, b) => compare(`${ExtensionIdentifier.toKey(a.identifier.id)}@${a.manifest.version}`, `${ExtensionIdentifier.toKey(b.identifier.id)}@${b.manifest.version}`));
+ if (added.length || removed.length) {
+ this._onDidChangeProfileExtensions.fire({ added, removed });
+ }
}
}
diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts
index 53c178a68f6..b9202112131 100644
--- a/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts
+++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts
@@ -4,7 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
-import { IExtensionManagementService, ILocalExtension, IGalleryExtension, IExtensionGalleryService, InstallOperation, InstallOptions, InstallVSIXOptions, ExtensionManagementError, ExtensionManagementErrorCode } from 'vs/platform/extensionManagement/common/extensionManagement';
+import { Event } from 'vs/base/common/event';
+import { ILocalExtension, IGalleryExtension, IExtensionGalleryService, InstallOperation, InstallOptions, InstallVSIXOptions, ExtensionManagementError, ExtensionManagementErrorCode } from 'vs/platform/extensionManagement/common/extensionManagement';
import { URI } from 'vs/base/common/uri';
import { ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
@@ -17,14 +18,15 @@ import { IProductService } from 'vs/platform/product/common/productService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { generateUuid } from 'vs/base/common/uuid';
import { joinPath } from 'vs/base/common/resources';
-import { IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
+import { IExtensionManagementServer, IProfileAwareExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { Promises } from 'vs/base/common/async';
import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService';
-import { NativeProfileAwareExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/profileAwareExtensionManagementService';
-import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
+import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/common/extensionManagementIpc';
-export class NativeRemoteExtensionManagementService extends NativeProfileAwareExtensionManagementService implements IExtensionManagementService {
+export class NativeRemoteExtensionManagementService extends ExtensionManagementChannelClient implements IProfileAwareExtensionManagementService {
+
+ readonly onDidChangeProfileExtensions = Event.None;
constructor(
channel: IChannel,
@@ -35,9 +37,8 @@ export class NativeRemoteExtensionManagementService extends NativeProfileAwareEx
@IProductService private readonly productService: IProductService,
@INativeWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService,
@IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService,
- @IUriIdentityService uriIdentityService: IUriIdentityService,
) {
- super(channel, undefined, uriIdentityService);
+ super(channel);
}
override async install(vsix: URI, options?: InstallVSIXOptions): Promise<ILocalExtension> {
diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts
index acdf6ca2308..5662085250a 100644
--- a/src/vs/workbench/services/extensions/browser/extensionService.ts
+++ b/src/vs/workbench/services/extensions/browser/extensionService.ts
@@ -30,6 +30,8 @@ import { IExtensionManifestPropertiesService } from 'vs/workbench/services/exten
import { IUserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit';
import { IAutomatedWindow } from 'vs/platform/log/browser/log';
import { ILogService } from 'vs/platform/log/common/log';
+import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
+import { dedupExtensions } from 'vs/workbench/services/extensions/common/extensionsUtil';
export class ExtensionService extends AbstractExtensionService implements IExtensionService {
@@ -48,12 +50,13 @@ export class ExtensionService extends AbstractExtensionService implements IExten
@IWorkspaceContextService contextService: IWorkspaceContextService,
@IConfigurationService configurationService: IConfigurationService,
@IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService,
- @IWebExtensionsScannerService webExtensionsScannerService: IWebExtensionsScannerService,
+ @IWebExtensionsScannerService private readonly _webExtensionsScannerService: IWebExtensionsScannerService,
@ILogService logService: ILogService,
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
@ILifecycleService lifecycleService: ILifecycleService,
@IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
@IUserDataInitializationService private readonly _userDataInitializationService: IUserDataInitializationService,
+ @IUserDataProfileService userDataProfileService: IUserDataProfileService,
) {
super(
instantiationService,
@@ -67,10 +70,10 @@ export class ExtensionService extends AbstractExtensionService implements IExten
contextService,
configurationService,
extensionManifestPropertiesService,
- webExtensionsScannerService,
logService,
remoteAgentService,
- lifecycleService
+ lifecycleService,
+ userDataProfileService
);
// Initialize installed extensions first and do it only after workbench is ready
@@ -92,7 +95,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
return this._remoteAgentService.scanSingleExtension(extension.location, extension.type === ExtensionType.System);
}
- const scannedExtension = await this._webExtensionsScannerService.scanExistingExtension(extension.location, extension.type);
+ const scannedExtension = await this._webExtensionsScannerService.scanExistingExtension(extension.location, extension.type, this._userDataProfileService.currentProfile.extensionsResource);
if (scannedExtension) {
return toExtensionDescription(scannedExtension);
}
@@ -189,6 +192,20 @@ export class ExtensionService extends AbstractExtensionService implements IExten
}
}
+ private async _scanWebExtensions(): Promise<IExtensionDescription[]> {
+ const system: IExtensionDescription[] = [], user: IExtensionDescription[] = [], development: IExtensionDescription[] = [];
+ try {
+ await Promise.all([
+ this._webExtensionsScannerService.scanSystemExtensions().then(extensions => system.push(...extensions.map(e => toExtensionDescription(e)))),
+ this._webExtensionsScannerService.scanUserExtensions(this._userDataProfileService.currentProfile.extensionsResource, { skipInvalidExtensions: true }).then(extensions => user.push(...extensions.map(e => toExtensionDescription(e)))),
+ this._webExtensionsScannerService.scanExtensionsUnderDevelopment().then(extensions => development.push(...extensions.map(e => toExtensionDescription(e, true))))
+ ]);
+ } catch (error) {
+ this._logService.error(error);
+ }
+ return dedupExtensions(system, user, development, this._logService);
+ }
+
protected async _scanAndHandleExtensions(): Promise<void> {
// fetch the remote environment
let [localExtensions, remoteEnv, remoteExtensions] = await Promise.all([
diff --git a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts
index 90f4cd0ec56..fc9dcd1125e 100644
--- a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts
+++ b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts
@@ -82,7 +82,15 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost
}
private async _getWebWorkerExtensionHostIframeSrc(): Promise<string> {
- const suffix = this._environmentService.debugExtensionHost && this._environmentService.debugRenderer ? '?debugged=1' : '?';
+ const suffixSearchParams = new URLSearchParams();
+ if (this._environmentService.debugExtensionHost && this._environmentService.debugRenderer) {
+ suffixSearchParams.set('debugged', '1');
+ }
+ if (globalThis.crossOriginIsolated) {
+ suffixSearchParams.set('vscode-coi', '3' /*COOP+COEP*/);
+ }
+ const suffix = `?${suffixSearchParams.toString()}`;
+
const iframeModulePath = 'vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html';
if (platform.isWeb) {
const webEndpointUrlTemplate = this._productService.webEndpointUrlTemplate;
diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts
index 5314122757d..61a3cfd5de2 100644
--- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts
+++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts
@@ -11,11 +11,11 @@ import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'
import * as perf from 'vs/base/common/performance';
import { isEqualOrParent } from 'vs/base/common/resources';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
-import { IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
+import { IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
-import { ActivationTimes, ExtensionPointContribution, IExtensionService, IExtensionsStatus, IMessage, IWillActivateEvent, IResponsiveStateChangeEvent, toExtension, IExtensionHost, ActivationKind, ExtensionHostKind, toExtensionDescription, ExtensionRunningLocation, extensionHostKindToString, ExtensionActivationReason, IInternalExtensionService, RemoteRunningLocation, LocalProcessRunningLocation, LocalWebWorkerRunningLocation } from 'vs/workbench/services/extensions/common/extensions';
+import { ActivationTimes, ExtensionPointContribution, IExtensionService, IExtensionsStatus, IMessage, IWillActivateEvent, IResponsiveStateChangeEvent, toExtension, IExtensionHost, ActivationKind, ExtensionHostKind, ExtensionRunningLocation, extensionHostKindToString, ExtensionActivationReason, IInternalExtensionService, RemoteRunningLocation, LocalProcessRunningLocation, LocalWebWorkerRunningLocation } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionMessageCollector, ExtensionPoint, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
import { ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol';
@@ -32,12 +32,11 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { Schemas } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService';
-import { dedupExtensions } from 'vs/workbench/services/extensions/common/extensionsUtil';
import { ApiProposalName, allApiProposals } from 'vs/workbench/services/extensions/common/extensionsApiProposals';
-import { forEach } from 'vs/base/common/collections';
import { ILogService } from 'vs/platform/log/common/log';
import { IExtensionHostExitInfo, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
+import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
const hasOwnProperty = Object.hasOwnProperty;
const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined);
@@ -186,10 +185,10 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
@IConfigurationService protected readonly _configurationService: IConfigurationService,
@IExtensionManifestPropertiesService protected readonly _extensionManifestPropertiesService: IExtensionManifestPropertiesService,
- @IWebExtensionsScannerService protected readonly _webExtensionsScannerService: IWebExtensionsScannerService,
@ILogService protected readonly _logService: ILogService,
@IRemoteAgentService protected readonly _remoteAgentService: IRemoteAgentService,
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
+ @IUserDataProfileService protected readonly _userDataProfileService: IUserDataProfileService,
) {
super();
@@ -1327,20 +1326,6 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
this._onDidChangeExtensionsStatus.fire([extensionId]);
}
- protected async _scanWebExtensions(): Promise<IExtensionDescription[]> {
- const system: IExtensionDescription[] = [], user: IExtensionDescription[] = [], development: IExtensionDescription[] = [];
- try {
- await Promise.all([
- this._webExtensionsScannerService.scanSystemExtensions().then(extensions => system.push(...extensions.map(e => toExtensionDescription(e)))),
- this._webExtensionsScannerService.scanUserExtensions({ skipInvalidExtensions: true }).then(extensions => user.push(...extensions.map(e => toExtensionDescription(e)))),
- this._webExtensionsScannerService.scanExtensionsUnderDevelopment().then(extensions => development.push(...extensions.map(e => toExtensionDescription(e, true))))
- ]);
- } catch (error) {
- this._logService.error(error);
- }
- return dedupExtensions(system, user, development, this._logService);
- }
-
//#endregion
protected abstract _createExtensionHost(runningLocation: ExtensionRunningLocation, isInitialStart: boolean): IExtensionHost | null;
@@ -1479,9 +1464,9 @@ class ProposedApiController {
// NEW world - product.json spells out what proposals each extension can use
if (productService.extensionEnabledApiProposals) {
- forEach(productService.extensionEnabledApiProposals, entry => {
- const key = ExtensionIdentifier.toKey(entry.key);
- const proposalNames = entry.value.filter(name => {
+ for (const [k, value] of Object.entries(productService.extensionEnabledApiProposals)) {
+ const key = ExtensionIdentifier.toKey(k);
+ const proposalNames = value.filter(name => {
if (!allApiProposals[<ApiProposalName>name]) {
_logService.warn(`Via 'product.json#extensionEnabledApiProposals' extension '${key}' wants API proposal '${name}' but that proposal DOES NOT EXIST. Likely, the proposal has been finalized (check 'vscode.d.ts') or was abandoned.`);
return false;
@@ -1489,7 +1474,7 @@ class ProposedApiController {
return true;
});
this._productEnabledExtensions.set(key, proposalNames);
- });
+ }
}
}
diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts
index f735af27335..17ab37e841f 100644
--- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts
+++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts
@@ -31,6 +31,7 @@ export const allApiProposals = Object.freeze({
idToken: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.idToken.d.ts',
inlineCompletionsAdditions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts',
inlineCompletionsNew: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.inlineCompletionsNew.d.ts',
+ interactiveWindow: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.interactiveWindow.d.ts',
ipc: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.ipc.d.ts',
notebookCellExecutionState: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookCellExecutionState.d.ts',
notebookContentProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookContentProvider.d.ts',
@@ -55,7 +56,7 @@ export const allApiProposals = Object.freeze({
telemetry: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.telemetry.d.ts',
terminalDataWriteEvent: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalDataWriteEvent.d.ts',
terminalDimensions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalDimensions.d.ts',
- terminalNameChangeEvent: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalNameChangeEvent.d.ts',
+ terminalExitReason: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalExitReason.d.ts',
testCoverage: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testCoverage.d.ts',
testObserver: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testObserver.d.ts',
textEditorDrop: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textEditorDrop.d.ts',
diff --git a/src/vs/workbench/services/extensions/electron-browser/nativeExtensionService.ts b/src/vs/workbench/services/extensions/electron-browser/nativeExtensionService.ts
deleted file mode 100644
index 2e4c745a9f8..00000000000
--- a/src/vs/workbench/services/extensions/electron-browser/nativeExtensionService.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
-import { ExtensionHostKind, ExtensionRunningLocation, IExtensionHost, IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
-import { NativeLocalProcessExtensionHost } from 'vs/workbench/services/extensions/electron-browser/nativeLocalProcessExtensionHost';
-import { ElectronExtensionService } from 'vs/workbench/services/extensions/electron-sandbox/electronExtensionService';
-
-export class NativeExtensionService extends ElectronExtensionService {
- protected override _createExtensionHost(runningLocation: ExtensionRunningLocation, isInitialStart: boolean): IExtensionHost | null {
- if (runningLocation.kind === ExtensionHostKind.LocalProcess) {
- return this._instantiationService.createInstance(NativeLocalProcessExtensionHost, runningLocation, this._createLocalExtensionHostDataProvider(isInitialStart, runningLocation));
- }
- return super._createExtensionHost(runningLocation, isInitialStart);
- }
-}
-
-registerSingleton(IExtensionService, NativeExtensionService);
diff --git a/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts b/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts
index 3c267c40962..649e83699e8 100644
--- a/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts
+++ b/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts
@@ -9,7 +9,7 @@ import * as nls from 'vs/nls';
import { runWhenIdle } from 'vs/base/common/async';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
-import { IWorkbenchExtensionEnablementService, EnablementState, IWebExtensionsScannerService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
+import { IWorkbenchExtensionEnablementService, EnablementState, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IRemoteExtensionHostDataProvider, RemoteExtensionHost, IRemoteExtensionHostInitData } from 'vs/workbench/services/extensions/common/remoteExtensionHost';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
@@ -26,7 +26,6 @@ import { ExtensionKind } from 'vs/platform/environment/common/environment';
import { IFileService } from 'vs/platform/files/common/files';
import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection';
import { IProductService } from 'vs/platform/product/common/productService';
-import { flatten } from 'vs/base/common/arrays';
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
@@ -48,6 +47,7 @@ import { isCI } from 'vs/base/common/platform';
import { IResolveAuthorityErrorResult } from 'vs/workbench/services/extensions/common/extensionHostProxy';
import { URI } from 'vs/base/common/uri';
import { ILocalProcessExtensionHostDataProvider, ILocalProcessExtensionHostInitData, SandboxLocalProcessExtensionHost } from 'vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost';
+import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
export abstract class ElectronExtensionService extends AbstractExtensionService implements IExtensionService {
@@ -70,7 +70,6 @@ export abstract class ElectronExtensionService extends AbstractExtensionService
@IWorkspaceContextService contextService: IWorkspaceContextService,
@IConfigurationService configurationService: IConfigurationService,
@IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService,
- @IWebExtensionsScannerService webExtensionsScannerService: IWebExtensionsScannerService,
@ILogService logService: ILogService,
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
@ILifecycleService lifecycleService: ILifecycleService,
@@ -80,6 +79,7 @@ export abstract class ElectronExtensionService extends AbstractExtensionService
@IRemoteExplorerService private readonly _remoteExplorerService: IRemoteExplorerService,
@IExtensionGalleryService private readonly _extensionGalleryService: IExtensionGalleryService,
@IWorkspaceTrustManagementService private readonly _workspaceTrustManagementService: IWorkspaceTrustManagementService,
+ @IUserDataProfileService userDataProfileService: IUserDataProfileService,
) {
super(
instantiationService,
@@ -93,10 +93,10 @@ export abstract class ElectronExtensionService extends AbstractExtensionService
contextService,
configurationService,
extensionManifestPropertiesService,
- webExtensionsScannerService,
logService,
remoteAgentService,
- lifecycleService
+ lifecycleService,
+ userDataProfileService
);
[this._enableLocalWebWorker, this._lazyLocalWebWorker] = this._isLocalWebWorkerEnabled();
@@ -148,10 +148,7 @@ export abstract class ElectronExtensionService extends AbstractExtensionService
}
private async _scanAllLocalExtensions(): Promise<IExtensionDescription[]> {
- return flatten(await Promise.all([
- this._extensionScanner.scannedExtensions,
- this._scanWebExtensions(),
- ]));
+ return this._extensionScanner.scannedExtensions;
}
protected _createLocalExtensionHostDataProvider(isInitialStart: boolean, desiredRunningLocation: ExtensionRunningLocation): ILocalProcessExtensionHostDataProvider & IWebWorkerExtensionHostDataProvider {
diff --git a/src/vs/workbench/services/extensions/electron-browser/nativeLocalProcessExtensionHost.ts b/src/vs/workbench/services/extensions/electron-sandbox/nativeLocalProcessExtensionHost.ts
index 888db160609..35d0657b669 100644
--- a/src/vs/workbench/services/extensions/electron-browser/nativeLocalProcessExtensionHost.ts
+++ b/src/vs/workbench/services/extensions/electron-sandbox/nativeLocalProcessExtensionHost.ts
@@ -3,7 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { createServer, Server } from 'net';
+/* eslint-disable code-import-patterns */
+/* eslint-disable code-layering */
+
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import * as platform from 'vs/base/common/platform';
import { StopWatch } from 'vs/base/common/stopwatch';
@@ -15,11 +17,12 @@ import { ILogService } from 'vs/platform/log/common/log';
import { IPCExtHostConnection, writeExtHostConnection } from 'vs/workbench/services/extensions/common/extensionHostEnv';
import { createMessageOfType, MessageType } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
import { ExtensionHostProcess, ExtHostMessagePortCommunication, IExtHostCommunication, SandboxLocalProcessExtensionHost } from 'vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost';
+import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals';
export class NativeLocalProcessExtensionHost extends SandboxLocalProcessExtensionHost {
protected override async _start(): Promise<IMessagePassingProtocol> {
const canUseUtilityProcess = await this._extensionHostStarter.canUseUtilityProcess();
- if (canUseUtilityProcess && this._configurationService.getValue<boolean | undefined>('extensions.experimental.useUtilityProcess')) {
+ if (canUseUtilityProcess && (this._configurationService.getValue<boolean | undefined>('extensions.experimental.useUtilityProcess') || process.sandboxed)) {
const communication = this._toDispose.add(new ExtHostMessagePortCommunication(this._logService));
return this._startWithCommunication(communication);
} else {
@@ -31,7 +34,7 @@ export class NativeLocalProcessExtensionHost extends SandboxLocalProcessExtensio
interface INamedPipePreparedData {
pipeName: string;
- namedPipeServer: Server;
+ namedPipeServer: import('net').Server;
}
class ExtHostNamedPipeCommunication extends Disposable implements IExtHostCommunication<INamedPipePreparedData> {
@@ -44,8 +47,9 @@ class ExtHostNamedPipeCommunication extends Disposable implements IExtHostCommun
super();
}
- prepare(): Promise<INamedPipePreparedData> {
- return new Promise<{ pipeName: string; namedPipeServer: Server }>((resolve, reject) => {
+ async prepare(): Promise<INamedPipePreparedData> {
+ const { createServer } = await import('net');
+ return new Promise<{ pipeName: string; namedPipeServer: import('net').Server }>((resolve, reject) => {
const pipeName = createRandomIPCHandle();
const namedPipeServer = createServer();
diff --git a/src/vs/workbench/services/extensions/electron-sandbox/sandboxExtensionService.ts b/src/vs/workbench/services/extensions/electron-sandbox/sandboxExtensionService.ts
index ea51d7abc8f..58e5c5bf71c 100644
--- a/src/vs/workbench/services/extensions/electron-sandbox/sandboxExtensionService.ts
+++ b/src/vs/workbench/services/extensions/electron-sandbox/sandboxExtensionService.ts
@@ -4,10 +4,19 @@
*--------------------------------------------------------------------------------------------*/
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
-import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
+import { ExtensionHostKind, ExtensionRunningLocation, IExtensionHost, IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { ElectronExtensionService } from 'vs/workbench/services/extensions/electron-sandbox/electronExtensionService';
+import { NativeLocalProcessExtensionHost } from 'vs/workbench/services/extensions/electron-sandbox/nativeLocalProcessExtensionHost';
+import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals';
export class SandboxExtensionService extends ElectronExtensionService {
+ protected override _createExtensionHost(runningLocation: ExtensionRunningLocation, isInitialStart: boolean): IExtensionHost | null {
+ if (!process.sandboxed && runningLocation.kind === ExtensionHostKind.LocalProcess) {
+ // TODO@bpasero remove me once electron utility process has landed
+ return this._instantiationService.createInstance(NativeLocalProcessExtensionHost, runningLocation, this._createLocalExtensionHostDataProvider(isInitialStart, runningLocation));
+ }
+ return super._createExtensionHost(runningLocation, isInitialStart);
+ }
}
registerSingleton(IExtensionService, SandboxExtensionService);
diff --git a/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts b/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts
index 18d684cacaa..eaa0069f098 100644
--- a/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts
+++ b/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts
@@ -33,6 +33,11 @@ import { TestEnvironmentService, TestFileService, TestLifecycleService, TestRemo
import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices';
import { mock } from 'vs/base/test/common/mock';
import { IExtensionHostManager } from 'vs/workbench/services/extensions/common/extensionHostManager';
+import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
+import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService';
+import { IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
+import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
+import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService';
suite('BrowserExtensionService', () => {
test('pickRunningLocation', () => {
@@ -175,7 +180,10 @@ suite('ExtensionService', () => {
[IWorkbenchExtensionEnablementService, TestWorkbenchExtensionEnablementService],
[ITelemetryService, NullTelemetryService],
[IEnvironmentService, TestEnvironmentService],
- [IWorkspaceTrustEnablementService, WorkspaceTrustEnablementService]
+ [IWorkspaceTrustEnablementService, WorkspaceTrustEnablementService],
+ [IUserDataProfilesService, UserDataProfilesService],
+ [IUserDataProfileService, UserDataProfileService],
+ [IUriIdentityService, UriIdentityService],
]);
extService = <MyTestExtensionService>instantiationService.get(IExtensionService);
});
diff --git a/src/vs/workbench/services/extensions/test/browser/extensionStorageMigration.test.ts b/src/vs/workbench/services/extensions/test/browser/extensionStorageMigration.test.ts
index bbee18ae82b..a02b86def5c 100644
--- a/src/vs/workbench/services/extensions/test/browser/extensionStorageMigration.test.ts
+++ b/src/vs/workbench/services/extensions/test/browser/extensionStorageMigration.test.ts
@@ -22,6 +22,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag
import { IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService';
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
+import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService';
suite('ExtensionStorageMigration', () => {
@@ -38,8 +39,8 @@ suite('ExtensionStorageMigration', () => {
fileService.registerProvider(ROOT.scheme, disposables.add(new InMemoryFileSystemProvider()));
instantiationService.stub(IFileService, fileService);
const environmentService = instantiationService.stub(IEnvironmentService, <Partial<IEnvironmentService>>{ userRoamingDataHome: ROOT, workspaceStorageHome });
- const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, new NullLogService()));
- instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile));
+ const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, new UriIdentityService(fileService), new NullLogService()));
+ instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService));
instantiationService.stub(IExtensionStorageService, instantiationService.createInstance(ExtensionStorageService));
});
diff --git a/src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html b/src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html
index 11135d5fff1..576bad14363 100644
--- a/src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html
+++ b/src/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html
@@ -4,7 +4,7 @@
<meta http-equiv="Content-Security-Policy" content="
default-src 'none';
child-src 'self' data: blob:;
- script-src 'self' 'unsafe-eval' 'sha256-fh3TwPMflhsEIpR8g1OYTIMVWhXTLcjQ9kh2tIpmv54=' https:;
+ script-src 'self' 'unsafe-eval' 'sha256-/r7rqQ+yrxt57sxLuQ6AMYcy/lUpvAIzHjIJt/OeLWU=' https:;
connect-src 'self' https: wss: http://localhost:* http://127.0.0.1:* ws://localhost:* ws://127.0.0.1:*;"/>
</head>
<body>
@@ -66,7 +66,12 @@
function start() {
try {
- const worker = new Worker('../../../../base/worker/workerMain.js', { name });
+ let workerUrl = '../../../../base/worker/workerMain.js';
+ if(globalThis.crossOriginIsolated) {
+ workerUrl += '?vscode-coi=2'; // COEP
+ }
+
+ const worker = new Worker(workerUrl, { name });
worker.postMessage('vs/workbench/api/worker/extensionHostWorker');
const nestedWorkers = new Map();
diff --git a/src/vs/workbench/services/history/browser/historyService.ts b/src/vs/workbench/services/history/browser/historyService.ts
index e0fb71b15ed..6f73bd54add 100644
--- a/src/vs/workbench/services/history/browser/historyService.ts
+++ b/src/vs/workbench/services/history/browser/historyService.ts
@@ -993,6 +993,10 @@ export class HistoryService extends Disposable implements IHistoryService {
try {
const entriesParsed: ISerializedEditorHistoryEntry[] = JSON.parse(entriesRaw);
for (const entryParsed of entriesParsed) {
+ if (!entryParsed.editor || !entryParsed.editor.resource) {
+ continue; // unexpected data format
+ }
+
try {
entries.push({
...entryParsed.editor,
diff --git a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts
index 40f41611139..a72466b917a 100644
--- a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts
+++ b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts
@@ -20,6 +20,7 @@ import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/co
import { IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication';
import { registerMainProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services';
import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust';
+import { IIntegrityService } from 'vs/workbench/services/integrity/common/integrity';
export class WorkbenchIssueService implements IWorkbenchIssueService {
declare readonly _serviceBrand: undefined;
@@ -33,7 +34,8 @@ export class WorkbenchIssueService implements IWorkbenchIssueService {
@IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService,
@IProductService private readonly productService: IProductService,
@IWorkbenchAssignmentService private readonly experimentService: IWorkbenchAssignmentService,
- @IAuthenticationService private readonly authenticationService: IAuthenticationService
+ @IAuthenticationService private readonly authenticationService: IAuthenticationService,
+ @IIntegrityService private readonly integrityService: IIntegrityService
) { }
async openReporter(dataOverrides: Partial<IssueReporterData> = {}): Promise<void> {
@@ -82,6 +84,14 @@ export class WorkbenchIssueService implements IWorkbenchIssueService {
// Ignore
}
+ // air on the side of caution and have false be the default
+ let isUnsupported = false;
+ try {
+ isUnsupported = !(await this.integrityService.isPure()).isPure;
+ } catch (e) {
+ // Ignore
+ }
+
const theme = this.themeService.getColorTheme();
const issueReporterData: IssueReporterData = Object.assign({
styles: getIssueReporterStyles(theme),
@@ -89,6 +99,7 @@ export class WorkbenchIssueService implements IWorkbenchIssueService {
enabledExtensions: extensionData,
experiments: experiments?.join('\n'),
restrictedMode: !this.workspaceTrustManagementService.isWorkspaceTrusted(),
+ isUnsupported,
githubAccessToken,
}, dataOverrides);
return this.issueService.openReporter(issueReporterData);
diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts
index ccd362686a0..fe4650f771f 100644
--- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts
+++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts
@@ -50,7 +50,7 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { dirname } from 'vs/base/common/resources';
import { getAllUnboundCommands } from 'vs/workbench/services/keybinding/browser/unboundCommands';
import { UserSettingsLabelProvider } from 'vs/base/common/keybindingLabels';
-import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
+import { DidChangeUserDataProfileEvent, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
interface ContributedKeyBinding {
command: string;
@@ -738,10 +738,15 @@ class UserKeybindings extends Disposable {
}
}));
- this._register(userDataProfileService.onDidChangeCurrentProfile(e => {
- this.watch();
- this.reloadConfigurationScheduler.schedule();
- }));
+ this._register(userDataProfileService.onDidChangeCurrentProfile(e => e.join(this.whenCurrentProfieChanged(e))));
+ }
+
+ private async whenCurrentProfieChanged(e: DidChangeUserDataProfileEvent): Promise<void> {
+ if (e.preserveData) {
+ await this.fileService.copy(e.previous.keybindingsResource, e.profile.keybindingsResource);
+ }
+ this.watch();
+ this.reloadConfigurationScheduler.schedule();
}
private watch(): void {
diff --git a/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts b/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts
index 66b5e7fa8a8..40e00516362 100644
--- a/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts
+++ b/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts
@@ -31,6 +31,7 @@ import { VSBuffer } from 'vs/base/common/buffer';
import { UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService';
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
+import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService';
interface Modifiers {
metaKey?: boolean;
@@ -66,7 +67,8 @@ suite('KeybindingsEditing', () => {
const configService = new TestConfigurationService();
configService.setUserConfiguration('files', { 'eol': '\n' });
- userDataProfileService = new UserDataProfileService(new UserDataProfilesService(environmentService, fileService, logService).defaultProfile);
+ const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, new UriIdentityService(fileService), logService);
+ userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService);
instantiationService = workbenchInstantiationService({
fileService: () => fileService,
diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keyboardMapperTestUtils.ts b/src/vs/workbench/services/keybinding/test/node/keyboardMapperTestUtils.ts
index 5d3f52834c2..742d09e2c3a 100644
--- a/src/vs/workbench/services/keybinding/test/electron-browser/keyboardMapperTestUtils.ts
+++ b/src/vs/workbench/services/keybinding/test/node/keyboardMapperTestUtils.ts
@@ -51,7 +51,7 @@ export function assertResolveUserBinding(mapper: IKeyboardMapper, parts: (Simple
}
export function readRawMapping<T>(file: string): Promise<T> {
- return Promises.readFile(getPathFromAmdModule(require, `vs/workbench/services/keybinding/test/electron-browser/${file}.js`)).then((buff) => {
+ return Promises.readFile(getPathFromAmdModule(require, `vs/workbench/services/keybinding/test/node/${file}.js`)).then((buff) => {
const contents = buff.toString();
const func = new Function('define', contents);
let rawMappings: T | null = null;
@@ -63,7 +63,7 @@ export function readRawMapping<T>(file: string): Promise<T> {
}
export function assertMapping(writeFileIfDifferent: boolean, mapper: IKeyboardMapper, file: string): Promise<void> {
- const filePath = path.normalize(getPathFromAmdModule(require, `vs/workbench/services/keybinding/test/electron-browser/${file}`));
+ const filePath = path.normalize(getPathFromAmdModule(require, `vs/workbench/services/keybinding/test/node/${file}`));
return Promises.readFile(filePath).then((buff) => {
const expected = buff.toString().replace(/\r\n/g, '\n');
diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/linux_de_ch.js b/src/vs/workbench/services/keybinding/test/node/linux_de_ch.js
index 3374e83f679..3374e83f679 100644
--- a/src/vs/workbench/services/keybinding/test/electron-browser/linux_de_ch.js
+++ b/src/vs/workbench/services/keybinding/test/node/linux_de_ch.js
diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/linux_de_ch.txt b/src/vs/workbench/services/keybinding/test/node/linux_de_ch.txt
index aaf9263ec26..aaf9263ec26 100644
--- a/src/vs/workbench/services/keybinding/test/electron-browser/linux_de_ch.txt
+++ b/src/vs/workbench/services/keybinding/test/node/linux_de_ch.txt
diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/linux_en_uk.js b/src/vs/workbench/services/keybinding/test/node/linux_en_uk.js
index 379e7355c8e..379e7355c8e 100644
--- a/src/vs/workbench/services/keybinding/test/electron-browser/linux_en_uk.js
+++ b/src/vs/workbench/services/keybinding/test/node/linux_en_uk.js
diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/linux_en_uk.txt b/src/vs/workbench/services/keybinding/test/node/linux_en_uk.txt
index 9d1ab7184c4..9d1ab7184c4 100644
--- a/src/vs/workbench/services/keybinding/test/electron-browser/linux_en_uk.txt
+++ b/src/vs/workbench/services/keybinding/test/node/linux_en_uk.txt
diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/linux_en_us.js b/src/vs/workbench/services/keybinding/test/node/linux_en_us.js
index 9f6ddd3caf9..9f6ddd3caf9 100644
--- a/src/vs/workbench/services/keybinding/test/electron-browser/linux_en_us.js
+++ b/src/vs/workbench/services/keybinding/test/node/linux_en_us.js
diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/linux_en_us.txt b/src/vs/workbench/services/keybinding/test/node/linux_en_us.txt
index 704e852f8d6..704e852f8d6 100644
--- a/src/vs/workbench/services/keybinding/test/electron-browser/linux_en_us.txt
+++ b/src/vs/workbench/services/keybinding/test/node/linux_en_us.txt
diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/linux_ru.js b/src/vs/workbench/services/keybinding/test/node/linux_ru.js
index 950223704b6..950223704b6 100644
--- a/src/vs/workbench/services/keybinding/test/electron-browser/linux_ru.js
+++ b/src/vs/workbench/services/keybinding/test/node/linux_ru.js
diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/linux_ru.txt b/src/vs/workbench/services/keybinding/test/node/linux_ru.txt
index 389681ee995..389681ee995 100644
--- a/src/vs/workbench/services/keybinding/test/electron-browser/linux_ru.txt
+++ b/src/vs/workbench/services/keybinding/test/node/linux_ru.txt
diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/macLinuxFallbackKeyboardMapper.test.ts b/src/vs/workbench/services/keybinding/test/node/macLinuxFallbackKeyboardMapper.test.ts
index b7db656ffd6..649227a8921 100644
--- a/src/vs/workbench/services/keybinding/test/electron-browser/macLinuxFallbackKeyboardMapper.test.ts
+++ b/src/vs/workbench/services/keybinding/test/node/macLinuxFallbackKeyboardMapper.test.ts
@@ -7,7 +7,7 @@ import { KeyChord, KeyCode, KeyMod, ScanCode } from 'vs/base/common/keyCodes';
import { SimpleKeybinding, createKeybinding, ScanCodeBinding } from 'vs/base/common/keybindings';
import { OperatingSystem } from 'vs/base/common/platform';
import { MacLinuxFallbackKeyboardMapper } from 'vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper';
-import { IResolvedKeybinding, assertResolveKeybinding, assertResolveKeyboardEvent, assertResolveUserBinding } from 'vs/workbench/services/keybinding/test/electron-browser/keyboardMapperTestUtils';
+import { IResolvedKeybinding, assertResolveKeybinding, assertResolveKeyboardEvent, assertResolveUserBinding } from 'vs/workbench/services/keybinding/test/node/keyboardMapperTestUtils';
suite('keyboardMapper - MAC fallback', () => {
diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/macLinuxKeyboardMapper.test.ts b/src/vs/workbench/services/keybinding/test/node/macLinuxKeyboardMapper.test.ts
index a5984fc12e0..5c7c6021539 100644
--- a/src/vs/workbench/services/keybinding/test/electron-browser/macLinuxKeyboardMapper.test.ts
+++ b/src/vs/workbench/services/keybinding/test/node/macLinuxKeyboardMapper.test.ts
@@ -10,7 +10,7 @@ import { UserSettingsLabelProvider } from 'vs/base/common/keybindingLabels';
import { OperatingSystem } from 'vs/base/common/platform';
import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding';
import { MacLinuxKeyboardMapper } from 'vs/workbench/services/keybinding/common/macLinuxKeyboardMapper';
-import { IResolvedKeybinding, assertMapping, assertResolveKeybinding, assertResolveKeyboardEvent, assertResolveUserBinding, readRawMapping } from 'vs/workbench/services/keybinding/test/electron-browser/keyboardMapperTestUtils';
+import { IResolvedKeybinding, assertMapping, assertResolveKeybinding, assertResolveKeyboardEvent, assertResolveUserBinding, readRawMapping } from 'vs/workbench/services/keybinding/test/node/keyboardMapperTestUtils';
import { IMacLinuxKeyboardMapping } from 'vs/platform/keyboardLayout/common/keyboardLayout';
const WRITE_FILE_IF_DIFFERENT = false;
diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/mac_de_ch.js b/src/vs/workbench/services/keybinding/test/node/mac_de_ch.js
index 6da177d6785..6da177d6785 100644
--- a/src/vs/workbench/services/keybinding/test/electron-browser/mac_de_ch.js
+++ b/src/vs/workbench/services/keybinding/test/node/mac_de_ch.js
diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/mac_de_ch.txt b/src/vs/workbench/services/keybinding/test/node/mac_de_ch.txt
index 60627a704dc..60627a704dc 100644
--- a/src/vs/workbench/services/keybinding/test/electron-browser/mac_de_ch.txt
+++ b/src/vs/workbench/services/keybinding/test/node/mac_de_ch.txt
diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/mac_en_us.js b/src/vs/workbench/services/keybinding/test/node/mac_en_us.js
index 1b62128a42d..1b62128a42d 100644
--- a/src/vs/workbench/services/keybinding/test/electron-browser/mac_en_us.js
+++ b/src/vs/workbench/services/keybinding/test/node/mac_en_us.js
diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/mac_en_us.txt b/src/vs/workbench/services/keybinding/test/node/mac_en_us.txt
index 833fdf61c32..833fdf61c32 100644
--- a/src/vs/workbench/services/keybinding/test/electron-browser/mac_en_us.txt
+++ b/src/vs/workbench/services/keybinding/test/node/mac_en_us.txt
diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/mac_zh_hant.js b/src/vs/workbench/services/keybinding/test/node/mac_zh_hant.js
index 7a3fd205cc5..7a3fd205cc5 100644
--- a/src/vs/workbench/services/keybinding/test/electron-browser/mac_zh_hant.js
+++ b/src/vs/workbench/services/keybinding/test/node/mac_zh_hant.js
diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/mac_zh_hant.txt b/src/vs/workbench/services/keybinding/test/node/mac_zh_hant.txt
index 0d7b9b6bbd6..0d7b9b6bbd6 100644
--- a/src/vs/workbench/services/keybinding/test/electron-browser/mac_zh_hant.txt
+++ b/src/vs/workbench/services/keybinding/test/node/mac_zh_hant.txt
diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/mac_zh_hant2.js b/src/vs/workbench/services/keybinding/test/node/mac_zh_hant2.js
index 5ad77d35f34..5ad77d35f34 100644
--- a/src/vs/workbench/services/keybinding/test/electron-browser/mac_zh_hant2.js
+++ b/src/vs/workbench/services/keybinding/test/node/mac_zh_hant2.js
diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/mac_zh_hant2.txt b/src/vs/workbench/services/keybinding/test/node/mac_zh_hant2.txt
index 17b24363b35..17b24363b35 100644
--- a/src/vs/workbench/services/keybinding/test/electron-browser/mac_zh_hant2.txt
+++ b/src/vs/workbench/services/keybinding/test/node/mac_zh_hant2.txt
diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/win_de_ch.js b/src/vs/workbench/services/keybinding/test/node/win_de_ch.js
index a0e38d675e2..a0e38d675e2 100644
--- a/src/vs/workbench/services/keybinding/test/electron-browser/win_de_ch.js
+++ b/src/vs/workbench/services/keybinding/test/node/win_de_ch.js
diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/win_de_ch.txt b/src/vs/workbench/services/keybinding/test/node/win_de_ch.txt
index ac0575e250e..ac0575e250e 100644
--- a/src/vs/workbench/services/keybinding/test/electron-browser/win_de_ch.txt
+++ b/src/vs/workbench/services/keybinding/test/node/win_de_ch.txt
diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/win_en_us.js b/src/vs/workbench/services/keybinding/test/node/win_en_us.js
index 6961ed286fe..6961ed286fe 100644
--- a/src/vs/workbench/services/keybinding/test/electron-browser/win_en_us.js
+++ b/src/vs/workbench/services/keybinding/test/node/win_en_us.js
diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/win_en_us.txt b/src/vs/workbench/services/keybinding/test/node/win_en_us.txt
index b011bd0be56..b011bd0be56 100644
--- a/src/vs/workbench/services/keybinding/test/electron-browser/win_en_us.txt
+++ b/src/vs/workbench/services/keybinding/test/node/win_en_us.txt
diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/win_por_ptb.js b/src/vs/workbench/services/keybinding/test/node/win_por_ptb.js
index 683b2e24113..683b2e24113 100644
--- a/src/vs/workbench/services/keybinding/test/electron-browser/win_por_ptb.js
+++ b/src/vs/workbench/services/keybinding/test/node/win_por_ptb.js
diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/win_por_ptb.txt b/src/vs/workbench/services/keybinding/test/node/win_por_ptb.txt
index c29ed546991..c29ed546991 100644
--- a/src/vs/workbench/services/keybinding/test/electron-browser/win_por_ptb.txt
+++ b/src/vs/workbench/services/keybinding/test/node/win_por_ptb.txt
diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/win_ru.js b/src/vs/workbench/services/keybinding/test/node/win_ru.js
index 4f024d361eb..4f024d361eb 100644
--- a/src/vs/workbench/services/keybinding/test/electron-browser/win_ru.js
+++ b/src/vs/workbench/services/keybinding/test/node/win_ru.js
diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/win_ru.txt b/src/vs/workbench/services/keybinding/test/node/win_ru.txt
index 61af5ab16e7..61af5ab16e7 100644
--- a/src/vs/workbench/services/keybinding/test/electron-browser/win_ru.txt
+++ b/src/vs/workbench/services/keybinding/test/node/win_ru.txt
diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/windowsKeyboardMapper.test.ts b/src/vs/workbench/services/keybinding/test/node/windowsKeyboardMapper.test.ts
index f4e3a99adc0..371d9b15f31 100644
--- a/src/vs/workbench/services/keybinding/test/electron-browser/windowsKeyboardMapper.test.ts
+++ b/src/vs/workbench/services/keybinding/test/node/windowsKeyboardMapper.test.ts
@@ -7,7 +7,7 @@ import { KeyChord, KeyCode, KeyMod, ScanCode } from 'vs/base/common/keyCodes';
import { SimpleKeybinding, createKeybinding, ScanCodeBinding } from 'vs/base/common/keybindings';
import { OperatingSystem } from 'vs/base/common/platform';
import { WindowsKeyboardMapper } from 'vs/workbench/services/keybinding/common/windowsKeyboardMapper';
-import { IResolvedKeybinding, assertMapping, assertResolveKeybinding, assertResolveKeyboardEvent, assertResolveUserBinding, readRawMapping } from 'vs/workbench/services/keybinding/test/electron-browser/keyboardMapperTestUtils';
+import { IResolvedKeybinding, assertMapping, assertResolveKeybinding, assertResolveKeyboardEvent, assertResolveUserBinding, readRawMapping } from 'vs/workbench/services/keybinding/test/node/keyboardMapperTestUtils';
import { IWindowsKeyboardMapping } from 'vs/platform/keyboardLayout/common/keyboardLayout';
const WRITE_FILE_IF_DIFFERENT = false;
diff --git a/src/vs/workbench/services/storage/browser/storageService.ts b/src/vs/workbench/services/storage/browser/storageService.ts
index 694d8317a1f..0266871f498 100644
--- a/src/vs/workbench/services/storage/browser/storageService.ts
+++ b/src/vs/workbench/services/storage/browser/storageService.ts
@@ -3,12 +3,13 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
+import { BroadcastDataChannel } from 'vs/base/browser/broadcast';
import { isSafari } from 'vs/base/browser/browser';
import { IndexedDB } from 'vs/base/browser/indexedDB';
import { DeferredPromise, Promises } from 'vs/base/common/async';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { Emitter } from 'vs/base/common/event';
-import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
+import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { assertIsDefined } from 'vs/base/common/types';
import { InMemoryStorageDatabase, isStorageItemsChangeEvent, IStorage, IStorageDatabase, IStorageItemsChangeEvent, IUpdateRequest, Storage } from 'vs/base/parts/storage/common/storage';
import { ILogService } from 'vs/platform/log/common/log';
@@ -23,7 +24,7 @@ export class BrowserStorageService extends AbstractStorageService {
private applicationStorage: IStorage | undefined;
private applicationStorageDatabase: IIndexedDBStorageDatabase | undefined;
- private readonly applicationStoragePromise = new DeferredPromise<{ indededDb: IIndexedDBStorageDatabase; storage: IStorage }>();
+ private readonly applicationStoragePromise = new DeferredPromise<{ indexedDb: IIndexedDBStorageDatabase; storage: IStorage }>();
private profileStorage: IStorage | undefined;
private profileStorageDatabase: IIndexedDBStorageDatabase | undefined;
@@ -92,7 +93,7 @@ export class BrowserStorageService extends AbstractStorageService {
this.updateIsNew(this.applicationStorage);
- this.applicationStoragePromise.complete({ indededDb: applicationStorageIndexedDB, storage: this.applicationStorage });
+ this.applicationStoragePromise.complete({ indexedDb: applicationStorageIndexedDB, storage: this.applicationStorage });
}
private async createProfileStorage(profile: IUserDataProfile): Promise<void> {
@@ -110,22 +111,24 @@ export class BrowserStorageService extends AbstractStorageService {
// avoid creating the storage library a second time on
// the same DB.
- const { indededDb: applicationStorageIndexedDB, storage: applicationStorage } = await this.applicationStoragePromise.p;
+ const { indexedDb: applicationStorageIndexedDB, storage: applicationStorage } = await this.applicationStoragePromise.p;
this.profileStorageDatabase = applicationStorageIndexedDB;
this.profileStorage = applicationStorage;
+
+ this.profileStorageDisposables.add(this.profileStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.PROFILE, key)));
} else {
const profileStorageIndexedDB = await IndexedDBStorageDatabase.create({ id: this.getId(StorageScope.PROFILE), broadcastChanges: true }, this.logService);
this.profileStorageDatabase = this.profileStorageDisposables.add(profileStorageIndexedDB);
this.profileStorage = this.profileStorageDisposables.add(new Storage(this.profileStorageDatabase));
- }
- this.profileStorageDisposables.add(this.profileStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.PROFILE, key)));
+ this.profileStorageDisposables.add(this.profileStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.PROFILE, key)));
- await this.profileStorage.init();
+ await this.profileStorage.init();
- this.updateIsNew(this.profileStorage);
+ this.updateIsNew(this.profileStorage);
+ }
}
private async createWorkspaceStorage(): Promise<void> {
@@ -166,7 +169,7 @@ export class BrowserStorageService extends AbstractStorageService {
}
protected async switchToProfile(toProfile: IUserDataProfile, preserveData: boolean): Promise<void> {
- if (this.profileStorageProfile && !this.canSwitchProfile(this.profileStorageProfile, toProfile)) {
+ if (!this.canSwitchProfile(this.profileStorageProfile, toProfile)) {
return;
}
@@ -300,7 +303,7 @@ export class IndexedDBStorageDatabase extends Disposable implements IIndexedDBSt
private readonly _onDidChangeItemsExternal = this._register(new Emitter<IStorageItemsChangeEvent>());
readonly onDidChangeItemsExternal = this._onDidChangeItemsExternal.event;
- private broadcastChannel: BroadcastChannel | undefined;
+ private broadcastChannel: BroadcastDataChannel<IStorageItemsChangeEvent> | undefined;
private pendingUpdate: Promise<boolean> | undefined = undefined;
get hasPendingUpdate(): boolean { return !!this.pendingUpdate; }
@@ -315,7 +318,7 @@ export class IndexedDBStorageDatabase extends Disposable implements IIndexedDBSt
super();
this.name = `${IndexedDBStorageDatabase.STORAGE_DATABASE_PREFIX}${options.id}`;
- this.broadcastChannel = options.broadcastChanges && ('BroadcastChannel' in window) ? new BroadcastChannel(IndexedDBStorageDatabase.STORAGE_BROADCAST_CHANNEL) : undefined;
+ this.broadcastChannel = options.broadcastChanges ? this._register(new BroadcastDataChannel<IStorageItemsChangeEvent>(IndexedDBStorageDatabase.STORAGE_BROADCAST_CHANNEL)) : undefined;
this.whenConnected = this.connect();
@@ -327,16 +330,10 @@ export class IndexedDBStorageDatabase extends Disposable implements IIndexedDBSt
// Check for storage change events from other
// windows/tabs via `BroadcastChannel` mechanisms.
if (this.broadcastChannel) {
- const listener = (event: MessageEvent) => {
- if (isStorageItemsChangeEvent(event.data)) {
- this._onDidChangeItemsExternal.fire(event.data);
+ this._register(this.broadcastChannel.onDidReceiveData(data => {
+ if (isStorageItemsChangeEvent(data)) {
+ this._onDidChangeItemsExternal.fire(data);
}
- };
-
- this.broadcastChannel.addEventListener('message', listener);
- this._register(toDisposable(() => {
- this.broadcastChannel?.removeEventListener('message', listener);
- this.broadcastChannel?.close();
}));
}
}
@@ -380,7 +377,7 @@ export class IndexedDBStorageDatabase extends Disposable implements IIndexedDBSt
deleted: request.delete
};
- this.broadcastChannel.postMessage(event);
+ this.broadcastChannel.postData(event);
}
}
diff --git a/src/vs/workbench/services/storage/test/browser/storageService.test.ts b/src/vs/workbench/services/storage/test/browser/storageService.test.ts
index 58d1c19d3cb..8491d3f9301 100644
--- a/src/vs/workbench/services/storage/test/browser/storageService.test.ts
+++ b/src/vs/workbench/services/storage/test/browser/storageService.test.ts
@@ -16,9 +16,11 @@ import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFil
import { NullLogService } from 'vs/platform/log/common/log';
import { StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { createSuite } from 'vs/platform/storage/test/common/storageService.test';
-import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile';
+import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService';
+import { IUserDataProfile, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
import { BrowserStorageService, IndexedDBStorageDatabase } from 'vs/workbench/services/storage/browser/storageService';
import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService';
+import { TestEnvironmentService } from 'vs/workbench/test/browser/workbenchTestServices';
async function createStorageService(): Promise<[DisposableStore, BrowserStorageService]> {
const disposables = new DisposableStore();
@@ -45,7 +47,7 @@ async function createStorageService(): Promise<[DisposableStore, BrowserStorageS
extensionsResource: joinPath(inMemoryExtraProfileRoot, 'extensionsResource')
};
- const storageService = disposables.add(new BrowserStorageService({ id: 'workspace-storage-test' }, new UserDataProfileService(inMemoryExtraProfile), logService));
+ const storageService = disposables.add(new BrowserStorageService({ id: 'workspace-storage-test' }, new UserDataProfileService(inMemoryExtraProfile, new UserDataProfilesService(TestEnvironmentService, fileService, new UriIdentityService(fileService), logService)), logService));
await storageService.initialize();
diff --git a/src/vs/workbench/services/telemetry/browser/telemetryService.ts b/src/vs/workbench/services/telemetry/browser/telemetryService.ts
index 892f318d293..3ad179570b6 100644
--- a/src/vs/workbench/services/telemetry/browser/telemetryService.ts
+++ b/src/vs/workbench/services/telemetry/browser/telemetryService.ts
@@ -11,7 +11,6 @@ import { ILoggerService } from 'vs/platform/log/common/log';
import { IProductService } from 'vs/platform/product/common/productService';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { OneDataSystemWebAppender } from 'vs/platform/telemetry/browser/1dsAppender';
-import { WebAppInsightsAppender } from 'vs/platform/telemetry/browser/appInsightsAppender';
import { ClassifiedEvent, GDPRClassification, StrictPropertyCheck } from 'vs/platform/telemetry/common/gdprTypings';
import { ITelemetryData, ITelemetryInfo, ITelemetryService, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry';
import { TelemetryLogAppender } from 'vs/platform/telemetry/common/telemetryLogAppender';
@@ -38,17 +37,11 @@ export class TelemetryService extends Disposable implements ITelemetryService {
) {
super();
- if (supportsTelemetry(productService, environmentService) && productService.aiConfig?.asimovKey && productService.aiConfig?.ariaKey) {
+ if (supportsTelemetry(productService, environmentService) && productService.aiConfig?.ariaKey) {
// If remote server is present send telemetry through that, else use the client side appender
- const internalTesting = configurationService.getValue<boolean>('telemetry.internalTesting');
const appenders = [];
- if (internalTesting || productService.aiConfig?.preferAria) {
- const telemetryProvider: ITelemetryAppender = remoteAgentService.getConnection() !== null ? { log: remoteAgentService.logTelemetry.bind(remoteAgentService), flush: remoteAgentService.flushTelemetry.bind(remoteAgentService) } : new OneDataSystemWebAppender('monacoworkbench', null, productService.aiConfig?.ariaKey);
- appenders.push(telemetryProvider);
- } else {
- const telemetryProvider: ITelemetryAppender = remoteAgentService.getConnection() !== null ? { log: remoteAgentService.logTelemetry.bind(remoteAgentService), flush: remoteAgentService.flushTelemetry.bind(remoteAgentService) } : new WebAppInsightsAppender('monacoworkbench', productService.aiConfig?.asimovKey);
- appenders.push(telemetryProvider);
- }
+ const telemetryProvider: ITelemetryAppender = remoteAgentService.getConnection() !== null ? { log: remoteAgentService.logTelemetry.bind(remoteAgentService), flush: remoteAgentService.flushTelemetry.bind(remoteAgentService) } : new OneDataSystemWebAppender(configurationService, 'monacoworkbench', null, productService.aiConfig?.ariaKey);
+ appenders.push(telemetryProvider);
appenders.push(new TelemetryLogAppender(loggerService, environmentService));
const config: ITelemetryServiceConfig = {
appenders,
diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts
index 2488577017b..3b1578dc366 100644
--- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts
+++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts
@@ -4,19 +4,12 @@
*--------------------------------------------------------------------------------------------*/
import { Disposable } from 'vs/base/common/lifecycle';
-import { joinPath } from 'vs/base/common/resources';
-import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
-import { ILocalExtension, Metadata } from 'vs/platform/extensionManagement/common/extensionManagement';
-import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService';
-import { ExtensionType } from 'vs/platform/extensions/common/extensions';
-import { IFileService } from 'vs/platform/files/common/files';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
-import { DidChangeProfilesEvent, EXTENSIONS_RESOURCE_NAME, IUserDataProfile, IUserDataProfilesService, UseDefaultProfileFlags, WorkspaceIdentifier } from 'vs/platform/userDataProfile/common/userDataProfile';
+import { DidChangeProfilesEvent, IUserDataProfile, IUserDataProfilesService, UseDefaultProfileFlags, WorkspaceIdentifier } from 'vs/platform/userDataProfile/common/userDataProfile';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
-import { IExtensionManagementServerService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IUserDataProfileManagementService, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
@@ -27,10 +20,6 @@ export class UserDataProfileManagementService extends Disposable implements IUse
constructor(
@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,
@IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService,
- @IFileService private readonly fileService: IFileService,
- @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
- @IWorkbenchExtensionManagementService private readonly extensionManagementService: IWorkbenchExtensionManagementService,
- @IExtensionsProfileScannerService private readonly extensionsProfileScannerService: IExtensionsProfileScannerService,
@IHostService private readonly hostService: IHostService,
@IDialogService private readonly dialogService: IDialogService,
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
@@ -41,47 +30,17 @@ export class UserDataProfileManagementService extends Disposable implements IUse
this._register(userDataProfilesService.onDidChangeProfiles(e => this.onDidChangeProfiles(e)));
}
- private async checkAndCreateExtensionsProfileResource(): Promise<URI> {
- if (this.userDataProfileService.currentProfile.extensionsResource) {
- return this.userDataProfileService.currentProfile.extensionsResource;
- }
- if (!this.userDataProfilesService.defaultProfile.extensionsResource) {
- // Extensions profile is not yet created for default profile, create it now
- return this.createDefaultExtensionsProfile(joinPath(this.userDataProfilesService.defaultProfile.location, EXTENSIONS_RESOURCE_NAME));
- }
- throw new Error('Invalid Profile');
- }
-
private onDidChangeProfiles(e: DidChangeProfilesEvent): void {
if (e.removed.some(profile => profile.id === this.userDataProfileService.currentProfile.id)) {
this.enterProfile(this.userDataProfilesService.defaultProfile, false, localize('reload message when removed', "The current profile has been removed. Please reload to switch back to default profile"));
return;
}
- if (this.userDataProfileService.currentProfile.isDefault) {
- this.userDataProfileService.updateCurrentProfile(this.userDataProfilesService.defaultProfile, false);
- return;
- }
}
async createAndEnterProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, fromExisting?: boolean): Promise<IUserDataProfile> {
- const workspaceIdentifier = this.getWorkspaceIdentifier();
- const promises: Promise<any>[] = [];
- const newProfile = this.userDataProfilesService.newProfile(name, useDefaultFlags);
- await this.fileService.createFolder(newProfile.location);
- const extensionsProfileResourcePromise = this.checkAndCreateExtensionsProfileResource();
- promises.push(extensionsProfileResourcePromise);
- if (fromExisting) {
- // Storage copy is handled by storage service while entering profile
- promises.push(this.fileService.copy(this.userDataProfileService.currentProfile.settingsResource, newProfile.settingsResource));
- promises.push((async () => this.fileService.copy(await extensionsProfileResourcePromise, newProfile.extensionsResource))());
- promises.push(this.fileService.copy(this.userDataProfileService.currentProfile.keybindingsResource, newProfile.keybindingsResource));
- promises.push(this.fileService.copy(this.userDataProfileService.currentProfile.tasksResource, newProfile.tasksResource));
- promises.push(this.fileService.copy(this.userDataProfileService.currentProfile.snippetsHome, newProfile.snippetsHome));
- }
- await Promise.allSettled(promises);
- const createdProfile = await this.userDataProfilesService.createProfile(newProfile, workspaceIdentifier);
- await this.enterProfile(createdProfile, !!fromExisting);
- return createdProfile;
+ const profile = await this.userDataProfilesService.createProfile(name, useDefaultFlags, this.getWorkspaceIdentifier());
+ await this.enterProfile(profile, !!fromExisting);
+ return profile;
}
async removeProfile(profile: IUserDataProfile): Promise<void> {
@@ -94,11 +53,7 @@ export class UserDataProfileManagementService extends Disposable implements IUse
if (profile.id === this.userDataProfileService.currentProfile.id) {
throw new Error(localize('cannotDeleteCurrentProfile', "Cannot delete the current profile"));
}
- const defaultExtensionsResourceToDelete = this.userDataProfilesService.profiles.length === 2 ? this.userDataProfilesService.defaultProfile.extensionsResource : undefined;
await this.userDataProfilesService.removeProfile(profile);
- if (defaultExtensionsResourceToDelete) {
- try { await this.fileService.del(defaultExtensionsResourceToDelete); } catch (error) { /* ignore */ }
- }
}
async switchProfile(profile: IUserDataProfile): Promise<void> {
@@ -141,15 +96,6 @@ export class UserDataProfileManagementService extends Disposable implements IUse
await this.userDataProfileService.updateCurrentProfile(profile, preserveData);
await this.extensionService.startExtensionHosts();
}
-
- private async createDefaultExtensionsProfile(extensionsProfileResource: URI): Promise<URI> {
- try { await this.fileService.del(extensionsProfileResource); } catch (error) { /* ignore */ }
- const extensionManagementService = this.extensionManagementServerService.localExtensionManagementServer?.extensionManagementService ?? this.extensionManagementService;
- const userExtensions = await extensionManagementService.getInstalled(ExtensionType.User);
- const extensions: [ILocalExtension, Metadata | undefined][] = await Promise.all(userExtensions.map(async e => ([e, await this.extensionManagementService.getMetadata(e)])));
- await this.extensionsProfileScannerService.addExtensionsToProfile(extensions, extensionsProfileResource);
- return extensionsProfileResource;
- }
}
registerSingleton(IUserDataProfileManagementService, UserDataProfileManagementService);
diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts
index a293ec712b1..6e09fd1d153 100644
--- a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts
+++ b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts
@@ -10,7 +10,7 @@ import { MenuId } from 'vs/platform/actions/common/actions';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IUserDataProfile, PROFILES_ENABLEMENT_CONFIG, UseDefaultProfileFlags } from 'vs/platform/userDataProfile/common/userDataProfile';
import { ContextKeyDefinedExpr, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
-import { IsWebContext, ProductQualityContext } from 'vs/platform/contextkey/common/contextkeys';
+import { ProductQualityContext } from 'vs/platform/contextkey/common/contextkeys';
export interface DidChangeUserDataProfileEvent {
readonly preserveData: boolean;
@@ -60,6 +60,7 @@ export interface IUserDataProfileImportExportService {
exportProfile(options?: ProfileCreationOptions): Promise<IUserDataProfileTemplate>;
importProfile(profile: IUserDataProfileTemplate): Promise<void>;
+ setProfile(profile: IUserDataProfileTemplate): Promise<void>;
}
export interface IResourceProfile {
@@ -72,4 +73,4 @@ export const PROFILES_TTILE = { value: localize('settings profiles', "Settings P
export const PROFILES_CATEGORY = PROFILES_TTILE.value;
export const PROFILE_EXTENSION = 'code-profile';
export const PROFILE_FILTER = [{ name: localize('profile', "Settings Profile"), extensions: [PROFILE_EXTENSION] }];
-export const PROFILES_ENABLEMENT_CONTEXT = ContextKeyExpr.and(ProductQualityContext.notEqualsTo('stable'), IsWebContext.negate(), ContextKeyDefinedExpr.create(`config.${PROFILES_ENABLEMENT_CONFIG}`));
+export const PROFILES_ENABLEMENT_CONTEXT = ContextKeyExpr.and(ProductQualityContext.notEqualsTo('stable'), ContextKeyDefinedExpr.create(`config.${PROFILES_ENABLEMENT_CONFIG}`));
diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfileImportExportService.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfileImportExportService.ts
index 7403079c1ef..8b9b4b76309 100644
--- a/src/vs/workbench/services/userDataProfile/common/userDataProfileImportExportService.ts
+++ b/src/vs/workbench/services/userDataProfile/common/userDataProfileImportExportService.ts
@@ -56,7 +56,7 @@ export class UserDataProfileImportExportService implements IUserDataProfileImpor
await this.progressService.withProgress({
location: ProgressLocation.Notification,
- title: localize('profiles.applying', "{0}: Importing...", PROFILES_CATEGORY),
+ title: localize('profiles.importing', "{0}: Importing...", PROFILES_CATEGORY),
}, async progress => {
await this.userDataProfileManagementService.createAndEnterProfile(name);
if (profileTemplate.settings) {
@@ -70,7 +70,25 @@ export class UserDataProfileImportExportService implements IUserDataProfileImpor
}
});
- this.notificationService.info(localize('applied profile', "{0}: Imported successfully.", PROFILES_CATEGORY));
+ this.notificationService.info(localize('imported profile', "{0}: Imported successfully.", PROFILES_CATEGORY));
+ }
+
+ async setProfile(profile: IUserDataProfileTemplate): Promise<void> {
+ await this.progressService.withProgress({
+ location: ProgressLocation.Notification,
+ title: localize('profiles.applying', "{0}: Applying...", PROFILES_CATEGORY),
+ }, async progress => {
+ if (profile.settings) {
+ await this.settingsProfile.applyProfile(profile.settings);
+ }
+ if (profile.globalState) {
+ await this.globalStateProfile.applyProfile(profile.globalState);
+ }
+ if (profile.extensions) {
+ await this.extensionsProfile.applyProfile(profile.extensions);
+ }
+ });
+ this.notificationService.info(localize('applied profile', "{0}: Applied successfully.", PROFILES_CATEGORY));
}
}
diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts
index 96fa879239f..e03387a0b29 100644
--- a/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts
+++ b/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts
@@ -6,7 +6,7 @@
import { Promises } from 'vs/base/common/async';
import { Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
-import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile';
+import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
import { DidChangeUserDataProfileEvent, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
export class UserDataProfileService extends Disposable implements IUserDataProfileService {
@@ -19,17 +19,29 @@ export class UserDataProfileService extends Disposable implements IUserDataProfi
private _currentProfile: IUserDataProfile;
get currentProfile(): IUserDataProfile { return this._currentProfile; }
- constructor(currentProfile: IUserDataProfile) {
+ constructor(
+ currentProfile: IUserDataProfile,
+ @IUserDataProfilesService userDataProfilesService: IUserDataProfilesService
+ ) {
super();
this._currentProfile = currentProfile;
+ this._register(userDataProfilesService.onDidChangeProfiles(() => {
+ /**
+ * If the current profile is default profile, then reset it because,
+ * In Desktop the extensions resource will be set/unset in the default profile when profiles are changed.
+ */
+ if (this._currentProfile.isDefault) {
+ this._currentProfile = userDataProfilesService.defaultProfile;
+ }
+ }));
}
async updateCurrentProfile(userDataProfile: IUserDataProfile, preserveData: boolean): Promise<void> {
- const previous = this._currentProfile;
- this._currentProfile = userDataProfile;
- if (this._currentProfile.id === previous.id) {
+ if (this._currentProfile.id === userDataProfile.id) {
return;
}
+ const previous = this._currentProfile;
+ this._currentProfile = userDataProfile;
const joiners: Promise<void>[] = [];
this._onDidChangeCurrentProfile.fire({
preserveData,
diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts
index c45f26e708a..b910e0408c4 100644
--- a/src/vs/workbench/test/browser/workbenchTestServices.ts
+++ b/src/vs/workbench/test/browser/workbenchTestServices.ts
@@ -35,7 +35,7 @@ import { TestConfigurationService } from 'vs/platform/configuration/test/common/
import { MenuBarVisibility, IWindowOpenable, IOpenWindowOptions, IOpenEmptyWindowOptions } from 'vs/platform/window/common/window';
import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
-import { IThemeService } from 'vs/platform/theme/common/themeService';
+import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService';
import { ITextResourceConfigurationService, ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration';
import { IPosition, Position as EditorPosition } from 'vs/editor/common/core/position';
@@ -122,7 +122,7 @@ import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEd
import { IEnterWorkspaceResult, IRecent, IRecentlyOpened, IWorkspaceFolderCreationData, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspaceTrustManagementService, IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust';
import { TestWorkspaceTrustManagementService, TestWorkspaceTrustRequestService } from 'vs/workbench/services/workspaces/test/common/testWorkspaceTrustService';
-import { IExtensionTerminalProfile, IShellLaunchConfig, ITerminalProfile, TerminalLocation, TerminalShellType } from 'vs/platform/terminal/common/terminal';
+import { IExtensionTerminalProfile, IShellLaunchConfig, ITerminalProfile, TerminalIcon, TerminalLocation, TerminalShellType } from 'vs/platform/terminal/common/terminal';
import { ICreateTerminalOptions, IDeserializedTerminalEditorInput, ITerminalEditorService, ITerminalGroup, ITerminalGroupService, ITerminalInstance, ITerminalInstanceService, TerminalEditorLocation } from 'vs/workbench/contrib/terminal/browser/terminal';
import { assertIsDefined, isArray } from 'vs/base/common/types';
import { IRegisterContributedProfileArgs, IShellLaunchConfigResolveOptions, ITerminalBackend, ITerminalProfileProvider, ITerminalProfileResolverService, ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal';
@@ -163,8 +163,9 @@ import { ILayoutOffsetInfo } from 'vs/platform/layout/browser/layoutService';
import { IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService';
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
-import { EnablementState, IExtensionManagementServer, IScannedExtension, IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService, ScanOptions } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
+import { EnablementState, IExtensionManagementServer, IScannedExtension, IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { InstallVSIXOptions, ILocalExtension, IGalleryExtension, InstallOptions, IExtensionIdentifier, UninstallOptions, IExtensionsControlManifest, IGalleryMetadata, IExtensionManagementParticipant } from 'vs/platform/extensionManagement/common/extensionManagement';
+import { Codicon } from 'vs/base/common/codicons';
export function createFileEditorInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput {
return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined, undefined, undefined, undefined, undefined);
@@ -285,9 +286,10 @@ export function workbenchInstantiationService(
instantiationService.stub(IModelService, disposables.add(instantiationService.createInstance(ModelService)));
const fileService = overrides?.fileService ? overrides.fileService(instantiationService) : new TestFileService();
instantiationService.stub(IFileService, fileService);
- const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, new NullLogService()));
- instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile));
- instantiationService.stub(IUriIdentityService, new UriIdentityService(fileService));
+ const uriIdentityService = new UriIdentityService(fileService);
+ instantiationService.stub(IUriIdentityService, uriIdentityService);
+ const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, new NullLogService()));
+ instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService));
instantiationService.stub(IWorkingCopyBackupService, new TestWorkingCopyBackupService());
instantiationService.stub(ITelemetryService, NullTelemetryService);
instantiationService.stub(INotificationService, new TestNotificationService());
@@ -517,6 +519,10 @@ export class TestMenuService implements IMenuService {
getActions: () => []
};
}
+
+ resetHiddenStates(): void {
+ // nothing
+ }
}
export class TestHistoryService implements IHistoryService {
@@ -852,7 +858,6 @@ export class TestEditorGroupView implements IEditorGroupView {
titleHeight!: IEditorGroupTitleHeight;
isEmpty = true;
- isMinimized = false;
onWillDispose: Event<void> = Event.None;
onDidModelChange: Event<IGroupModelChangeEvent> = Event.None;
@@ -1863,6 +1868,7 @@ export class TestTerminalProfileResolverService implements ITerminalProfileResol
async getDefaultProfile(options: IShellLaunchConfigResolveOptions): Promise<ITerminalProfile> { return { path: '/default', profileName: 'Default', isDefault: true }; }
async getDefaultShell(options: IShellLaunchConfigResolveOptions): Promise<string> { return '/default'; }
async getDefaultShellArgs(options: IShellLaunchConfigResolveOptions): Promise<string | string[]> { return []; }
+ getDefaultIcon(): TerminalIcon & ThemeIcon { return Codicon.terminal; }
async getEnvironment(): Promise<IProcessEnvironment> { return env; }
getSafeConfigValue(key: string, os: OperatingSystem): unknown | undefined { return undefined; }
getSafeConfigValueFullKey(key: string): unknown | undefined { return undefined; }
@@ -2002,9 +2008,13 @@ export class TestWorkbenchExtensionManagementService implements IWorkbenchExtens
export class TestWebExtensionsScannerService implements IWebExtensionsScannerService {
_serviceBrand: undefined;
+ onDidChangeProfileExtensions = Event.None;
async scanSystemExtensions(): Promise<IExtension[]> { return []; }
- async scanUserExtensions(options?: ScanOptions | undefined): Promise<IScannedExtension[]> { return []; }
+ async scanUserExtensions(): Promise<IScannedExtension[]> { return []; }
async scanExtensionsUnderDevelopment(): Promise<IExtension[]> { return []; }
+ async copyExtensions(): Promise<void> {
+ throw new Error('Method not implemented.');
+ }
scanExistingExtension(extensionLocation: URI, extensionType: ExtensionType): Promise<IScannedExtension | null> {
throw new Error('Method not implemented.');
}
@@ -2014,7 +2024,7 @@ export class TestWebExtensionsScannerService implements IWebExtensionsScannerSer
addExtensionFromGallery(galleryExtension: IGalleryExtension, metadata?: Partial<IGalleryMetadata & { isApplicationScoped: boolean; isMachineScoped: boolean; isBuiltin: boolean; isSystem: boolean; updated: boolean; preRelease: boolean; installedTimestamp: number }> | undefined): Promise<IExtension> {
throw new Error('Method not implemented.');
}
- removeExtension(identifier: IExtensionIdentifier, version?: string | undefined): Promise<void> {
+ removeExtension(): Promise<void> {
throw new Error('Method not implemented.');
}
scanMetadata(extensionLocation: URI): Promise<Partial<IGalleryMetadata & { isApplicationScoped: boolean; isMachineScoped: boolean; isBuiltin: boolean; isSystem: boolean; updated: boolean; preRelease: boolean; installedTimestamp: number }> | undefined> {
diff --git a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts
index ded96d4e010..f4fce1346e2 100644
--- a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts
+++ b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts
@@ -53,6 +53,8 @@ import { FileService } from 'vs/platform/files/common/fileService';
import { joinPath } from 'vs/base/common/resources';
import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService';
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
+import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService';
+import { VSBuffer } from 'vs/base/common/buffer';
const args = parseArgs(process.argv, OPTIONS);
@@ -271,8 +273,8 @@ export class TestNativeHostService implements INativeHostService {
async writeClipboardText(text: string, type?: 'selection' | 'clipboard' | undefined): Promise<void> { }
async readClipboardFindText(): Promise<string> { return ''; }
async writeClipboardFindText(text: string): Promise<void> { }
- async writeClipboardBuffer(format: string, buffer: Uint8Array, type?: 'selection' | 'clipboard' | undefined): Promise<void> { }
- async readClipboardBuffer(format: string): Promise<Uint8Array> { return Uint8Array.from([]); }
+ async writeClipboardBuffer(format: string, buffer: VSBuffer, type?: 'selection' | 'clipboard' | undefined): Promise<void> { }
+ async readClipboardBuffer(format: string): Promise<VSBuffer> { return VSBuffer.wrap(Uint8Array.from([])); }
async hasClipboard(format: string, type?: 'selection' | 'clipboard' | undefined): Promise<boolean> { return false; }
async sendInputEvent(event: MouseInputEvent): Promise<void> { }
async windowsGetStringRegKey(hive: 'HKEY_CURRENT_USER' | 'HKEY_LOCAL_MACHINE' | 'HKEY_CLASSES_ROOT' | 'HKEY_USERS' | 'HKEY_CURRENT_CONFIG', path: string, name: string): Promise<string | undefined> { return undefined; }
@@ -289,8 +291,9 @@ export function workbenchInstantiationService(disposables = new DisposableStore(
instantiationService.stub(INativeEnvironmentService, TestEnvironmentService);
instantiationService.stub(IWorkbenchEnvironmentService, TestEnvironmentService);
instantiationService.stub(INativeWorkbenchEnvironmentService, TestEnvironmentService);
- const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(TestEnvironmentService, new FileService(new NullLogService()), new NullLogService()));
- instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile));
+ const fileService = new FileService(new NullLogService());
+ const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(TestEnvironmentService, fileService, new UriIdentityService(fileService), new NullLogService()));
+ instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService));
return instantiationService;
}
diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts
index 3319bbe6143..f3deec51ef4 100644
--- a/src/vs/workbench/workbench.common.main.ts
+++ b/src/vs/workbench/workbench.common.main.ts
@@ -52,6 +52,7 @@ import 'vs/workbench/browser/parts/views/viewsService';
//#region --- workbench services
+import 'vs/platform/actions/common/actions.contribution';
import 'vs/platform/undoRedo/common/undoRedoService';
import 'vs/workbench/services/extensions/browser/extensionUrlHandler';
import 'vs/workbench/services/keybinding/common/keybindingEditing';
@@ -76,7 +77,6 @@ import 'vs/workbench/services/commands/common/commandService';
import 'vs/workbench/services/themes/browser/workbenchThemeService';
import 'vs/workbench/services/label/common/labelService';
import 'vs/workbench/services/extensions/common/extensionManifestPropertiesService';
-import 'vs/workbench/services/extensionManagement/browser/webExtensionsScannerService';
import 'vs/workbench/services/extensionManagement/browser/extensionEnablementService';
import 'vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService';
import 'vs/workbench/services/extensionRecommendations/common/extensionIgnoredRecommendationsService';
@@ -117,8 +117,6 @@ import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyServ
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration';
import { TextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService';
-import { IMenuService } from 'vs/platform/actions/common/actions';
-import { MenuService } from 'vs/platform/actions/common/menuService';
import { IDownloadService } from 'vs/platform/download/common/download';
import { DownloadService } from 'vs/platform/download/common/downloadService';
import { OpenerService } from 'vs/editor/browser/services/openerService';
@@ -141,7 +139,6 @@ registerSingleton(IMarkerDecorationsService, MarkerDecorationsService);
registerSingleton(IMarkerService, MarkerService, true);
registerSingleton(IContextKeyService, ContextKeyService);
registerSingleton(ITextResourceConfigurationService, TextResourceConfigurationService);
-registerSingleton(IMenuService, MenuService, true);
registerSingleton(IDownloadService, DownloadService, true);
registerSingleton(IOpenerService, OpenerService, true);
registerSingleton(IExtensionsProfileScannerService, ExtensionsProfileScannerService);
@@ -329,7 +326,7 @@ import 'vs/workbench/contrib/userDataSync/browser/userDataSync.contribution';
import 'vs/workbench/contrib/userDataProfile/browser/userDataProfile.contribution';
// Continue Edit Session
-import 'vs/workbench/contrib/sessionSync/browser/sessionSync.contribution';
+import 'vs/workbench/contrib/editSessions/browser/editSessions.contribution';
// Code Actions
import 'vs/workbench/contrib/codeActions/browser/codeActions.contribution';
diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts
index 6b7b44d4155..8831bf9469b 100644
--- a/src/vs/workbench/workbench.desktop.main.ts
+++ b/src/vs/workbench/workbench.desktop.main.ts
@@ -4,51 +4,157 @@
*--------------------------------------------------------------------------------------------*/
-// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-//
-// NOTE: Please do NOT register services here. Use `registerSingleton()`
-// from `workbench.common.main.ts` if the service is shared between
-// desktop and web or `workbench.sandbox.main.ts` if the service
-// is desktop only.
-//
-// The `node` & `electron-browser` layer is deprecated for workbench!
-//
-// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+// #######################################################################
+// ### ###
+// ### !!! PLEASE ADD COMMON IMPORTS INTO WORKBENCH.COMMON.MAIN.TS !!! ###
+// ### ###
+// #######################################################################
+//#region --- workbench common
-//#region --- workbench common & sandbox
+import 'vs/workbench/workbench.common.main';
-import 'vs/workbench/workbench.sandbox.main';
+//#endregion
+
+
+//#region --- workbench (desktop main)
+
+import 'vs/workbench/electron-sandbox/desktop.main';
+import 'vs/workbench/electron-sandbox/desktop.contribution';
+
+//#endregion
+
+
+//#region --- workbench parts
+
+import 'vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution';
//#endregion
//#region --- workbench services
+import 'vs/workbench/services/textfile/electron-sandbox/nativeTextFileService';
+import 'vs/workbench/services/dialogs/electron-sandbox/fileDialogService';
+import 'vs/workbench/services/workspaces/electron-sandbox/workspacesService';
+import 'vs/workbench/services/textMate/browser/nativeTextMateService';
+import 'vs/workbench/services/menubar/electron-sandbox/menubarService';
+import 'vs/workbench/services/issue/electron-sandbox/issueService';
+import 'vs/workbench/services/update/electron-sandbox/updateService';
+import 'vs/workbench/services/url/electron-sandbox/urlService';
+import 'vs/workbench/services/lifecycle/electron-sandbox/lifecycleService';
+import 'vs/workbench/services/title/electron-sandbox/titleService';
+import 'vs/workbench/services/host/electron-sandbox/nativeHostService';
+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';
+import 'vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementService';
+import 'vs/workbench/services/extensionManagement/electron-sandbox/extensionUrlTrustService';
+import 'vs/workbench/services/credentials/electron-sandbox/credentialsService';
+import 'vs/workbench/services/encryption/electron-sandbox/encryptionService';
+import 'vs/workbench/services/localization/electron-sandbox/languagePackService';
+import 'vs/workbench/services/telemetry/electron-sandbox/telemetryService';
+import 'vs/workbench/services/extensions/electron-sandbox/extensionHostStarter';
+import 'vs/platform/extensionManagement/electron-sandbox/extensionsScannerService';
+import 'vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService';
+import 'vs/workbench/services/extensionManagement/electron-sandbox/extensionTipsService';
+import 'vs/workbench/services/userDataSync/electron-sandbox/userDataSyncMachinesService';
+import 'vs/workbench/services/userDataSync/electron-sandbox/userDataSyncService';
+import 'vs/workbench/services/userDataSync/electron-sandbox/userDataSyncAccountService';
+import 'vs/workbench/services/userDataSync/electron-sandbox/userDataSyncStoreManagementService';
+import 'vs/workbench/services/userDataSync/electron-sandbox/userDataAutoSyncService';
+import 'vs/workbench/services/timer/electron-sandbox/timerService';
+import 'vs/workbench/services/environment/electron-sandbox/shellEnvironmentService';
+import 'vs/workbench/services/integrity/electron-sandbox/integrityService';
+import 'vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService';
+import 'vs/workbench/services/checksum/electron-sandbox/checksumService';
+import 'vs/platform/remote/electron-sandbox/sharedProcessTunnelService';
+import 'vs/workbench/services/tunnel/electron-sandbox/tunnelService';
+import 'vs/platform/diagnostics/electron-sandbox/diagnosticsService';
+import 'vs/platform/profiling/electron-sandbox/profilingService';
+import 'vs/platform/telemetry/electron-sandbox/customEndpointTelemetryService';
+import 'vs/workbench/services/files/electron-sandbox/elevatedFileService';
+import 'vs/workbench/services/search/electron-sandbox/searchService';
+import 'vs/workbench/services/workingCopy/electron-sandbox/workingCopyHistoryService';
+import 'vs/workbench/services/userDataSync/browser/userDataSyncEnablementService';
+import 'vs/workbench/services/extensions/electron-sandbox/sandboxExtensionService';
+
+import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
+import { IUserDataInitializationService, UserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit';
+
+registerSingleton(IUserDataInitializationService, UserDataInitializationService);
+
+//#endregion
+
+
+//#region --- workbench contributions
+
+// Logs
+import 'vs/workbench/contrib/logs/electron-sandbox/logs.contribution';
+
+// Localizations
+import 'vs/workbench/contrib/localization/electron-sandbox/localization.contribution';
+
+// Explorer
+import 'vs/workbench/contrib/files/electron-sandbox/files.contribution';
+import 'vs/workbench/contrib/files/electron-sandbox/fileActions.contribution';
+
+// CodeEditor Contributions
+import 'vs/workbench/contrib/codeEditor/electron-sandbox/codeEditor.contribution';
+
+// Debug
+import 'vs/workbench/contrib/debug/electron-sandbox/extensionHostDebugService';
+
+// Extensions Management
+import 'vs/workbench/contrib/extensions/electron-sandbox/extensions.contribution';
+
+// Issues
+import 'vs/workbench/contrib/issue/electron-sandbox/issue.contribution';
+
+// Remote
+import 'vs/workbench/contrib/remote/electron-sandbox/remote.contribution';
+
+// Configuration Exporter
+import 'vs/workbench/contrib/configExporter/electron-sandbox/configurationExportHelper.contribution';
+
+// Terminal
+import 'vs/workbench/contrib/terminal/electron-sandbox/terminal.contribution';
+
+// Themes Support
+import 'vs/workbench/contrib/themes/browser/themes.test.contribution';
+
+// User Data Sync
+import 'vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution';
+
+// Output
+import 'vs/workbench/contrib/output/electron-sandbox/outputChannelModelService';
+
+// Tags
+import 'vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService';
+import 'vs/workbench/contrib/tags/electron-sandbox/tags.contribution';
+
+// Performance
+import 'vs/workbench/contrib/performance/electron-sandbox/performance.contribution';
+
+// Tasks
+import 'vs/workbench/contrib/tasks/electron-sandbox/taskService';
+
+// External terminal
+import 'vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal.contribution';
+
+// Webview
+import 'vs/workbench/contrib/webview/electron-sandbox/webview.contribution';
-// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-//
-// NOTE: Please do NOT register services here. Use `registerSingleton()`
-// from `workbench.common.main.ts` if the service is shared between
-// desktop and web or `workbench.sandbox.main.ts` if the service
-// is desktop only.
-//
-// The `node` & `electron-browser` layer is deprecated for workbench!
-//
-// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-
-import 'vs/workbench/services/extensions/electron-browser/nativeExtensionService';
-
-// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-//
-// NOTE: Please do NOT register services here. Use `registerSingleton()`
-// from `workbench.common.main.ts` if the service is shared between
-// desktop and web or `workbench.sandbox.main.ts` if the service
-// is desktop only.
-//
-// The `node` & `electron-browser` layer is deprecated for workbench!
-//
-// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+// Splash
+import 'vs/workbench/contrib/splash/electron-sandbox/splash.contribution';
+// Local History
+import 'vs/workbench/contrib/localHistory/electron-sandbox/localHistory.contribution';
//#endregion
diff --git a/src/vs/workbench/workbench.desktop.sandbox.main.ts b/src/vs/workbench/workbench.desktop.sandbox.main.ts
deleted file mode 100644
index 894e6785212..00000000000
--- a/src/vs/workbench/workbench.desktop.sandbox.main.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-
-// #######################################################################
-// ### ###
-// ### !!! PLEASE ADD COMMON IMPORTS INTO WORKBENCH.COMMON.MAIN.TS !!! ###
-// ### ###
-// #######################################################################
-
-
-//#region --- workbench common & sandbox
-
-import 'vs/workbench/workbench.sandbox.main';
-
-//#endregion
-
-
-//#region --- workbench (desktop main)
-
-import 'vs/workbench/electron-sandbox/desktop.main';
-
-//#endregion
-
-
-//#region --- workbench services
-
-import 'vs/workbench/services/extensions/electron-sandbox/sandboxExtensionService';
-
-//#endregion
diff --git a/src/vs/workbench/workbench.sandbox.main.ts b/src/vs/workbench/workbench.sandbox.main.ts
deleted file mode 100644
index dd9ddd715b1..00000000000
--- a/src/vs/workbench/workbench.sandbox.main.ts
+++ /dev/null
@@ -1,159 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-
-// #######################################################################
-// ### ###
-// ### !!! PLEASE ADD COMMON IMPORTS INTO WORKBENCH.COMMON.MAIN.TS !!! ###
-// ### ###
-// #######################################################################
-
-//#region --- workbench common
-
-import 'vs/workbench/workbench.common.main';
-
-//#endregion
-
-
-//#region --- workbench (desktop main)
-
-import 'vs/workbench/electron-sandbox/desktop.main';
-import 'vs/workbench/electron-sandbox/desktop.contribution';
-
-//#endregion
-
-
-//#region --- workbench parts
-
-import 'vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution';
-
-//#endregion
-
-
-//#region --- workbench services
-
-import 'vs/workbench/services/textfile/electron-sandbox/nativeTextFileService';
-import 'vs/workbench/services/dialogs/electron-sandbox/fileDialogService';
-import 'vs/workbench/services/workspaces/electron-sandbox/workspacesService';
-import 'vs/workbench/services/textMate/browser/nativeTextMateService';
-import 'vs/workbench/services/menubar/electron-sandbox/menubarService';
-import 'vs/workbench/services/issue/electron-sandbox/issueService';
-import 'vs/workbench/services/update/electron-sandbox/updateService';
-import 'vs/workbench/services/url/electron-sandbox/urlService';
-import 'vs/workbench/services/lifecycle/electron-sandbox/lifecycleService';
-import 'vs/workbench/services/title/electron-sandbox/titleService';
-import 'vs/workbench/services/host/electron-sandbox/nativeHostService';
-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';
-import 'vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementService';
-import 'vs/workbench/services/extensionManagement/electron-sandbox/extensionUrlTrustService';
-import 'vs/workbench/services/credentials/electron-sandbox/credentialsService';
-import 'vs/workbench/services/encryption/electron-sandbox/encryptionService';
-import 'vs/workbench/services/localization/electron-sandbox/languagePackService';
-import 'vs/workbench/services/telemetry/electron-sandbox/telemetryService';
-import 'vs/workbench/services/extensions/electron-sandbox/extensionHostStarter';
-import 'vs/platform/extensionManagement/electron-sandbox/extensionsScannerService';
-import 'vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService';
-import 'vs/workbench/services/extensionManagement/electron-sandbox/extensionTipsService';
-import 'vs/workbench/services/userDataSync/electron-sandbox/userDataSyncMachinesService';
-import 'vs/workbench/services/userDataSync/electron-sandbox/userDataSyncService';
-import 'vs/workbench/services/userDataSync/electron-sandbox/userDataSyncAccountService';
-import 'vs/workbench/services/userDataSync/electron-sandbox/userDataSyncStoreManagementService';
-import 'vs/workbench/services/userDataSync/electron-sandbox/userDataAutoSyncService';
-import 'vs/workbench/services/timer/electron-sandbox/timerService';
-import 'vs/workbench/services/environment/electron-sandbox/shellEnvironmentService';
-import 'vs/workbench/services/integrity/electron-sandbox/integrityService';
-import 'vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService';
-import 'vs/workbench/services/checksum/electron-sandbox/checksumService';
-import 'vs/platform/remote/electron-sandbox/sharedProcessTunnelService';
-import 'vs/workbench/services/tunnel/electron-sandbox/tunnelService';
-import 'vs/platform/diagnostics/electron-sandbox/diagnosticsService';
-import 'vs/platform/profiling/electron-sandbox/profilingService';
-import 'vs/platform/telemetry/electron-sandbox/customEndpointTelemetryService';
-import 'vs/workbench/services/files/electron-sandbox/elevatedFileService';
-import 'vs/workbench/services/search/electron-sandbox/searchService';
-import 'vs/workbench/services/workingCopy/electron-sandbox/workingCopyHistoryService';
-import 'vs/workbench/services/userDataSync/browser/userDataSyncEnablementService';
-
-import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
-import { IUserDataInitializationService, UserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit';
-
-registerSingleton(IUserDataInitializationService, UserDataInitializationService);
-
-//#endregion
-
-
-//#region --- workbench contributions
-
-// Logs
-import 'vs/workbench/contrib/logs/electron-sandbox/logs.contribution';
-
-// Localizations
-import 'vs/workbench/contrib/localization/electron-sandbox/localization.contribution';
-
-// Explorer
-import 'vs/workbench/contrib/files/electron-sandbox/files.contribution';
-import 'vs/workbench/contrib/files/electron-sandbox/fileActions.contribution';
-
-// CodeEditor Contributions
-import 'vs/workbench/contrib/codeEditor/electron-sandbox/codeEditor.contribution';
-
-// Debug
-import 'vs/workbench/contrib/debug/electron-sandbox/extensionHostDebugService';
-
-// Extensions Management
-import 'vs/workbench/contrib/extensions/electron-sandbox/extensions.contribution';
-
-// Issues
-import 'vs/workbench/contrib/issue/electron-sandbox/issue.contribution';
-
-// Remote
-import 'vs/workbench/contrib/remote/electron-sandbox/remote.contribution';
-
-// Configuration Exporter
-import 'vs/workbench/contrib/configExporter/electron-sandbox/configurationExportHelper.contribution';
-
-// Terminal
-import 'vs/workbench/contrib/terminal/electron-sandbox/terminal.contribution';
-
-// Themes Support
-import 'vs/workbench/contrib/themes/browser/themes.test.contribution';
-
-// User Data Sync
-import 'vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution';
-
-// Output
-import 'vs/workbench/contrib/output/electron-sandbox/outputChannelModelService';
-
-// Tags
-import 'vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService';
-import 'vs/workbench/contrib/tags/electron-sandbox/tags.contribution';
-
-// Performance
-import 'vs/workbench/contrib/performance/electron-sandbox/performance.contribution';
-
-// Tasks
-import 'vs/workbench/contrib/tasks/electron-sandbox/taskService';
-
-// External terminal
-import 'vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal.contribution';
-
-// Webview
-import 'vs/workbench/contrib/webview/electron-sandbox/webview.contribution';
-
-// Splash
-import 'vs/workbench/contrib/splash/electron-sandbox/splash.contribution';
-
-// Local History
-import 'vs/workbench/contrib/localHistory/electron-sandbox/localHistory.contribution';
-
-//#endregion
diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts
index f0833043d64..a71d4de98e3 100644
--- a/src/vs/workbench/workbench.web.main.ts
+++ b/src/vs/workbench/workbench.web.main.ts
@@ -40,6 +40,7 @@ import 'vs/workbench/services/search/browser/searchService';
import 'vs/workbench/services/textfile/browser/browserTextFileService';
import 'vs/workbench/services/keybinding/browser/keyboardLayoutService';
import 'vs/workbench/services/extensions/browser/extensionService';
+import 'vs/workbench/services/extensionManagement/browser/webExtensionsScannerService';
import 'vs/workbench/services/extensionManagement/common/extensionManagementServerService';
import 'vs/workbench/services/extensionManagement/browser/extensionUrlTrustService';
import 'vs/workbench/services/telemetry/browser/telemetryService';