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
path: root/src/vs
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
parent268c941bf0fd8255d4dd7c106c22e9b911772916 (diff)
parent4404dc63561a286e13f9ba5a669e5403a367425a (diff)
Merge branch 'main' into joh/cellUrijoh/cellUri
Diffstat (limited to 'src/vs')
-rw-r--r--src/vs/base/browser/broadcast.ts69
-rw-r--r--src/vs/base/browser/ui/button/button.css22
-rw-r--r--src/vs/base/browser/ui/button/button.ts23
-rw-r--r--src/vs/base/browser/ui/codicons/codicon/codicon.ttfbin71980 -> 72116 bytes
-rw-r--r--src/vs/base/browser/ui/grid/grid.ts10
-rw-r--r--src/vs/base/browser/ui/grid/gridview.ts25
-rw-r--r--src/vs/base/browser/ui/list/listWidget.ts17
-rw-r--r--src/vs/base/browser/ui/resizable/resizable.ts (renamed from src/vs/editor/contrib/suggest/browser/resizable.ts)0
-rw-r--r--src/vs/base/browser/ui/splitview/splitview.ts17
-rw-r--r--src/vs/base/browser/ui/tree/abstractTree.ts6
-rw-r--r--src/vs/base/common/arrays.ts12
-rw-r--r--src/vs/base/common/codicons.ts3
-rw-r--r--src/vs/base/common/collections.ts14
-rw-r--r--src/vs/base/common/event.ts50
-rw-r--r--src/vs/base/common/observable.ts30
-rw-r--r--src/vs/base/common/observableImpl/autorun.ts167
-rw-r--r--src/vs/base/common/observableImpl/base.ts244
-rw-r--r--src/vs/base/common/observableImpl/derived.ts167
-rw-r--r--src/vs/base/common/observableImpl/logging.ts312
-rw-r--r--src/vs/base/common/observableImpl/utils.ts281
-rw-r--r--src/vs/base/common/observableValue.ts11
-rw-r--r--src/vs/base/common/product.ts4
-rw-r--r--src/vs/base/common/types.ts13
-rw-r--r--src/vs/base/node/ps.ts5
-rw-r--r--src/vs/base/parts/ipc/node/ipc.net.ts33
-rw-r--r--src/vs/base/parts/quickinput/browser/quickInput.ts11
-rw-r--r--src/vs/base/parts/quickinput/browser/quickInputList.ts50
-rw-r--r--src/vs/base/parts/quickinput/common/quickInput.ts7
-rw-r--r--src/vs/base/parts/sandbox/electron-browser/preload.js8
-rw-r--r--src/vs/base/test/common/arrays.test.ts13
-rw-r--r--src/vs/base/test/common/collections.test.ts21
-rw-r--r--src/vs/base/test/common/event.test.ts30
-rw-r--r--src/vs/base/test/common/map.test.ts4
-rw-r--r--src/vs/base/test/common/observable.test.ts507
-rw-r--r--src/vs/code/electron-browser/sharedProcess/contrib/extensionsCleaner.ts4
-rw-r--r--src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts20
-rw-r--r--src/vs/code/electron-browser/workbench/workbench.html19
-rw-r--r--src/vs/code/electron-browser/workbench/workbench.js213
-rw-r--r--src/vs/code/electron-main/app.ts17
-rw-r--r--src/vs/code/electron-sandbox/issue/issueReporterMain.ts5
-rw-r--r--src/vs/code/electron-sandbox/issue/issueReporterModel.ts12
-rw-r--r--src/vs/code/electron-sandbox/workbench/workbench.js6
-rw-r--r--src/vs/code/node/cliProcessMain.ts10
-rw-r--r--src/vs/code/test/electron-sandbox/issue/testReporterModel.test.ts42
-rw-r--r--src/vs/editor/browser/config/migrateOptions.ts11
-rw-r--r--src/vs/editor/browser/services/abstractCodeEditorService.ts22
-rw-r--r--src/vs/editor/browser/services/bulkEditService.ts64
-rw-r--r--src/vs/editor/browser/services/codeEditorService.ts6
-rw-r--r--src/vs/editor/browser/widget/codeEditorWidget.ts26
-rw-r--r--src/vs/editor/common/config/editorOptions.ts22
-rw-r--r--src/vs/editor/common/languages.ts14
-rw-r--r--src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/brackets.ts14
-rw-r--r--src/vs/editor/common/viewLayout/linesLayout.ts18
-rw-r--r--src/vs/editor/common/viewLayout/viewLayout.ts7
-rw-r--r--src/vs/editor/contrib/codeAction/test/browser/codeAction.test.ts2
-rw-r--r--src/vs/editor/contrib/folding/browser/folding.ts116
-rw-r--r--src/vs/editor/contrib/folding/browser/foldingDecorations.ts33
-rw-r--r--src/vs/editor/contrib/folding/browser/foldingModel.ts186
-rw-r--r--src/vs/editor/contrib/folding/browser/foldingRanges.ts200
-rw-r--r--src/vs/editor/contrib/folding/browser/hiddenRangeModel.ts24
-rw-r--r--src/vs/editor/contrib/folding/browser/intializingRangeProvider.ts65
-rw-r--r--src/vs/editor/contrib/folding/test/browser/foldingRanges.test.ts129
-rw-r--r--src/vs/editor/contrib/markdownRenderer/browser/markdownRenderer.ts7
-rw-r--r--src/vs/editor/contrib/quickAccess/browser/gotoSymbolQuickAccess.ts23
-rw-r--r--src/vs/editor/contrib/snippet/browser/snippetController2.ts79
-rw-r--r--src/vs/editor/contrib/snippet/browser/snippetParser.ts32
-rw-r--r--src/vs/editor/contrib/snippet/browser/snippetSession.ts95
-rw-r--r--src/vs/editor/contrib/snippet/test/browser/snippetController2.test.ts167
-rw-r--r--src/vs/editor/contrib/snippet/test/browser/snippetSession.test.ts32
-rw-r--r--src/vs/editor/contrib/suggest/browser/suggest.ts1
-rw-r--r--src/vs/editor/contrib/suggest/browser/suggestController.ts29
-rw-r--r--src/vs/editor/contrib/suggest/browser/suggestModel.ts27
-rw-r--r--src/vs/editor/contrib/suggest/browser/suggestWidget.ts14
-rw-r--r--src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts2
-rw-r--r--src/vs/editor/contrib/suggest/test/browser/suggestModel.test.ts37
-rw-r--r--src/vs/editor/standalone/browser/standaloneCodeEditorService.ts16
-rw-r--r--src/vs/editor/standalone/browser/standaloneThemeService.ts70
-rw-r--r--src/vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast.ts12
-rw-r--r--src/vs/editor/test/browser/editorTestServices.ts2
-rw-r--r--src/vs/monaco.d.ts20
-rw-r--r--src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts6
-rw-r--r--src/vs/platform/actions/browser/menuEntryActionViewItem.ts20
-rw-r--r--src/vs/platform/actions/common/actions.contribution.ts14
-rw-r--r--src/vs/platform/actions/common/actions.ts56
-rw-r--r--src/vs/platform/actions/common/menuResetAction.ts29
-rw-r--r--src/vs/platform/actions/common/menuService.ts216
-rw-r--r--src/vs/platform/actions/test/common/menuService.test.ts15
-rw-r--r--src/vs/platform/environment/common/argv.ts1
-rw-r--r--src/vs/platform/environment/common/environment.ts1
-rw-r--r--src/vs/platform/environment/common/environmentService.ts3
-rw-r--r--src/vs/platform/environment/electron-main/environmentMainService.ts4
-rw-r--r--src/vs/platform/environment/node/argv.ts1
-rw-r--r--src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts115
-rw-r--r--src/vs/platform/extensionManagement/common/extensionManagement.ts7
-rw-r--r--src/vs/platform/extensionManagement/common/extensionTipsService.ts5
-rw-r--r--src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts37
-rw-r--r--src/vs/platform/extensionManagement/common/extensionsScannerService.ts66
-rw-r--r--src/vs/platform/extensionManagement/electron-main/defaultExtensionsProfileInit.ts27
-rw-r--r--src/vs/platform/extensionManagement/electron-sandbox/defaultExtensionsProfileInit.ts45
-rw-r--r--src/vs/platform/extensionManagement/electron-sandbox/extensionTipsService.ts6
-rw-r--r--src/vs/platform/extensionManagement/electron-sandbox/extensionsScannerService.ts4
-rw-r--r--src/vs/platform/extensionManagement/node/extensionManagementService.ts82
-rw-r--r--src/vs/platform/extensionManagement/node/extensionsScannerService.ts4
-rw-r--r--src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.ts10
-rw-r--r--src/vs/platform/extensions/electron-main/extensionHostStarter.ts1
-rw-r--r--src/vs/platform/files/browser/indexedDBFileSystemProvider.ts98
-rw-r--r--src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts27
-rw-r--r--src/vs/platform/files/test/node/parcelWatcher.integrationTest.ts14
-rw-r--r--src/vs/platform/instantiation/common/instantiationService.ts2
-rw-r--r--src/vs/platform/issue/common/issue.ts1
-rw-r--r--src/vs/platform/languagePacks/common/languagePacks.ts7
-rw-r--r--src/vs/platform/native/common/native.ts5
-rw-r--r--src/vs/platform/native/electron-main/nativeHostMainService.ts9
-rw-r--r--src/vs/platform/storage/electron-sandbox/storageService.ts24
-rw-r--r--src/vs/platform/telemetry/browser/1dsAppender.ts4
-rw-r--r--src/vs/platform/telemetry/browser/appInsightsAppender.ts77
-rw-r--r--src/vs/platform/telemetry/common/1dsAppender.ts28
-rw-r--r--src/vs/platform/telemetry/node/1dsAppender.ts4
-rw-r--r--src/vs/platform/telemetry/node/appInsightsAppender.ts121
-rw-r--r--src/vs/platform/telemetry/test/browser/1dsAppender.test.ts (renamed from src/vs/platform/telemetry/test/electron-browser/appInsightsAppender.test.ts)25
-rw-r--r--src/vs/platform/terminal/common/capabilities/capabilities.ts2
-rw-r--r--src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts4
-rw-r--r--src/vs/platform/terminal/common/terminal.ts36
-rw-r--r--src/vs/platform/terminal/common/terminalEnvironment.ts23
-rw-r--r--src/vs/platform/terminal/common/terminalPlatformConfiguration.ts42
-rw-r--r--src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts22
-rw-r--r--src/vs/platform/terminal/node/ptyHostService.ts4
-rw-r--r--src/vs/platform/terminal/node/ptyService.ts11
-rw-r--r--src/vs/platform/terminal/test/common/terminalEnvironment.test.ts40
-rw-r--r--src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts3
-rw-r--r--src/vs/platform/userDataProfile/browser/userDataProfile.ts83
-rw-r--r--src/vs/platform/userDataProfile/common/userDataProfile.ts266
-rw-r--r--src/vs/platform/userDataProfile/electron-main/userDataProfile.ts159
-rw-r--r--src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts38
-rw-r--r--src/vs/platform/userDataProfile/node/userDataProfile.ts91
-rw-r--r--src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts9
-rw-r--r--src/vs/platform/userDataProfile/test/electron-main/userDataProfileMainService.test.ts10
-rw-r--r--src/vs/platform/userDataSync/common/userDataSync.ts2
-rw-r--r--src/vs/platform/userDataSync/common/userDataSyncStoreService.ts4
-rw-r--r--src/vs/platform/userDataSync/test/common/userDataSyncClient.ts6
-rw-r--r--src/vs/platform/window/common/window.ts1
-rw-r--r--src/vs/platform/window/electron-main/window.ts2
-rw-r--r--src/vs/platform/windows/electron-main/window.ts90
-rw-r--r--src/vs/platform/windows/electron-main/windowsMainService.ts7
-rw-r--r--src/vs/platform/windows/test/electron-main/windowsFinder.test.ts1
-rw-r--r--src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts5
-rw-r--r--src/vs/server/node/extensionsScannerService.ts4
-rw-r--r--src/vs/server/node/remoteExtensionHostAgentCli.ts6
-rw-r--r--src/vs/server/node/remoteTerminalChannel.ts1
-rw-r--r--src/vs/server/node/serverServices.ts23
-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
429 files changed, 8687 insertions, 4837 deletions
diff --git a/src/vs/base/browser/broadcast.ts b/src/vs/base/browser/broadcast.ts
new file mode 100644
index 00000000000..d7785f42ff8
--- /dev/null
+++ b/src/vs/base/browser/broadcast.ts
@@ -0,0 +1,69 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { getErrorMessage } from 'vs/base/common/errors';
+import { Emitter } from 'vs/base/common/event';
+import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
+
+export class BroadcastDataChannel<T> extends Disposable {
+
+ private broadcastChannel: BroadcastChannel | undefined;
+
+ private readonly _onDidReceiveData = this._register(new Emitter<T>());
+ readonly onDidReceiveData = this._onDidReceiveData.event;
+
+ constructor(private readonly channelName: string) {
+ super();
+
+ // Use BroadcastChannel
+ if ('BroadcastChannel' in window) {
+ try {
+ this.broadcastChannel = new BroadcastChannel(channelName);
+ const listener = (event: MessageEvent) => {
+ this._onDidReceiveData.fire(event.data);
+ };
+ this.broadcastChannel.addEventListener('message', listener);
+ this._register(toDisposable(() => {
+ if (this.broadcastChannel) {
+ this.broadcastChannel.removeEventListener('message', listener);
+ this.broadcastChannel.close();
+ }
+ }));
+ } catch (error) {
+ console.warn('Error while creating broadcast channel. Falling back to localStorage.', getErrorMessage(error));
+ }
+ }
+
+ // BroadcastChannel is not supported. Use storage.
+ if (!this.broadcastChannel) {
+ this.channelName = `BroadcastDataChannel.${channelName}`;
+ this.createBroadcastChannel();
+ }
+ }
+
+ private createBroadcastChannel(): void {
+ const listener = (event: StorageEvent) => {
+ if (event.key === this.channelName && event.newValue) {
+ this._onDidReceiveData.fire(JSON.parse(event.newValue));
+ }
+ };
+ window.addEventListener('storage', listener);
+ this._register(toDisposable(() => window.removeEventListener('storage', listener)));
+ }
+
+ /**
+ * Sends the data to other BroadcastChannel objects set up for this channel. Data can be structured objects, e.g. nested objects and arrays.
+ * @param data data to broadcast
+ */
+ postData(data: T): void {
+ if (this.broadcastChannel) {
+ this.broadcastChannel.postMessage(data);
+ } else {
+ // remove previous changes so that event is triggered even if new changes are same as old changes
+ window.localStorage.removeItem(this.channelName);
+ window.localStorage.setItem(this.channelName, JSON.stringify(data));
+ }
+ }
+}
diff --git a/src/vs/base/browser/ui/button/button.css b/src/vs/base/browser/ui/button/button.css
index ed4131eefd3..cade1d85c64 100644
--- a/src/vs/base/browser/ui/button/button.css
+++ b/src/vs/base/browser/ui/button/button.css
@@ -38,8 +38,26 @@
cursor: pointer;
}
-.monaco-button-dropdown > .monaco-dropdown-button {
- margin-left: 1px;
+.monaco-button-dropdown.disabled {
+ cursor: default;
+}
+
+.monaco-button-dropdown > .monaco-button:focus {
+ outline-offset: -1px !important;
+}
+
+.monaco-button-dropdown.disabled .monaco-button-dropdown-separator {
+ opacity: 0.4;
+}
+
+.monaco-button-dropdown .monaco-button-dropdown-separator {
+ padding: 4px 0;
+ cursor: default;
+}
+
+.monaco-button-dropdown .monaco-button-dropdown-separator > div {
+ height: 100%;
+ width: 1px;
}
.monaco-description-button {
diff --git a/src/vs/base/browser/ui/button/button.ts b/src/vs/base/browser/ui/button/button.ts
index a2349f9dc96..489425b6ce4 100644
--- a/src/vs/base/browser/ui/button/button.ts
+++ b/src/vs/base/browser/ui/button/button.ts
@@ -15,6 +15,7 @@ import { Emitter, Event as BaseEvent } from 'vs/base/common/event';
import { KeyCode } from 'vs/base/common/keyCodes';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { mixin } from 'vs/base/common/objects';
+import { localize } from 'vs/nls';
import 'vs/css!./button';
export interface IButtonOptions extends IButtonStyles {
@@ -136,8 +137,8 @@ export class Button extends Disposable implements IButton {
// Also set hover background when button is focused for feedback
this.focusTracker = this._register(trackFocus(this._element));
- this._register(this.focusTracker.onDidFocus(() => this.setHoverBackground()));
- this._register(this.focusTracker.onDidBlur(() => this.applyStyles())); // restore standard styles
+ this._register(this.focusTracker.onDidFocus(() => { if (this.enabled) { this.setHoverBackground(); } }));
+ this._register(this.focusTracker.onDidBlur(() => { if (this.enabled) { this.applyStyles(); } }));
this.applyStyles();
}
@@ -246,6 +247,8 @@ export class ButtonWithDropdown extends Disposable implements IButton {
private readonly button: Button;
private readonly action: Action;
private readonly dropdownButton: Button;
+ private readonly separatorContainer: HTMLDivElement;
+ private readonly separator: HTMLDivElement;
readonly element: HTMLElement;
private readonly _onDidClick = this._register(new Emitter<Event | undefined>());
@@ -262,7 +265,17 @@ export class ButtonWithDropdown extends Disposable implements IButton {
this._register(this.button.onDidClick(e => this._onDidClick.fire(e)));
this.action = this._register(new Action('primaryAction', this.button.label, undefined, true, async () => this._onDidClick.fire(undefined)));
+ this.separatorContainer = document.createElement('div');
+ this.separatorContainer.classList.add('monaco-button-dropdown-separator');
+
+ this.separator = document.createElement('div');
+ this.separatorContainer.appendChild(this.separator);
+ this.element.appendChild(this.separatorContainer);
+
this.dropdownButton = this._register(new Button(this.element, { ...options, title: false, supportIcons: true }));
+ this.dropdownButton.element.title = localize("button dropdown more actions", 'More Actions...');
+ this.dropdownButton.element.setAttribute('aria-haspopup', 'true');
+ this.dropdownButton.element.setAttribute('aria-expanded', 'false');
this.dropdownButton.element.classList.add('monaco-dropdown-button');
this.dropdownButton.icon = Codicon.dropDownButton;
this._register(this.dropdownButton.onDidClick(e => {
@@ -288,6 +301,8 @@ export class ButtonWithDropdown extends Disposable implements IButton {
set enabled(enabled: boolean) {
this.button.enabled = enabled;
this.dropdownButton.enabled = enabled;
+
+ this.element.classList.toggle('disabled', !enabled);
}
get enabled(): boolean {
@@ -297,6 +312,10 @@ export class ButtonWithDropdown extends Disposable implements IButton {
style(styles: IButtonStyles): void {
this.button.style(styles);
this.dropdownButton.style(styles);
+
+ // Separator
+ this.separatorContainer.style.backgroundColor = styles.buttonBackground?.toString() ?? '';
+ this.separator.style.backgroundColor = styles.buttonForeground?.toString() ?? '';
}
focus(): void {
diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf
index 13999090718..9d4627dfb64 100644
--- a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf
+++ b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf
Binary files differ
diff --git a/src/vs/base/browser/ui/grid/grid.ts b/src/vs/base/browser/ui/grid/grid.ts
index e4cbed18667..cf8798982b0 100644
--- a/src/vs/base/browser/ui/grid/grid.ts
+++ b/src/vs/base/browser/ui/grid/grid.ts
@@ -528,6 +528,16 @@ export class Grid<T extends IView = IView> extends Disposable {
}
/**
+ * Returns whether all other {@link IView views} are at their minimum size.
+ *
+ * @param view The reference {@link IView view}.
+ */
+ isViewSizeMaximized(view: T): boolean {
+ const location = this.getViewLocation(view);
+ return this.gridview.isViewSizeMaximized(location);
+ }
+
+ /**
* Get the size of a {@link IView view}.
*
* @param view The {@link IView view}. Provide `undefined` to get the size
diff --git a/src/vs/base/browser/ui/grid/gridview.ts b/src/vs/base/browser/ui/grid/gridview.ts
index e121bcc10c8..8457663d657 100644
--- a/src/vs/base/browser/ui/grid/gridview.ts
+++ b/src/vs/base/browser/ui/grid/gridview.ts
@@ -592,6 +592,10 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
this.splitview.resizeView(index, size);
}
+ isChildSizeMaximized(index: number): boolean {
+ return this.splitview.isViewSizeMaximized(index);
+ }
+
distributeViewSizes(recursive = false): void {
this.splitview.distributeViewSizes();
@@ -1432,6 +1436,27 @@ export class GridView implements IDisposable {
}
/**
+ * Returns whether all other {@link IView views} are at their minimum size.
+ *
+ * @param location The {@link GridLocation location} of the view.
+ */
+ isViewSizeMaximized(location: GridLocation): boolean {
+ const [ancestors, node] = this.getNode(location);
+
+ if (!(node instanceof LeafNode)) {
+ throw new Error('Invalid location');
+ }
+
+ for (let i = 0; i < ancestors.length; i++) {
+ if (!ancestors[i].isChildSizeMaximized(location[i])) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
* Distribute the size among all {@link IView views} within the entire
* grid or within a single {@link SplitView}.
*
diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts
index 20df7f37ba8..db0a0b718dc 100644
--- a/src/vs/base/browser/ui/list/listWidget.ts
+++ b/src/vs/base/browser/ui/list/listWidget.ts
@@ -258,6 +258,23 @@ export function isMonacoEditor(e: HTMLElement): boolean {
return isMonacoEditor(e.parentElement);
}
+export function isButton(e: HTMLElement): boolean {
+ if ((e.tagName === 'A' && e.classList.contains('monaco-button')) ||
+ (e.tagName === 'DIV' && e.classList.contains('monaco-button-dropdown'))) {
+ return true;
+ }
+
+ if (e.classList.contains('monaco-list')) {
+ return false;
+ }
+
+ if (!e.parentElement) {
+ return false;
+ }
+
+ return isButton(e.parentElement);
+}
+
class KeyboardController<T> implements IDisposable {
private readonly disposables = new DisposableStore();
diff --git a/src/vs/editor/contrib/suggest/browser/resizable.ts b/src/vs/base/browser/ui/resizable/resizable.ts
index 95dfb06b8d0..95dfb06b8d0 100644
--- a/src/vs/editor/contrib/suggest/browser/resizable.ts
+++ b/src/vs/base/browser/ui/resizable/resizable.ts
diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts
index 57fbf0b1544..393f3ea3fda 100644
--- a/src/vs/base/browser/ui/splitview/splitview.ts
+++ b/src/vs/base/browser/ui/splitview/splitview.ts
@@ -932,6 +932,23 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
}
/**
+ * Returns whether all other {@link IView views} are at their minimum size.
+ */
+ isViewSizeMaximized(index: number): boolean {
+ if (index < 0 || index >= this.viewItems.length) {
+ return false;
+ }
+
+ for (const item of this.viewItems) {
+ if (item !== this.viewItems[index] && item.size > item.minimumSize) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
* Distribute the entire {@link SplitView} size among all {@link IView views}.
*/
distributeViewSizes(): void {
diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts
index c03f5c5e1af..124d926541f 100644
--- a/src/vs/base/browser/ui/tree/abstractTree.ts
+++ b/src/vs/base/browser/ui/tree/abstractTree.ts
@@ -9,7 +9,7 @@ import { DomEmitter } from 'vs/base/browser/event';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { IIdentityProvider, IKeyboardNavigationDelegate, IKeyboardNavigationLabelProvider, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IListMouseEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView';
-import { DefaultKeyboardNavigationDelegate, IListOptions, IListStyles, isInputElement, isMonacoEditor, List, MouseController } from 'vs/base/browser/ui/list/listWidget';
+import { DefaultKeyboardNavigationDelegate, IListOptions, IListStyles, isButton, isInputElement, isMonacoEditor, List, MouseController } from 'vs/base/browser/ui/list/listWidget';
import { getVisibleState, isFilterResult } from 'vs/base/browser/ui/tree/indexTreeModel';
import { ICollapseStateChangeEvent, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeEvent, ITreeFilter, ITreeModel, ITreeModelSpliceEvent, ITreeMouseEvent, ITreeNavigator, ITreeNode, ITreeRenderer, TreeDragOverBubble, TreeError, TreeFilterResult, TreeMouseEventTarget, TreeVisibility } from 'vs/base/browser/ui/tree/tree';
import { distinct, equals, firstOrDefault, range } from 'vs/base/common/arrays';
@@ -1153,7 +1153,9 @@ class TreeNodeListMouseController<T, TFilterData, TRef> extends MouseController<
}
protected override onViewPointer(e: IListMouseEvent<ITreeNode<T, TFilterData>>): void {
- if (isInputElement(e.browserEvent.target as HTMLElement) || isMonacoEditor(e.browserEvent.target as HTMLElement)) {
+ if (isButton(e.browserEvent.target as HTMLElement) ||
+ isInputElement(e.browserEvent.target as HTMLElement) ||
+ isMonacoEditor(e.browserEvent.target as HTMLElement)) {
return;
}
diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts
index b567c8fe977..d543fdd8f83 100644
--- a/src/vs/base/common/arrays.ts
+++ b/src/vs/base/common/arrays.ts
@@ -47,6 +47,18 @@ export function equals<T>(one: ReadonlyArray<T> | undefined, other: ReadonlyArra
}
/**
+ * Remove the element at `index` by replacing it with the last element. This is faster than `splice`
+ * but changes the order of the array
+ */
+export function removeFastWithoutKeepingOrder<T>(array: T[], index: number) {
+ const last = array.length - 1;
+ if (index < last) {
+ array[index] = array[last];
+ }
+ array.pop();
+}
+
+/**
* Performs a binary search algorithm over a sorted array.
*
* @param array The array being searched.
diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts
index 9e696833141..e08e651d91e 100644
--- a/src/vs/base/common/codicons.ts
+++ b/src/vs/base/common/codicons.ts
@@ -558,7 +558,8 @@ export class Codicon implements CSSIcon {
public static readonly mapFilled = new Codicon('map-filled', { fontCharacter: '\\ec06' });
public static readonly circleSmall = new Codicon('circle-small', { fontCharacter: '\\ec07' });
public static readonly bellSlash = new Codicon('bell-slash', { fontCharacter: '\\ec08' });
- public static readonly bellSlashDot = new Codicon('bell-slash-dot', { fontCharacter: '\\f101' });
+ public static readonly bellSlashDot = new Codicon('bell-slash-dot', { fontCharacter: '\\ec09' });
+ public static readonly commentUnresolved = new Codicon('comment-unresolved', { fontCharacter: '\\ec0a' });
// derived icons, that could become separate icons
diff --git a/src/vs/base/common/collections.ts b/src/vs/base/common/collections.ts
index 1f16cd438ea..d8ee92f757e 100644
--- a/src/vs/base/common/collections.ts
+++ b/src/vs/base/common/collections.ts
@@ -16,20 +16,6 @@ export type IStringDictionary<V> = Record<string, V>;
export type INumberDictionary<V> = Record<number, V>;
/**
- * Iterates over each entry in the provided dictionary. The iterator will stop when the callback returns `false`.
- *
- * @deprecated Use `Object.entries(x)` with a `for...of` loop.
- */
-export function forEach<T>(from: IStringDictionary<T> | INumberDictionary<T>, callback: (entry: { key: any; value: T }) => any): void {
- for (const [key, value] of Object.entries(from)) {
- const result = callback({ key, value });
- if (result === false) {
- return;
- }
- }
-}
-
-/**
* Groups the collection into a dictionary based on the provided
* group function.
*/
diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts
index 647b5f1beed..d395e4a9f32 100644
--- a/src/vs/base/common/event.ts
+++ b/src/vs/base/common/event.ts
@@ -8,6 +8,7 @@ import { onUnexpectedError } from 'vs/base/common/errors';
import { once as onceFn } from 'vs/base/common/functional';
import { combinedDisposable, Disposable, DisposableStore, IDisposable, SafeDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { LinkedList } from 'vs/base/common/linkedList';
+import { IObservable, IObserver } from 'vs/base/common/observableImpl/base';
import { StopWatch } from 'vs/base/common/stopwatch';
@@ -423,6 +424,55 @@ export namespace Event {
store?.dispose();
});
}
+
+ class EmitterObserver<T> implements IObserver {
+
+ readonly emitter: Emitter<T>;
+
+ private _counter = 0;
+ private _hasChanged = false;
+
+ constructor(readonly obs: IObservable<T, any>, store: DisposableStore | undefined) {
+ const options = {
+ onFirstListenerAdd: () => {
+ obs.addObserver(this);
+ },
+ onLastListenerRemove: () => {
+ obs.removeObserver(this);
+ }
+ };
+ if (!store) {
+ _addLeakageTraceLogic(options);
+ }
+ this.emitter = new Emitter<T>(options);
+ if (store) {
+ store.add(this.emitter);
+ }
+ }
+
+ beginUpdate<T>(_observable: IObservable<T, void>): void {
+ // console.assert(_observable === this.obs);
+ this._counter++;
+ }
+
+ handleChange<T, TChange>(_observable: IObservable<T, TChange>, _change: TChange): void {
+ this._hasChanged = true;
+ }
+
+ endUpdate<T>(_observable: IObservable<T, void>): void {
+ if (--this._counter === 0) {
+ if (this._hasChanged) {
+ this._hasChanged = false;
+ this.emitter.fire(this.obs.get());
+ }
+ }
+ }
+ }
+
+ export function fromObservable<T>(obs: IObservable<T, any>, store?: DisposableStore): Event<T> {
+ const observer = new EmitterObserver(obs, store);
+ return observer.emitter.event;
+ }
}
export interface EmitterOptions {
diff --git a/src/vs/base/common/observable.ts b/src/vs/base/common/observable.ts
new file mode 100644
index 00000000000..c588d68b9f6
--- /dev/null
+++ b/src/vs/base/common/observable.ts
@@ -0,0 +1,30 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+export {
+ IObservable,
+ IObserver,
+ IReader,
+ ISettable,
+ ISettableObservable,
+ ITransaction,
+ observableValue,
+ transaction,
+} from 'vs/base/common/observableImpl/base';
+export { derived } from 'vs/base/common/observableImpl/derived';
+export {
+ autorun,
+ autorunDelta,
+ autorunHandleChanges,
+ autorunWithStore,
+} from 'vs/base/common/observableImpl/autorun';
+export * from 'vs/base/common/observableImpl/utils';
+
+import { ConsoleObservableLogger, setLogger } from 'vs/base/common/observableImpl/logging';
+
+const enableLogging = false;
+if (enableLogging) {
+ setLogger(new ConsoleObservableLogger());
+}
diff --git a/src/vs/base/common/observableImpl/autorun.ts b/src/vs/base/common/observableImpl/autorun.ts
new file mode 100644
index 00000000000..6efe4736783
--- /dev/null
+++ b/src/vs/base/common/observableImpl/autorun.ts
@@ -0,0 +1,167 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
+import { IReader, IObservable, IObserver } from 'vs/base/common/observableImpl/base';
+import { getLogger } from 'vs/base/common/observableImpl/logging';
+
+export function autorun(debugName: string, fn: (reader: IReader) => void): IDisposable {
+ return new AutorunObserver(debugName, fn, 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(
+ debugName: 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(debugName, fn, options.handleChange);
+}
+
+export function autorunWithStore(
+ fn: (reader: IReader, store: DisposableStore) => void,
+ debugName: string
+): IDisposable {
+ const store = new DisposableStore();
+ const disposable = autorun(
+ debugName,
+ reader => {
+ store.clear();
+ fn(reader, store);
+ }
+ );
+ return toDisposable(() => {
+ disposable.dispose();
+ store.dispose();
+ });
+}
+
+export class AutorunObserver implements IObserver, IReader, IDisposable {
+ public needsToRun = true;
+ private updateCount = 0;
+ private disposed = false;
+
+ /**
+ * 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(
+ public readonly debugName: string,
+ private readonly runFn: (reader: IReader) => void,
+ private readonly _handleChange: ((context: IChangeContext) => boolean) | undefined
+ ) {
+ getLogger()?.handleAutorunCreated(this);
+ this.runIfNeeded();
+ }
+
+ public subscribeTo<T>(observable: IObservable<T>) {
+ // In case the run action disposes the autorun
+ if (this.disposed) {
+ return;
+ }
+ this._dependencies.add(observable);
+ if (!this.staleDependencies.delete(observable)) {
+ observable.addObserver(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(): void {
+ this.updateCount++;
+ }
+
+ public endUpdate(): void {
+ 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;
+
+ getLogger()?.handleAutorunTriggered(this);
+
+ 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.removeObserver(this);
+ }
+ this.staleDependencies.clear();
+ }
+ }
+
+ public dispose(): void {
+ this.disposed = true;
+ for (const o of this._dependencies) {
+ o.removeObserver(this);
+ }
+ this._dependencies.clear();
+ }
+
+ public toString(): string {
+ return `Autorun<${this.debugName}>`;
+ }
+}
+
+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(name, (reader) => {
+ const newValue = observable.read(reader);
+ const lastValue = _lastValue;
+ _lastValue = newValue;
+ handler({ lastValue, newValue });
+ });
+}
diff --git a/src/vs/base/common/observableImpl/base.ts b/src/vs/base/common/observableImpl/base.ts
new file mode 100644
index 00000000000..9d83083e49b
--- /dev/null
+++ b/src/vs/base/common/observableImpl/base.ts
@@ -0,0 +1,244 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import type { derived } from 'vs/base/common/observableImpl/derived';
+import { getLogger } from 'vs/base/common/observableImpl/logging';
+
+export interface IObservable<T, TChange = void> {
+ readonly TChange: TChange;
+
+ /**
+ * Reads the current value.
+ *
+ * Must not be called from {@link IObserver.handleChange}.
+ */
+ get(): T;
+
+ /**
+ * Adds an observer.
+ */
+ addObserver(observer: IObserver): void;
+ removeObserver(observer: IObserver): void;
+
+ /**
+ * Subscribes the reader to this observable and returns the current value of this observable.
+ */
+ read(reader: IReader): T;
+
+ map<TNew>(fn: (value: T) => TNew): IObservable<TNew>;
+
+ readonly debugName: string;
+}
+
+export interface IReader {
+ /**
+ * Reports an observable that was read.
+ *
+ * Is called by {@link IObservable.read}.
+ */
+ subscribeTo<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;
+}
+
+let _derived: typeof derived;
+/**
+ * @internal
+ * This is to allow splitting files.
+*/
+export function _setDerived(derived: typeof _derived) {
+ _derived = derived;
+}
+
+export abstract class ConvenientObservable<T, TChange> implements IObservable<T, TChange> {
+ get TChange(): TChange { return null!; }
+
+ public abstract get(): T;
+ public abstract addObserver(observer: IObserver): void;
+ public abstract removeObserver(observer: IObserver): void;
+
+ /** @sealed */
+ public read(reader: IReader): T {
+ reader.subscribeTo(this);
+ return this.get();
+ }
+
+ /** @sealed */
+ public map<TNew>(fn: (value: T) => TNew): IObservable<TNew> {
+ return _derived(
+ () => {
+ const name = getFunctionName(fn);
+ return name !== undefined ? name : `${this.debugName} (mapped)`;
+ },
+ (reader) => fn(this.read(reader))
+ );
+ }
+
+ public abstract get debugName(): string;
+}
+
+export abstract class BaseObservable<T, TChange = void> extends ConvenientObservable<T, TChange> {
+ protected readonly observers = new Set<IObserver>();
+
+ /** @sealed */
+ public addObserver(observer: IObserver): void {
+ const len = this.observers.size;
+ this.observers.add(observer);
+ if (len === 0) {
+ this.onFirstObserverAdded();
+ }
+ }
+
+ /** @sealed */
+ public removeObserver(observer: IObserver): void {
+ const deleted = this.observers.delete(observer);
+ if (deleted && this.observers.size === 0) {
+ this.onLastObserverRemoved();
+ }
+ }
+
+ protected onFirstObserverAdded(): void { }
+ protected onLastObserverRemoved(): void { }
+}
+
+export function transaction(fn: (tx: ITransaction) => void, getDebugName?: () => string): void {
+ const tx = new TransactionImpl(fn, getDebugName);
+ try {
+ getLogger()?.handleBeginTransaction(tx);
+ fn(tx);
+ } finally {
+ tx.finish();
+ getLogger()?.handleEndTransaction();
+ }
+}
+
+export function getFunctionName(fn: Function): string | undefined {
+ const fnSrc = fn.toString();
+ // Pattern: /** @description ... */
+ const regexp = /\/\*\*\s*@description\s*([^*]*)\*\//;
+ const match = regexp.exec(fnSrc);
+ const result = match ? match[1] : undefined;
+ return result?.trim();
+}
+
+export class TransactionImpl implements ITransaction {
+ private updatingObservers: { observer: IObserver; observable: IObservable<any> }[] | null = [];
+
+ constructor(private readonly fn: Function, private readonly _getDebugName?: () => string) { }
+
+ public getDebugName(): string | undefined {
+ if (this._getDebugName) {
+ return this._getDebugName();
+ }
+ return getFunctionName(this.fn);
+ }
+
+ public updateObserver(
+ observer: IObserver,
+ observable: IObservable<any>
+ ): void {
+ this.updatingObservers!.push({ observer, observable });
+ observer.beginUpdate(observable);
+ }
+
+ public finish(): void {
+ const updatingObservers = this.updatingObservers!;
+ // Prevent anyone from updating observers from now on.
+ this.updatingObservers = null;
+ for (const { observer, observable } of updatingObservers) {
+ observer.endUpdate(observable);
+ }
+ }
+}
+
+export interface ISettableObservable<T, TChange = void> extends IObservable<T, TChange>, ISettable<T, TChange> {
+}
+
+export function observableValue<T, TChange = void>(name: string, initialValue: T): ISettableObservable<T, TChange> {
+ return new ObservableValue(name, initialValue);
+}
+
+export class ObservableValue<T, TChange = void>
+ extends BaseObservable<T, TChange>
+ implements ISettableObservable<T, TChange>
+{
+ private value: T;
+
+ constructor(public readonly debugName: string, initialValue: T) {
+ 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);
+ }, () => `Setting ${this.debugName}`);
+ return;
+ }
+
+ const oldValue = this.value;
+ this.value = value;
+ getLogger()?.handleObservableChanged(this, { oldValue, newValue: value, change, didChange: true });
+
+ for (const observer of this.observers) {
+ tx.updateObserver(observer, this);
+ observer.handleChange(this, change);
+ }
+ }
+
+ override toString(): string {
+ return `${this.debugName}: ${this.value}`;
+ }
+}
+
diff --git a/src/vs/base/common/observableImpl/derived.ts b/src/vs/base/common/observableImpl/derived.ts
new file mode 100644
index 00000000000..84a93132f15
--- /dev/null
+++ b/src/vs/base/common/observableImpl/derived.ts
@@ -0,0 +1,167 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { IReader, IObservable, BaseObservable, IObserver, _setDerived } from 'vs/base/common/observableImpl/base';
+import { getLogger } from 'vs/base/common/observableImpl/logging';
+
+export function derived<T>(debugName: string | (() => string), computeFn: (reader: IReader) => T): IObservable<T> {
+ return new Derived(debugName, computeFn);
+}
+
+_setDerived(derived);
+
+export class Derived<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>>();
+
+ public override get debugName(): string {
+ return typeof this._debugName === 'function' ? this._debugName() : this._debugName;
+ }
+
+ constructor(
+ private readonly _debugName: string | (() => string),
+ private readonly computeFn: (reader: IReader) => T
+ ) {
+ super();
+
+ getLogger()?.handleDerivedCreated(this);
+ }
+
+ protected override onLastObserverRemoved(): 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.removeObserver(this);
+ }
+ this._dependencies.clear();
+ }
+
+ 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.
+ const result = this.computeFn(this);
+ // Clear new dependencies
+ this.onLastObserverRemoved();
+ return result;
+ }
+
+ 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.removeObserver(this);
+ }
+ this.staleDependencies.clear();
+ }
+
+ this.hasValue = true;
+ const didChange = this.hadValue && oldValue !== this.value;
+ getLogger()?.handleDerivedRecomputed(this, {
+ oldValue,
+ newValue: this.value,
+ change: undefined,
+ didChange
+ });
+ if (didChange) {
+ for (const r of this.observers) {
+ r.handleChange(this, undefined);
+ }
+ }
+ }
+ return this.value!;
+ }
+
+ // IObserver Implementation
+ public beginUpdate(): void {
+ if (this.updateCount === 0) {
+ for (const r of this.observers) {
+ r.beginUpdate(this);
+ }
+ }
+ this.updateCount++;
+ }
+
+ public handleChange<T, TChange>(
+ _observable: IObservable<T, TChange>,
+ _change: TChange
+ ): void {
+ 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 endUpdate(): void {
+ this.updateCount--;
+ if (this.updateCount === 0) {
+ if (this.observers.size > 0) {
+ // Propagate invalidation
+ this.get();
+ }
+
+ for (const r of this.observers) {
+ r.endUpdate(this);
+ }
+ }
+ }
+
+ // IReader Implementation
+ public subscribeTo<T>(observable: IObservable<T>) {
+ this._dependencies.add(observable);
+ // We are already added as observer for stale dependencies.
+ if (!this.staleDependencies.delete(observable)) {
+ observable.addObserver(this);
+ }
+ }
+
+ override toString(): string {
+ return `LazyDerived<${this.debugName}>`;
+ }
+}
diff --git a/src/vs/base/common/observableImpl/logging.ts b/src/vs/base/common/observableImpl/logging.ts
new file mode 100644
index 00000000000..0c221d0c700
--- /dev/null
+++ b/src/vs/base/common/observableImpl/logging.ts
@@ -0,0 +1,312 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { AutorunObserver } from 'vs/base/common/observableImpl/autorun';
+import { IObservable, ObservableValue, TransactionImpl } from 'vs/base/common/observableImpl/base';
+import { Derived } from 'vs/base/common/observableImpl/derived';
+import { FromEventObservable } from 'vs/base/common/observableImpl/utils';
+
+let globalObservableLogger: IObservableLogger | undefined;
+
+export function setLogger(logger: IObservableLogger): void {
+ globalObservableLogger = logger;
+}
+
+export function getLogger(): IObservableLogger | undefined {
+ return globalObservableLogger;
+}
+
+interface IChangeInformation {
+ oldValue: unknown;
+ newValue: unknown;
+ change: unknown;
+ didChange: boolean;
+}
+
+export interface IObservableLogger {
+ handleObservableChanged(observable: ObservableValue<unknown, unknown>, info: IChangeInformation): void;
+ handleFromEventObservableTriggered(observable: FromEventObservable<any, any>, info: IChangeInformation): void;
+
+ handleAutorunCreated(autorun: AutorunObserver): void;
+ handleAutorunTriggered(autorun: AutorunObserver): void;
+
+ handleDerivedCreated(observable: Derived<unknown>): void;
+ handleDerivedRecomputed(observable: Derived<unknown>, info: IChangeInformation): void;
+
+ handleBeginTransaction(transaction: TransactionImpl): void;
+ handleEndTransaction(): void;
+}
+
+export class ConsoleObservableLogger implements IObservableLogger {
+ private indentation = 0;
+
+ private textToConsoleArgs(text: ConsoleText): unknown[] {
+ return consoleTextToArgs([
+ normalText(repeat('| ', this.indentation)),
+ text,
+ ]);
+ }
+
+ private formatInfo(info: IChangeInformation): ConsoleText[] {
+ return info.didChange
+ ? [
+ normalText(` `),
+ styled(formatValue(info.oldValue, 70), {
+ color: 'red',
+ strikeThrough: true,
+ }),
+ normalText(` `),
+ styled(formatValue(info.newValue, 60), {
+ color: 'green',
+ }),
+ ]
+ : [normalText(` (unchanged)`)];
+ }
+
+ handleObservableChanged(observable: IObservable<unknown, unknown>, info: IChangeInformation): void {
+ console.log(...this.textToConsoleArgs([
+ formatKind('observable value changed'),
+ styled(observable.debugName, { color: 'BlueViolet' }),
+ ...this.formatInfo(info),
+ ]));
+ }
+
+ private readonly changedObservablesSets = new WeakMap<object, Set<IObservable<any, any>>>();
+
+ formatChanges(changes: Set<IObservable<any, any>>): ConsoleText | undefined {
+ if (changes.size === 0) {
+ return undefined;
+ }
+ return styled(
+ ' (changed deps: ' +
+ [...changes].map((o) => o.debugName).join(', ') +
+ ')',
+ { color: 'gray' }
+ );
+ }
+
+ handleDerivedCreated(derived: Derived<unknown>): void {
+ const existingHandleChange = derived.handleChange;
+ this.changedObservablesSets.set(derived, new Set());
+ derived.handleChange = (observable, change) => {
+ this.changedObservablesSets.get(derived)!.add(observable);
+ return existingHandleChange.apply(derived, [observable, change]);
+ };
+ }
+
+ handleDerivedRecomputed(derived: Derived<unknown>, info: IChangeInformation): void {
+ const changedObservables = this.changedObservablesSets.get(derived)!;
+ console.log(...this.textToConsoleArgs([
+ formatKind('derived recomputed'),
+ styled(derived.debugName, { color: 'BlueViolet' }),
+ ...this.formatInfo(info),
+ this.formatChanges(changedObservables)
+ ]));
+ changedObservables.clear();
+ }
+
+ handleFromEventObservableTriggered(observable: FromEventObservable<any, any>, info: IChangeInformation): void {
+ console.log(...this.textToConsoleArgs([
+ formatKind('observable from event triggered'),
+ styled(observable.debugName, { color: 'BlueViolet' }),
+ ...this.formatInfo(info),
+ ]));
+ }
+
+ handleAutorunCreated(autorun: AutorunObserver): void {
+ const existingHandleChange = autorun.handleChange;
+ this.changedObservablesSets.set(autorun, new Set());
+ autorun.handleChange = (observable, change) => {
+ this.changedObservablesSets.get(autorun)!.add(observable);
+ return existingHandleChange.apply(autorun, [observable, change]);
+ };
+ }
+
+ handleAutorunTriggered(autorun: AutorunObserver): void {
+ const changedObservables = this.changedObservablesSets.get(autorun)!;
+ console.log(...this.textToConsoleArgs([
+ formatKind('autorun'),
+ styled(autorun.debugName, { color: 'BlueViolet' }),
+ this.formatChanges(changedObservables)
+ ]));
+ changedObservables.clear();
+ }
+
+ handleBeginTransaction(transaction: TransactionImpl): void {
+ let transactionName = transaction.getDebugName();
+ if (transactionName === undefined) {
+ transactionName = '';
+ }
+ console.log(...this.textToConsoleArgs([
+ formatKind('transaction'),
+ styled(transactionName, { color: 'BlueViolet' }),
+ ]));
+ this.indentation++;
+ }
+
+ handleEndTransaction(): void {
+ this.indentation--;
+ }
+}
+
+type ConsoleText =
+ | (ConsoleText | undefined)[]
+ | { text: string; style: string; data?: Record<string, unknown> }
+ | { data: Record<string, unknown> };
+
+function consoleTextToArgs(text: ConsoleText): unknown[] {
+ const styles = new Array<any>();
+ const initial = {};
+ const data = initial;
+ let firstArg = '';
+
+ function process(t: ConsoleText): void {
+ if ('length' in t) {
+ for (const item of t) {
+ if (item) {
+ process(item);
+ }
+ }
+ } else if ('text' in t) {
+ firstArg += `%c${t.text}`;
+ styles.push(t.style);
+ if (t.data) {
+ Object.assign(data, t.data);
+ }
+ } else if ('data' in t) {
+ Object.assign(data, t.data);
+ }
+ }
+
+ process(text);
+
+ const result = [firstArg, ...styles];
+ if (Object.keys(data).length > 0) {
+ result.push(data);
+ }
+
+ return result;
+}
+
+function normalText(text: string): ConsoleText {
+ return styled(text, { color: 'black' });
+}
+
+function formatKind(kind: string): ConsoleText {
+ return styled(padStr(`${kind}: `, 10), { color: 'black', bold: true });
+}
+
+function styled(
+ text: string,
+ options: { color: string; strikeThrough?: boolean; bold?: boolean } = {
+ color: 'black',
+ }
+): ConsoleText {
+ function objToCss(styleObj: Record<string, string>): string {
+ return Object.entries(styleObj).reduce(
+ (styleString, [propName, propValue]) => {
+ return `${styleString}${propName}:${propValue};`;
+ },
+ ''
+ );
+ }
+
+ const style: Record<string, string> = {
+ color: options.color,
+ };
+ if (options.strikeThrough) {
+ style['text-decoration'] = 'line-through';
+ }
+ if (options.bold) {
+ style['font-weight'] = 'bold';
+ }
+
+ return {
+ text,
+ style: objToCss(style),
+ };
+}
+
+function formatValue(value: unknown, availableLen: number): string {
+ switch (typeof value) {
+ case 'number':
+ return '' + value;
+ case 'string':
+ if (value.length + 2 <= availableLen) {
+ return `"${value}"`;
+ }
+ return `"${value.substr(0, availableLen - 7)}"+...`;
+
+ case 'boolean':
+ return value ? 'true' : 'false';
+ case 'undefined':
+ return 'undefined';
+ case 'object':
+ if (value === null) {
+ return 'null';
+ }
+ if (Array.isArray(value)) {
+ return formatArray(value, availableLen);
+ }
+ return formatObject(value, availableLen);
+ case 'symbol':
+ return value.toString();
+ case 'function':
+ return `[[Function${value.name ? ' ' + value.name : ''}]]`;
+ default:
+ return '' + value;
+ }
+}
+
+function formatArray(value: unknown[], availableLen: number): string {
+ let result = '[ ';
+ let first = true;
+ for (const val of value) {
+ if (!first) {
+ result += ', ';
+ }
+ if (result.length - 5 > availableLen) {
+ result += '...';
+ break;
+ }
+ first = false;
+ result += `${formatValue(val, availableLen - result.length)}`;
+ }
+ result += ' ]';
+ return result;
+}
+
+function formatObject(value: object, availableLen: number): string {
+ let result = '{ ';
+ let first = true;
+ for (const [key, val] of Object.entries(value)) {
+ if (!first) {
+ result += ', ';
+ }
+ if (result.length - 5 > availableLen) {
+ result += '...';
+ break;
+ }
+ first = false;
+ result += `${key}: ${formatValue(val, availableLen - result.length)}`;
+ }
+ result += ' }';
+ return result;
+}
+
+function repeat(str: string, count: number): string {
+ let result = '';
+ for (let i = 1; i <= count; i++) {
+ result += str;
+ }
+ return result;
+}
+
+function padStr(str: string, length: number): string {
+ while (str.length < length) {
+ str += ' ';
+ }
+ return str;
+}
diff --git a/src/vs/base/common/observableImpl/utils.ts b/src/vs/base/common/observableImpl/utils.ts
new file mode 100644
index 00000000000..0b07d089b83
--- /dev/null
+++ b/src/vs/base/common/observableImpl/utils.ts
@@ -0,0 +1,281 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
+import { autorun } from 'vs/base/common/observableImpl/autorun';
+import { IObservable, BaseObservable, transaction, IReader, ITransaction, ConvenientObservable, IObserver, observableValue, getFunctionName } from 'vs/base/common/observableImpl/base';
+import { derived } from 'vs/base/common/observableImpl/derived';
+import { Event } from 'vs/base/common/event';
+import { getLogger } from 'vs/base/common/observableImpl/logging';
+
+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 override get debugName(): string {
+ return this.toString();
+ }
+
+ public get(): T {
+ return this.value;
+ }
+ public addObserver(observer: IObserver): void {
+ // NO OP
+ }
+ public removeObserver(observer: IObserver): void {
+ // NO OP
+ }
+
+ override toString(): string {
+ return `Const: ${this.value}`;
+ }
+}
+
+
+export function observableFromPromise<T>(promise: Promise<T>): IObservable<{ value?: T }> {
+ const observable = 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('waitForState', reader => {
+ const currentState = observable.read(reader);
+ if (predicate(currentState)) {
+ d.dispose();
+ resolve(currentState);
+ }
+ });
+ });
+}
+
+export function observableFromEvent<T, TArgs = unknown>(
+ event: Event<TArgs>,
+ getValue: (args: TArgs | undefined) => T
+): IObservable<T> {
+ return new FromEventObservable(event, getValue);
+}
+
+export 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();
+ }
+
+ private getDebugName(): string | undefined {
+ return getFunctionName(this.getValue);
+ }
+
+ public get debugName(): string {
+ const name = this.getDebugName();
+ return 'From Event' + (name ? `: ${name}` : '');
+ }
+
+ protected override onFirstObserverAdded(): void {
+ this.subscription = this.event(this.handleEvent);
+ }
+
+ private readonly handleEvent = (args: TArgs | undefined) => {
+ const newValue = this.getValue(args);
+
+ const didChange = this.value !== newValue;
+
+ getLogger()?.handleFromEventObservableTriggered(this, { oldValue: this.value, newValue, change: undefined, didChange });
+
+ if (didChange) {
+ this.value = newValue;
+
+ if (this.hasValue) {
+ transaction(
+ (tx) => {
+ for (const o of this.observers) {
+ tx.updateObserver(o, this);
+ o.handleChange(this, undefined);
+ }
+ },
+ () => {
+ const name = this.getDebugName();
+ return 'Event fired' + (name ? `: ${name}` : '');
+ }
+ );
+ }
+ this.hasValue = true;
+ }
+ };
+
+ protected override onLastObserverRemoved(): 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 keep it updated
+ return this.getValue(undefined);
+ }
+ }
+}
+
+export namespace observableFromEvent {
+ export const Observer = FromEventObservable;
+}
+
+export function observableSignalFromEvent(
+ debugName: string,
+ event: Event<any>
+): IObservable<void> {
+ return new FromEventObservableSignal(debugName, event);
+}
+
+class FromEventObservableSignal extends BaseObservable<void> {
+ private subscription: IDisposable | undefined;
+
+ constructor(
+ public readonly debugName: string,
+ private readonly event: Event<any>,
+ ) {
+ super();
+ }
+
+ protected override onFirstObserverAdded(): void {
+ this.subscription = this.event(this.handleEvent);
+ }
+
+ private readonly handleEvent = () => {
+ transaction(
+ (tx) => {
+ for (const o of this.observers) {
+ tx.updateObserver(o, this);
+ o.handleChange(this, undefined);
+ }
+ },
+ () => this.debugName
+ );
+ };
+
+ protected override onLastObserverRemoved(): void {
+ this.subscription!.dispose();
+ this.subscription = undefined;
+ }
+
+ public override get(): void {
+ // NO OP
+ }
+}
+
+export function debouncedObservable<T>(observable: IObservable<T>, debounceMs: number, disposableStore: DisposableStore): IObservable<T | undefined> {
+ const debouncedObservable = observableValue<T | undefined>('debounced', undefined);
+
+ let timeout: any = undefined;
+
+ disposableStore.add(autorun('debounce', reader => {
+ const value = observable.read(reader);
+
+ if (timeout) {
+ clearTimeout(timeout);
+ }
+ timeout = setTimeout(() => {
+ transaction(tx => {
+ debouncedObservable.set(value, tx);
+ });
+ }, debounceMs);
+
+ }));
+
+ return debouncedObservable;
+}
+
+export function wasEventTriggeredRecently(event: Event<any>, timeoutMs: number, disposableStore: DisposableStore): IObservable<boolean> {
+ const observable = observableValue('triggeredRecently', false);
+
+ 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 {
+ const o = new KeepAliveObserver();
+ observable.addObserver(o);
+ return toDisposable(() => {
+ observable.removeObserver(o);
+ });
+}
+
+class KeepAliveObserver implements IObserver {
+ beginUpdate<T>(observable: IObservable<T, void>): void {
+ // NO OP
+ }
+
+ handleChange<T, TChange>(observable: IObservable<T, TChange>, change: TChange): void {
+ // NO OP
+ }
+
+ endUpdate<T>(observable: IObservable<T, void>): void {
+ // NO OP
+ }
+}
+
+export function derivedObservableWithCache<T>(name: string, computeFn: (reader: IReader, lastValue: T | undefined) => T): IObservable<T> {
+ let lastValue: T | undefined = undefined;
+ const observable = derived(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 = observableValue('derivedObservableWithWritableCache.counter', 0);
+ const observable = derived(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/base/common/observableValue.ts b/src/vs/base/common/observableValue.ts
index 7bdcbae66cb..e1f207c0841 100644
--- a/src/vs/base/common/observableValue.ts
+++ b/src/vs/base/common/observableValue.ts
@@ -5,17 +5,28 @@
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
+//@ts-ignore
+import type { IObservable } from 'vs/base/common/observable';
+/**
+ * @deprecated Use {@link IObservable} instead.
+ */
export interface IObservableValue<T> {
onDidChange: Event<T>;
readonly value: T;
}
+/**
+ * @deprecated Use {@link IObservable} instead.
+ */
export const staticObservableValue = <T>(value: T): IObservableValue<T> => ({
onDidChange: Event.None,
value,
});
+/**
+ * @deprecated Use {@link IObservable} instead.
+ */
export class MutableObservableValue<T> extends Disposable implements IObservableValue<T> {
private readonly changeEmitter = this._register(new Emitter<T>());
diff --git a/src/vs/base/common/product.ts b/src/vs/base/common/product.ts
index 5dcf73fc4a3..1ae8079810e 100644
--- a/src/vs/base/common/product.ts
+++ b/src/vs/base/common/product.ts
@@ -99,9 +99,7 @@ export interface IProductConfiguration {
readonly enableTelemetry?: boolean;
readonly openToWelcomeMainPage?: boolean;
readonly aiConfig?: {
- readonly asimovKey: string;
readonly ariaKey: string;
- readonly preferAria: boolean;
};
readonly sendASmile?: {
@@ -155,7 +153,7 @@ export interface IProductConfiguration {
readonly 'configurationSync.store'?: ConfigurationSyncStore;
- readonly 'sessionSync.store'?: Omit<ConfigurationSyncStore, 'insidersUrl' | 'stableUrl'>;
+ readonly 'editSessions.store'?: Omit<ConfigurationSyncStore, 'insidersUrl' | 'stableUrl'>;
readonly darwinUniversalAssetId?: string;
}
diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts
index 50ac38cf407..3e1cb2a7354 100644
--- a/src/vs/base/common/types.ts
+++ b/src/vs/base/common/types.ts
@@ -47,18 +47,9 @@ export function isObject(obj: unknown): obj is Object {
* @returns whether the provided parameter is of type `Buffer` or Uint8Array dervived type
*/
export function isTypedArray(obj: unknown): obj is Object {
+ const TypedArray = Object.getPrototypeOf(Uint8Array);
return typeof obj === 'object'
- && (obj instanceof Uint8Array ||
- obj instanceof Uint16Array ||
- obj instanceof Uint32Array ||
- obj instanceof Float32Array ||
- obj instanceof Float64Array ||
- obj instanceof Int8Array ||
- obj instanceof Int16Array ||
- obj instanceof Int32Array ||
- obj instanceof BigInt64Array ||
- obj instanceof BigUint64Array ||
- obj instanceof Uint8ClampedArray);
+ && obj instanceof TypedArray;
}
/**
diff --git a/src/vs/base/node/ps.ts b/src/vs/base/node/ps.ts
index 4eded868127..38cd56f7d75 100644
--- a/src/vs/base/node/ps.ts
+++ b/src/vs/base/node/ps.ts
@@ -52,6 +52,7 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> {
const ISSUE_REPORTER_HINT = /--vscode-window-kind=issue-reporter/;
const PROCESS_EXPLORER_HINT = /--vscode-window-kind=process-explorer/;
const UTILITY_NETWORK_HINT = /--utility-sub-type=network/;
+ const UTILITY_EXTENSION_HOST_HINT = /--vscode-utility-kind=extension-host/;
const WINDOWS_CRASH_REPORTER = /--crashes-directory/;
const WINDOWS_PTY = /\\pipe\\winpty-control/;
const WINDOWS_CONSOLE_HOST = /conhost\.exe/;
@@ -93,6 +94,10 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> {
if (UTILITY_NETWORK_HINT.exec(cmd)) {
return 'utility-network-service';
}
+
+ if (UTILITY_EXTENSION_HOST_HINT.exec(cmd)) {
+ return 'extension-host';
+ }
}
return matches[1];
}
diff --git a/src/vs/base/parts/ipc/node/ipc.net.ts b/src/vs/base/parts/ipc/node/ipc.net.ts
index 9831dcde97c..bff8e41d8e5 100644
--- a/src/vs/base/parts/ipc/node/ipc.net.ts
+++ b/src/vs/base/parts/ipc/node/ipc.net.ts
@@ -3,9 +3,10 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { createHash } from 'crypto';
-import { createConnection, createServer, Server as NetServer, Socket } from 'net';
-import { tmpdir } from 'os';
+// import { createHash } from 'crypto';
+import type { Server as NetServer, Socket } from 'net';
+// import { tmpdir } from 'os';
+import type * as zlib from 'zlib';
import { VSBuffer } from 'vs/base/common/buffer';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
@@ -15,7 +16,16 @@ import { Platform, platform } from 'vs/base/common/platform';
import { generateUuid } from 'vs/base/common/uuid';
import { ClientConnectionEvent, IPCServer } from 'vs/base/parts/ipc/common/ipc';
import { ChunkStream, Client, ISocket, Protocol, SocketCloseEvent, SocketCloseEventType, SocketDiagnostics, SocketDiagnosticsEventType } from 'vs/base/parts/ipc/common/ipc.net';
-import * as zlib from 'zlib';
+
+// TODO@bpasero remove me once electron utility process has landed
+function getNodeDependencies() {
+ return {
+ crypto: (require.__$__nodeRequire('crypto') as any) as typeof import('crypto'),
+ zlib: (require.__$__nodeRequire('zlib') as any) as typeof import('zlib'),
+ net: (require.__$__nodeRequire('net') as any) as typeof import('net'),
+ os: (require.__$__nodeRequire('os') as any) as typeof import('os')
+ };
+}
export class NodeSocket implements ISocket {
@@ -580,7 +590,7 @@ class ZlibInflateStream extends Disposable {
options: zlib.ZlibOptions
) {
super();
- this._zlibInflate = zlib.createInflateRaw(options);
+ this._zlibInflate = getNodeDependencies().zlib.createInflateRaw(options);
this._zlibInflate.on('error', (err) => {
this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateError, { message: err?.message, code: (<any>err)?.code });
this._onError.fire(err);
@@ -631,7 +641,7 @@ class ZlibDeflateStream extends Disposable {
) {
super();
- this._zlibDeflate = zlib.createDeflateRaw({
+ this._zlibDeflate = getNodeDependencies().zlib.createDeflateRaw({
windowBits: 15
});
this._zlibDeflate.on('error', (err) => {
@@ -692,7 +702,8 @@ function unmask(buffer: VSBuffer, mask: number): void {
// Read this before there's any chance it is overwritten
// Related to https://github.com/microsoft/vscode/issues/30624
-export const XDG_RUNTIME_DIR = <string | undefined>process.env['XDG_RUNTIME_DIR'];
+// TODO@bpasero revert me once electron utility process has landed
+export const XDG_RUNTIME_DIR = typeof process !== 'undefined' ? <string | undefined>process.env['XDG_RUNTIME_DIR'] : undefined;
const safeIpcPathLengths: { [platform: number]: number } = {
[Platform.Linux]: 107,
@@ -713,7 +724,7 @@ export function createRandomIPCHandle(): string {
if (XDG_RUNTIME_DIR) {
result = join(XDG_RUNTIME_DIR, `vscode-ipc-${randomSuffix}.sock`);
} else {
- result = join(tmpdir(), `vscode-ipc-${randomSuffix}.sock`);
+ result = join(getNodeDependencies().os.tmpdir(), `vscode-ipc-${randomSuffix}.sock`);
}
// Validate length
@@ -723,7 +734,7 @@ export function createRandomIPCHandle(): string {
}
export function createStaticIPCHandle(directoryPath: string, type: string, version: string): string {
- const scope = createHash('md5').update(directoryPath).digest('hex');
+ const scope = getNodeDependencies().crypto.createHash('md5').update(directoryPath).digest('hex');
// Windows: use named pipe
if (process.platform === 'win32') {
@@ -785,7 +796,7 @@ export function serve(port: number): Promise<Server>;
export function serve(namedPipe: string): Promise<Server>;
export function serve(hook: any): Promise<Server> {
return new Promise<Server>((c, e) => {
- const server = createServer();
+ const server = getNodeDependencies().net.createServer();
server.on('error', e);
server.listen(hook, () => {
@@ -800,7 +811,7 @@ export function connect(port: number, clientId: string): Promise<Client>;
export function connect(namedPipe: string, clientId: string): Promise<Client>;
export function connect(hook: any, clientId: string): Promise<Client> {
return new Promise<Client>((c, e) => {
- const socket = createConnection(hook, () => {
+ const socket = getNodeDependencies().net.createConnection(hook, () => {
socket.removeListener('error', e);
c(Client.fromSocket(new NodeSocket(socket, `ipc-client${clientId}`), clientId));
});
diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts
index 1a496080028..58a09cc29e5 100644
--- a/src/vs/base/parts/quickinput/browser/quickInput.ts
+++ b/src/vs/base/parts/quickinput/browser/quickInput.ts
@@ -450,6 +450,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
private _matchOnDescription = false;
private _matchOnDetail = false;
private _matchOnLabel = true;
+ private _matchOnLabelMode: 'fuzzy' | 'contiguous' = 'fuzzy';
private _sortByLabel = true;
private _autoFocusOnList = true;
private _keepScrollPosition = false;
@@ -595,6 +596,15 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
this.update();
}
+ get matchOnLabelMode() {
+ return this._matchOnLabelMode;
+ }
+
+ set matchOnLabelMode(matchOnLabelMode: 'fuzzy' | 'contiguous') {
+ this._matchOnLabelMode = matchOnLabelMode;
+ this.update();
+ }
+
get sortByLabel() {
return this._sortByLabel;
}
@@ -994,6 +1004,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
this.ui.list.matchOnDescription = this.matchOnDescription;
this.ui.list.matchOnDetail = this.matchOnDetail;
this.ui.list.matchOnLabel = this.matchOnLabel;
+ this.ui.list.matchOnLabelMode = this.matchOnLabelMode;
this.ui.list.sortByLabel = this.sortByLabel;
if (this.itemsUpdated) {
this.itemsUpdated = false;
diff --git a/src/vs/base/parts/quickinput/browser/quickInputList.ts b/src/vs/base/parts/quickinput/browser/quickInputList.ts
index 2ada8ddcc60..3292370eb5a 100644
--- a/src/vs/base/parts/quickinput/browser/quickInputList.ts
+++ b/src/vs/base/parts/quickinput/browser/quickInputList.ts
@@ -17,10 +17,11 @@ import { compareAnything } from 'vs/base/common/comparers';
import { memoize } from 'vs/base/common/decorators';
import { Emitter, Event } from 'vs/base/common/event';
import { IMatch } from 'vs/base/common/filters';
-import { matchesFuzzyIconAware, parseLabelWithIcons } from 'vs/base/common/iconLabels';
+import { IParsedLabelWithIcons, matchesFuzzyIconAware, parseLabelWithIcons } from 'vs/base/common/iconLabels';
import { KeyCode } from 'vs/base/common/keyCodes';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import * as platform from 'vs/base/common/platform';
+import { ltrim } from 'vs/base/common/strings';
import { withNullAsUndefined } from 'vs/base/common/types';
import { IQuickInputOptions } from 'vs/base/parts/quickinput/browser/quickInput';
import { getIconClass } from 'vs/base/parts/quickinput/browser/quickInputUtils';
@@ -258,6 +259,7 @@ export class QuickInputList {
matchOnDescription = false;
matchOnDetail = false;
matchOnLabel = true;
+ matchOnLabelMode: 'fuzzy' | 'contiguous' = 'fuzzy';
matchOnMeta = true;
sortByLabel = true;
private readonly _onChangedAllVisibleChecked = new Emitter<boolean>();
@@ -610,6 +612,8 @@ export class QuickInputList {
this.list.layout();
return false;
}
+
+ const queryWithWhitespace = query;
query = query.trim();
// Reset filtering
@@ -628,7 +632,12 @@ export class QuickInputList {
else {
let currentSeparator: IQuickPickSeparator | undefined;
this.elements.forEach(element => {
- const labelHighlights = this.matchOnLabel ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneLabel))) : undefined;
+ let labelHighlights: IMatch[] | undefined;
+ if (this.matchOnLabelMode === 'fuzzy') {
+ labelHighlights = this.matchOnLabel ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneLabel))) : undefined;
+ } else {
+ labelHighlights = this.matchOnLabel ? withNullAsUndefined(matchesContiguousIconAware(queryWithWhitespace, parseLabelWithIcons(element.saneLabel))) : undefined;
+ }
const descriptionHighlights = this.matchOnDescription ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneDescription || ''))) : undefined;
const detailHighlights = this.matchOnDetail ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneDetail || ''))) : undefined;
const metaHighlights = this.matchOnMeta ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneMeta || ''))) : undefined;
@@ -726,6 +735,43 @@ export class QuickInputList {
}
}
+export function matchesContiguousIconAware(query: string, target: IParsedLabelWithIcons): IMatch[] | null {
+
+ const { text, iconOffsets } = target;
+
+ // Return early if there are no icon markers in the word to match against
+ if (!iconOffsets || iconOffsets.length === 0) {
+ return matchesContiguous(query, text);
+ }
+
+ // Trim the word to match against because it could have leading
+ // whitespace now if the word started with an icon
+ const wordToMatchAgainstWithoutIconsTrimmed = ltrim(text, ' ');
+ const leadingWhitespaceOffset = text.length - wordToMatchAgainstWithoutIconsTrimmed.length;
+
+ // match on value without icon
+ const matches = matchesContiguous(query, wordToMatchAgainstWithoutIconsTrimmed);
+
+ // Map matches back to offsets with icon and trimming
+ if (matches) {
+ for (const match of matches) {
+ const iconOffset = iconOffsets[match.start + leadingWhitespaceOffset] /* icon offsets at index */ + leadingWhitespaceOffset /* overall leading whitespace offset */;
+ match.start += iconOffset;
+ match.end += iconOffset;
+ }
+ }
+
+ return matches;
+}
+
+function matchesContiguous(word: string, wordToMatchAgainst: string): IMatch[] | null {
+ const matchIndex = wordToMatchAgainst.toLowerCase().indexOf(word.toLowerCase());
+ if (matchIndex !== -1) {
+ return [{ start: matchIndex, end: matchIndex + word.length }];
+ }
+ return null;
+}
+
function compareEntries(elementA: ListElement, elementB: ListElement, lookFor: string): number {
const labelHighlightsA = elementA.labelHighlights || [];
diff --git a/src/vs/base/parts/quickinput/common/quickInput.ts b/src/vs/base/parts/quickinput/common/quickInput.ts
index bf9979e00b1..34824941387 100644
--- a/src/vs/base/parts/quickinput/common/quickInput.ts
+++ b/src/vs/base/parts/quickinput/common/quickInput.ts
@@ -292,6 +292,13 @@ export interface IQuickPick<T extends IQuickPickItem> extends IQuickInput {
matchOnLabel: boolean;
+ /**
+ * The mode to filter label with. Fuzzy will use fuzzy searching and
+ * contiguous will make filter entries that do not contain the exact string
+ * (including whitespace). This defaults to `'fuzzy'`.
+ */
+ matchOnLabelMode: 'fuzzy' | 'contiguous';
+
sortByLabel: boolean;
autoFocusOnList: boolean;
diff --git a/src/vs/base/parts/sandbox/electron-browser/preload.js b/src/vs/base/parts/sandbox/electron-browser/preload.js
index 3e25c4097f1..53e38ce14c2 100644
--- a/src/vs/base/parts/sandbox/electron-browser/preload.js
+++ b/src/vs/base/parts/sandbox/electron-browser/preload.js
@@ -145,7 +145,7 @@
/**
* @param {string} channel
* @param {any[]} args
- * @returns {Promise<any> | undefined}
+ * @returns {Promise<any> | never}
*/
invoke(channel, ...args) {
if (validateIPC(channel)) {
@@ -156,7 +156,7 @@
/**
* @param {string} channel
* @param {(event: IpcRendererEvent, ...args: any[]) => void} listener
- * @returns {IpcRenderer}
+ * @returns {IpcRenderer | never}
*/
on(channel, listener) {
if (validateIPC(channel)) {
@@ -169,7 +169,7 @@
/**
* @param {string} channel
* @param {(event: IpcRendererEvent, ...args: any[]) => void} listener
- * @returns {IpcRenderer}
+ * @returns {IpcRenderer | never}
*/
once(channel, listener) {
if (validateIPC(channel)) {
@@ -182,7 +182,7 @@
/**
* @param {string} channel
* @param {(event: IpcRendererEvent, ...args: any[]) => void} listener
- * @returns {IpcRenderer}
+ * @returns {IpcRenderer | never}
*/
removeListener(channel, listener) {
if (validateIPC(channel)) {
diff --git a/src/vs/base/test/common/arrays.test.ts b/src/vs/base/test/common/arrays.test.ts
index 4519661b5c9..23e2b720421 100644
--- a/src/vs/base/test/common/arrays.test.ts
+++ b/src/vs/base/test/common/arrays.test.ts
@@ -6,6 +6,19 @@ import * as assert from 'assert';
import * as arrays from 'vs/base/common/arrays';
suite('Arrays', () => {
+
+ test('removeFastWithoutKeepingOrder', () => {
+ const array = [1, 4, 5, 7, 55, 59, 60, 61, 64, 69];
+ arrays.removeFastWithoutKeepingOrder(array, 1);
+ assert.deepStrictEqual(array, [1, 69, 5, 7, 55, 59, 60, 61, 64]);
+
+ arrays.removeFastWithoutKeepingOrder(array, 0);
+ assert.deepStrictEqual(array, [64, 69, 5, 7, 55, 59, 60, 61]);
+
+ arrays.removeFastWithoutKeepingOrder(array, 7);
+ assert.deepStrictEqual(array, [64, 69, 5, 7, 55, 59, 60]);
+ });
+
test('findFirst', () => {
const array = [1, 4, 5, 7, 55, 59, 60, 61, 64, 69];
diff --git a/src/vs/base/test/common/collections.test.ts b/src/vs/base/test/common/collections.test.ts
index 138d7486390..9dfe59a58fe 100644
--- a/src/vs/base/test/common/collections.test.ts
+++ b/src/vs/base/test/common/collections.test.ts
@@ -8,27 +8,6 @@ import * as collections from 'vs/base/common/collections';
suite('Collections', () => {
- test('forEach', () => {
- collections.forEach({}, () => assert(false));
- collections.forEach(Object.create(null), () => assert(false));
-
- let count = 0;
- collections.forEach({ toString: 123 }, () => count++);
- assert.strictEqual(count, 1);
-
- count = 0;
- const dict = Object.create(null);
- dict['toString'] = 123;
- collections.forEach(dict, () => count++);
- assert.strictEqual(count, 1);
-
- collections.forEach(dict, () => false);
-
- // don't iterate over properties that are not on the object itself
- const test = Object.create({ 'derived': true });
- collections.forEach(test, () => assert(false));
- });
-
test('groupBy', () => {
const group1 = 'a', group2 = 'b';
diff --git a/src/vs/base/test/common/event.test.ts b/src/vs/base/test/common/event.test.ts
index a34566c10f1..39d878bce5d 100644
--- a/src/vs/base/test/common/event.test.ts
+++ b/src/vs/base/test/common/event.test.ts
@@ -8,7 +8,8 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { errorHandler, setUnexpectedErrorHandler } from 'vs/base/common/errors';
import { AsyncEmitter, DebounceEmitter, Emitter, Event, EventBufferer, EventMultiplexer, IWaitUntil, MicrotaskEmitter, PauseableEmitter, Relay } from 'vs/base/common/event';
import { DisposableStore, IDisposable, isDisposable, setDisposableTracker, toDisposable } from 'vs/base/common/lifecycle';
-import { DisposableTracker } from 'vs/base/test/common/utils';
+import { observableValue, transaction } from 'vs/base/common/observable';
+import { DisposableTracker, ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
namespace Samples {
@@ -624,6 +625,33 @@ suite('PausableEmitter', function () {
});
});
+suite('Event utils - ensureNoDisposablesAreLeakedInTestSuite', function () {
+ ensureNoDisposablesAreLeakedInTestSuite();
+
+ test('fromObservable', function () {
+
+ const obs = observableValue('test', 12);
+ const event = Event.fromObservable(obs);
+
+ const values: number[] = [];
+ const d = event(n => { values.push(n); });
+
+ obs.set(3, undefined);
+ obs.set(13, undefined);
+ obs.set(3, undefined);
+ obs.set(33, undefined);
+ obs.set(1, undefined);
+
+ transaction(tx => {
+ obs.set(334, tx);
+ obs.set(99, tx);
+ });
+
+ assert.deepStrictEqual(values, ([3, 13, 3, 33, 1, 99]));
+ d.dispose();
+ });
+});
+
suite('Event utils', () => {
suite('EventBufferer', () => {
diff --git a/src/vs/base/test/common/map.test.ts b/src/vs/base/test/common/map.test.ts
index 3a979de3229..896d06ec812 100644
--- a/src/vs/base/test/common/map.test.ts
+++ b/src/vs/base/test/common/map.test.ts
@@ -852,12 +852,12 @@ suite('Map', () => {
for (const item of keys) {
tst.set(item, true);
- assert.ok(tst._isBalanced());
+ assert.ok(tst._isBalanced(), `SET${item}|${keys.map(String).join()}`);
}
for (const item of keys) {
tst.delete(item);
- assert.ok(tst._isBalanced());
+ assert.ok(tst._isBalanced(), `DEL${item}|${keys.map(String).join()}`);
}
}
});
diff --git a/src/vs/base/test/common/observable.test.ts b/src/vs/base/test/common/observable.test.ts
new file mode 100644
index 00000000000..a3460dca22b
--- /dev/null
+++ b/src/vs/base/test/common/observable.test.ts
@@ -0,0 +1,507 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as assert from 'assert';
+import { Emitter } from 'vs/base/common/event';
+import { ISettableObservable, autorun, derived, ITransaction, observableFromEvent, observableValue, transaction } from 'vs/base/common/observable';
+import { BaseObservable, IObservable, IObserver } from 'vs/base/common/observableImpl/base';
+
+suite('observable integration', () => {
+ test('basic observable + autorun', () => {
+ const log = new Log();
+ const observable = observableValue('MyObservableValue', 0);
+
+ autorun('MyAutorun', (reader) => {
+ log.log(`value: ${observable.read(reader)}`);
+ });
+ assert.deepStrictEqual(log.getAndClearEntries(), ['value: 0']);
+
+ observable.set(1, undefined);
+ assert.deepStrictEqual(log.getAndClearEntries(), ['value: 1']);
+
+ observable.set(1, undefined);
+ assert.deepStrictEqual(log.getAndClearEntries(), []);
+
+ transaction((tx) => {
+ observable.set(2, tx);
+ assert.deepStrictEqual(log.getAndClearEntries(), []);
+
+ observable.set(3, tx);
+ assert.deepStrictEqual(log.getAndClearEntries(), []);
+ });
+
+ assert.deepStrictEqual(log.getAndClearEntries(), ['value: 3']);
+ });
+
+ test('basic computed + autorun', () => {
+ const log = new Log();
+ const observable1 = observableValue('MyObservableValue1', 0);
+ const observable2 = observableValue('MyObservableValue2', 0);
+
+ const computed = derived('computed', (reader) => {
+ const value1 = observable1.read(reader);
+ const value2 = observable2.read(reader);
+ const sum = value1 + value2;
+ log.log(`recompute: ${value1} + ${value2} = ${sum}`);
+ return sum;
+ });
+
+ autorun('MyAutorun', (reader) => {
+ log.log(`value: ${computed.read(reader)}`);
+ });
+ assert.deepStrictEqual(log.getAndClearEntries(), [
+ 'recompute: 0 + 0 = 0',
+ 'value: 0',
+ ]);
+
+ observable1.set(1, undefined);
+ assert.deepStrictEqual(log.getAndClearEntries(), [
+ 'recompute: 1 + 0 = 1',
+ 'value: 1',
+ ]);
+
+ observable2.set(1, undefined);
+ assert.deepStrictEqual(log.getAndClearEntries(), [
+ 'recompute: 1 + 1 = 2',
+ 'value: 2',
+ ]);
+
+ transaction((tx) => {
+ observable1.set(5, tx);
+ assert.deepStrictEqual(log.getAndClearEntries(), []);
+
+ observable2.set(5, tx);
+ assert.deepStrictEqual(log.getAndClearEntries(), []);
+ });
+
+ assert.deepStrictEqual(log.getAndClearEntries(), [
+ 'recompute: 5 + 5 = 10',
+ 'value: 10',
+ ]);
+
+ transaction((tx) => {
+ observable1.set(6, tx);
+ assert.deepStrictEqual(log.getAndClearEntries(), []);
+
+ observable2.set(4, tx);
+ assert.deepStrictEqual(log.getAndClearEntries(), []);
+ });
+
+ assert.deepStrictEqual(log.getAndClearEntries(), ['recompute: 6 + 4 = 10']);
+ });
+
+ test('read during transaction', () => {
+ const log = new Log();
+ const observable1 = observableValue('MyObservableValue1', 0);
+ const observable2 = observableValue('MyObservableValue2', 0);
+
+ const computed = derived('computed', (reader) => {
+ const value1 = observable1.read(reader);
+ const value2 = observable2.read(reader);
+ const sum = value1 + value2;
+ log.log(`recompute: ${value1} + ${value2} = ${sum}`);
+ return sum;
+ });
+
+ autorun('MyAutorun', (reader) => {
+ log.log(`value: ${computed.read(reader)}`);
+ });
+
+ assert.deepStrictEqual(log.getAndClearEntries(), [
+ 'recompute: 0 + 0 = 0',
+ 'value: 0',
+ ]);
+
+ log.log(`computed is ${computed.get()}`);
+ assert.deepStrictEqual(log.getAndClearEntries(), ['computed is 0']);
+
+ transaction((tx) => {
+ observable1.set(-1, tx);
+ log.log(`computed is ${computed.get()}`);
+ assert.deepStrictEqual(log.getAndClearEntries(), [
+ 'recompute: -1 + 0 = -1',
+ 'computed is -1',
+ ]);
+
+ log.log(`computed is ${computed.get()}`);
+ assert.deepStrictEqual(log.getAndClearEntries(), ['computed is -1']);
+
+ observable2.set(1, tx);
+ assert.deepStrictEqual(log.getAndClearEntries(), []);
+ });
+ assert.deepStrictEqual(log.getAndClearEntries(), [
+ 'recompute: -1 + 1 = 0',
+ 'value: 0',
+ ]);
+ });
+
+ test('topological order', () => {
+ const log = new Log();
+ const observable1 = observableValue('MyObservableValue1', 0);
+ const observable2 = observableValue('MyObservableValue2', 0);
+
+ const computed1 = derived('computed1', (reader) => {
+ const value1 = observable1.read(reader);
+ const value2 = observable2.read(reader);
+ const sum = value1 + value2;
+ log.log(`recompute1: ${value1} + ${value2} = ${sum}`);
+ return sum;
+ });
+
+ const computed2 = derived('computed2', (reader) => {
+ const value1 = computed1.read(reader);
+ const value2 = observable1.read(reader);
+ const value3 = observable2.read(reader);
+ const sum = value1 + value2 + value3;
+ log.log(`recompute2: ${value1} + ${value2} + ${value3} = ${sum}`);
+ return sum;
+ });
+
+ const computed3 = derived('computed3', (reader) => {
+ const value1 = computed2.read(reader);
+ const value2 = observable1.read(reader);
+ const value3 = observable2.read(reader);
+ const sum = value1 + value2 + value3;
+ log.log(`recompute3: ${value1} + ${value2} + ${value3} = ${sum}`);
+ return sum;
+ });
+
+ autorun('MyAutorun', (reader) => {
+ log.log(`value: ${computed3.read(reader)}`);
+ });
+ assert.deepStrictEqual(log.getAndClearEntries(), [
+ 'recompute1: 0 + 0 = 0',
+ 'recompute2: 0 + 0 + 0 = 0',
+ 'recompute3: 0 + 0 + 0 = 0',
+ 'value: 0',
+ ]);
+
+ observable1.set(1, undefined);
+ assert.deepStrictEqual(log.getAndClearEntries(), [
+ 'recompute1: 1 + 0 = 1',
+ 'recompute2: 1 + 1 + 0 = 2',
+ 'recompute3: 2 + 1 + 0 = 3',
+ 'value: 3',
+ ]);
+
+ transaction((tx) => {
+ observable1.set(2, tx);
+ log.log(`computed2: ${computed2.get()}`);
+ assert.deepStrictEqual(log.getAndClearEntries(), [
+ 'recompute1: 2 + 0 = 2',
+ 'recompute2: 2 + 2 + 0 = 4',
+ 'computed2: 4',
+ ]);
+
+ observable1.set(3, tx);
+ log.log(`computed2: ${computed2.get()}`);
+ assert.deepStrictEqual(log.getAndClearEntries(), [
+ 'recompute1: 3 + 0 = 3',
+ 'recompute2: 3 + 3 + 0 = 6',
+ 'computed2: 6',
+ ]);
+ });
+ assert.deepStrictEqual(log.getAndClearEntries(), [
+ 'recompute3: 6 + 3 + 0 = 9',
+ 'value: 9',
+ ]);
+ });
+
+ test('self-disposing autorun', () => {
+ const log = new Log();
+
+ const observable1 = new LoggingObservableValue('MyObservableValue1', 0, log);
+ const observable2 = new LoggingObservableValue('MyObservableValue2', 0, log);
+ const observable3 = new LoggingObservableValue('MyObservableValue3', 0, log);
+
+ const d = autorun('autorun', (reader) => {
+ if (observable1.read(reader) >= 2) {
+ observable2.read(reader);
+ d.dispose();
+ observable3.read(reader);
+ }
+ });
+ assert.deepStrictEqual(log.getAndClearEntries(), [
+ 'MyObservableValue1.firstObserverAdded',
+ 'MyObservableValue1.get',
+ ]);
+
+ observable1.set(1, undefined);
+ assert.deepStrictEqual(log.getAndClearEntries(), [
+ 'MyObservableValue1.set (value 1)',
+ 'MyObservableValue1.get',
+ ]);
+
+ observable1.set(2, undefined);
+ assert.deepStrictEqual(log.getAndClearEntries(), [
+ 'MyObservableValue1.set (value 2)',
+ 'MyObservableValue1.get',
+ 'MyObservableValue2.firstObserverAdded',
+ 'MyObservableValue2.get',
+ 'MyObservableValue1.lastObserverRemoved',
+ 'MyObservableValue2.lastObserverRemoved',
+ 'MyObservableValue3.get',
+ ]);
+ });
+
+ test('from event', () => {
+ const log = new Log();
+
+ let value = 0;
+ const eventEmitter = new Emitter<void>();
+
+ let id = 0;
+ const observable = observableFromEvent(
+ (handler) => {
+ const curId = id++;
+ log.log(`subscribed handler ${curId}`);
+ const disposable = eventEmitter.event(handler);
+
+ return {
+ dispose: () => {
+ log.log(`unsubscribed handler ${curId}`);
+ disposable.dispose();
+ },
+ };
+ },
+ () => {
+ log.log(`compute value ${value}`);
+ return value;
+ }
+ );
+ assert.deepStrictEqual(log.getAndClearEntries(), []);
+
+ log.log(`get value: ${observable.get()}`);
+ assert.deepStrictEqual(log.getAndClearEntries(), [
+ 'compute value 0',
+ 'get value: 0',
+ ]);
+
+ log.log(`get value: ${observable.get()}`);
+ assert.deepStrictEqual(log.getAndClearEntries(), [
+ 'compute value 0',
+ 'get value: 0',
+ ]);
+
+ const shouldReadObservable = observableValue('shouldReadObservable', true);
+
+ const autorunDisposable = autorun('MyAutorun', (reader) => {
+ if (shouldReadObservable.read(reader)) {
+ observable.read(reader);
+ log.log(
+ `autorun, should read: true, value: ${observable.read(reader)}`
+ );
+ } else {
+ log.log(`autorun, should read: false`);
+ }
+ });
+ assert.deepStrictEqual(log.getAndClearEntries(), [
+ 'subscribed handler 0',
+ 'compute value 0',
+ 'autorun, should read: true, value: 0',
+ ]);
+
+ log.log(`get value: ${observable.get()}`);
+ assert.deepStrictEqual(log.getAndClearEntries(), ['get value: 0']);
+
+ value = 1;
+ eventEmitter.fire();
+ assert.deepStrictEqual(log.getAndClearEntries(), [
+ 'compute value 1',
+ 'autorun, should read: true, value: 1',
+ ]);
+
+ shouldReadObservable.set(false, undefined);
+ assert.deepStrictEqual(log.getAndClearEntries(), [
+ 'autorun, should read: false',
+ 'unsubscribed handler 0',
+ ]);
+
+ shouldReadObservable.set(true, undefined);
+ assert.deepStrictEqual(log.getAndClearEntries(), [
+ 'subscribed handler 1',
+ 'compute value 1',
+ 'autorun, should read: true, value: 1',
+ ]);
+
+ autorunDisposable.dispose();
+ assert.deepStrictEqual(log.getAndClearEntries(), [
+ 'unsubscribed handler 1',
+ ]);
+ });
+
+ test('get without observers', () => {
+ // Maybe this scenario should not be supported.
+
+ const log = new Log();
+ const observable1 = observableValue('MyObservableValue1', 0);
+ const computed1 = derived('computed', (reader) => {
+ const value1 = observable1.read(reader);
+ const result = value1 % 3;
+ log.log(`recompute1: ${value1} % 3 = ${result}`);
+ return result;
+ });
+ const computed2 = derived('computed', (reader) => {
+ const value1 = computed1.read(reader);
+
+ const result = value1 * 2;
+ log.log(`recompute2: ${value1} * 2 = ${result}`);
+ return result;
+ });
+ const computed3 = derived('computed', (reader) => {
+ const value1 = computed1.read(reader);
+
+ const result = value1 * 3;
+ log.log(`recompute3: ${value1} * 3 = ${result}`);
+ return result;
+ });
+ const computedSum = derived('computed', (reader) => {
+ const value1 = computed2.read(reader);
+ const value2 = computed3.read(reader);
+
+ const result = value1 + value2;
+ log.log(`recompute4: ${value1} + ${value2} = ${result}`);
+ return result;
+ });
+ assert.deepStrictEqual(log.getAndClearEntries(), []);
+
+ observable1.set(1, undefined);
+ assert.deepStrictEqual(log.getAndClearEntries(), []);
+
+ log.log(`value: ${computedSum.get()}`);
+ assert.deepStrictEqual(log.getAndClearEntries(), [
+ 'recompute1: 1 % 3 = 1',
+ 'recompute2: 1 * 2 = 2',
+ 'recompute3: 1 * 3 = 3',
+ 'recompute4: 2 + 3 = 5',
+ 'value: 5',
+ ]);
+
+ log.log(`value: ${computedSum.get()}`);
+ assert.deepStrictEqual(log.getAndClearEntries(), [
+ 'recompute1: 1 % 3 = 1',
+ 'recompute2: 1 * 2 = 2',
+ 'recompute3: 1 * 3 = 3',
+ 'recompute4: 2 + 3 = 5',
+ 'value: 5',
+ ]);
+ });
+});
+
+suite('observable details', () => {
+ test('1', () => {
+ const log = new Log();
+
+ const shouldReadObservable = observableValue('shouldReadObservable', true);
+ const observable = new LoggingObservableValue('observable', 0, log);
+ const computed = derived('test', reader => {
+ if (shouldReadObservable.read(reader)) {
+ return observable.read(reader) * 2;
+ }
+ return 1;
+ });
+ autorun('test', reader => {
+ const value = computed.read(reader);
+ log.log(`autorun: ${value}`);
+ });
+
+ assert.deepStrictEqual(log.getAndClearEntries(), (["observable.firstObserverAdded", "observable.get", "autorun: 0"]));
+
+ transaction(tx => {
+ observable.set(1, tx);
+ assert.deepStrictEqual(log.getAndClearEntries(), (["observable.set (value 1)"]));
+
+ shouldReadObservable.set(false, tx);
+ assert.deepStrictEqual(log.getAndClearEntries(), ([]));
+
+ computed.get();
+ assert.deepStrictEqual(log.getAndClearEntries(), (["observable.lastObserverRemoved"]));
+ });
+ assert.deepStrictEqual(log.getAndClearEntries(), (["autorun: 1"]));
+ });
+});
+
+export class LoggingObserver implements IObserver {
+ private count = 0;
+
+ constructor(public readonly debugName: string, private readonly log: Log) {
+ }
+
+ beginUpdate<T>(observable: IObservable<T, void>): void {
+ this.count++;
+ this.log.log(`${this.debugName}.beginUpdate (count ${this.count})`);
+ }
+ handleChange<T, TChange>(observable: IObservable<T, TChange>, change: TChange): void {
+ this.log.log(`${this.debugName}.handleChange (count ${this.count})`);
+ }
+ endUpdate<T>(observable: IObservable<T, void>): void {
+ this.log.log(`${this.debugName}.endUpdate (count ${this.count})`);
+ this.count--;
+ }
+}
+
+export class LoggingObservableValue<T, TChange = void>
+ extends BaseObservable<T, TChange>
+ implements ISettableObservable<T, TChange>
+{
+ private value: T;
+
+ constructor(public readonly debugName: string, initialValue: T, private readonly log: Log) {
+ super();
+ this.value = initialValue;
+ }
+
+ protected override onFirstObserverAdded(): void {
+ this.log.log(`${this.debugName}.firstObserverAdded`);
+ }
+
+ protected override onLastObserverRemoved(): void {
+ this.log.log(`${this.debugName}.lastObserverRemoved`);
+ }
+
+ public get(): T {
+ this.log.log(`${this.debugName}.get`);
+ 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);
+ }, () => `Setting ${this.debugName}`);
+ return;
+ }
+
+ this.log.log(`${this.debugName}.set (value ${value})`);
+
+ this.value = value;
+
+ for (const observer of this.observers) {
+ tx.updateObserver(observer, this);
+ observer.handleChange(this, change);
+ }
+ }
+
+ override toString(): string {
+ return `${this.debugName}: ${this.value}`;
+ }
+}
+
+class Log {
+ private readonly entries: string[] = [];
+ public log(message: string): void {
+ this.entries.push(message);
+ }
+
+ public getAndClearEntries(): string[] {
+ const entries = [...this.entries];
+ this.entries.length = 0;
+ return entries;
+ }
+}
diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/extensionsCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/extensionsCleaner.ts
index 221d77ffdfa..0116d6e8aa8 100644
--- a/src/vs/code/electron-browser/sharedProcess/contrib/extensionsCleaner.ts
+++ b/src/vs/code/electron-browser/sharedProcess/contrib/extensionsCleaner.ts
@@ -34,7 +34,7 @@ export class ExtensionsCleaner extends Disposable {
) {
super();
- extensionManagementService.removeUninstalledExtensions(this.userDataProfilesService.profiles.length === 0);
+ extensionManagementService.removeUninstalledExtensions(this.userDataProfilesService.profiles.length === 1);
migrateUnsupportedExtensions(extensionManagementService, extensionGalleryService, extensionStorageService, extensionEnablementService, logService);
ExtensionStorageService.removeOutdatedExtensionVersions(extensionManagementService, storageService);
this._register(instantiationService.createInstance(ProfileExtensionsCleaner));
@@ -66,7 +66,7 @@ class ProfileExtensionsCleaner extends Disposable {
this.logService.error(error);
}
- if (all.length === 0) {
+ if (all.length === 1) {
// Exit profile mode
this.profileModeDisposables.clear();
// Listen for entering into profile mode
diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts
index 11116cc4cce..7c128863922 100644
--- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts
+++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts
@@ -31,7 +31,7 @@ import { INativeEnvironmentService } from 'vs/platform/environment/common/enviro
import { SharedProcessEnvironmentService } from 'vs/platform/sharedProcess/node/sharedProcessEnvironmentService';
import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService';
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService';
-import { IExtensionGalleryService, IExtensionManagementService, IExtensionTipsService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
+import { IDefaultExtensionsProfileInitService, IExtensionGalleryService, IExtensionManagementService, IExtensionTipsService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionManagementChannel, ExtensionTipsChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc';
import { ExtensionTipsService } from 'vs/platform/extensionManagement/electron-sandbox/extensionTipsService';
import { ExtensionManagementService, INativeServerExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
@@ -64,7 +64,6 @@ import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetry
import { TelemetryLogAppender } from 'vs/platform/telemetry/common/telemetryLogAppender';
import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
import { supportsTelemetry, ITelemetryAppender, NullAppender, NullTelemetryService, getPiiPathsFromEnvironment } from 'vs/platform/telemetry/common/telemetryUtils';
-import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender';
import { CustomEndpointTelemetryService } from 'vs/platform/telemetry/node/customEndpointTelemetryService';
import { LocalReconnectConstants, TerminalIpcChannels, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
import { ILocalPtyService } from 'vs/platform/terminal/electron-sandbox/terminal';
@@ -106,6 +105,7 @@ import { PolicyChannelClient } from 'vs/platform/policy/common/policyIpc';
import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy';
import { UserDataProfilesNativeService } from 'vs/platform/userDataProfile/electron-sandbox/userDataProfile';
import { OneDataSystemWebAppender } from 'vs/platform/telemetry/browser/1dsAppender';
+import { DefaultExtensionsProfileInitService } from 'vs/platform/extensionManagement/electron-sandbox/defaultExtensionsProfileInit';
class SharedProcessMain extends Disposable {
@@ -232,7 +232,7 @@ class SharedProcessMain extends Disposable {
fileService.registerProvider(Schemas.vscodeUserData, userDataFileSystemProvider);
// User Data Profiles
- const userDataProfilesService = this._register(new UserDataProfilesNativeService(this.configuration.profiles, mainProcessService, environmentService, fileService, logService));
+ const userDataProfilesService = this._register(new UserDataProfilesNativeService(this.configuration.profiles, mainProcessService, environmentService));
services.set(IUserDataProfilesService, userDataProfilesService);
// Configuration
@@ -281,16 +281,10 @@ class SharedProcessMain extends Disposable {
const logAppender = new TelemetryLogAppender(loggerService, environmentService);
appenders.push(logAppender);
const { installSourcePath } = environmentService;
- const internalTesting = configurationService.getValue<boolean>('telemetry.internalTesting');
- if (internalTesting && productService.aiConfig?.ariaKey) {
- const collectorAppender = new OneDataSystemWebAppender('monacoworkbench', null, productService.aiConfig.ariaKey);
+ if (productService.aiConfig?.ariaKey) {
+ const collectorAppender = new OneDataSystemWebAppender(configurationService, 'monacoworkbench', null, productService.aiConfig.ariaKey);
this._register(toDisposable(() => collectorAppender.flush())); // Ensure the 1DS appender is disposed so that it flushes remaining data
appenders.push(collectorAppender);
- } else if (productService.aiConfig && productService.aiConfig.asimovKey) {
- // Application Insights
- const appInsightsAppender = new AppInsightsAppender('monacoworkbench', null, productService.aiConfig.asimovKey);
- this._register(toDisposable(() => appInsightsAppender.flush())); // Ensure the AI appender is disposed so that it flushes remaining data
- appenders.push(appInsightsAppender);
}
telemetryService = new TelemetryService({
@@ -316,6 +310,7 @@ class SharedProcessMain extends Disposable {
services.set(IExtensionsProfileScannerService, new SyncDescriptor(ExtensionsProfileScannerService));
services.set(IExtensionsScannerService, new SyncDescriptor(ExtensionsScannerService));
services.set(INativeServerExtensionManagementService, new SyncDescriptor(ExtensionManagementService));
+ services.set(IDefaultExtensionsProfileInitService, new SyncDescriptor(DefaultExtensionsProfileInitService));
// Extension Gallery
services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService));
@@ -426,6 +421,9 @@ class SharedProcessMain extends Disposable {
// Worker
const sharedProcessWorkerChannel = ProxyChannel.fromService(accessor.get(ISharedProcessWorkerService));
this.server.registerChannel(ipcSharedProcessWorkerChannelName, sharedProcessWorkerChannel);
+
+ // Default Extensions Profile Init
+ this.server.registerChannel('IDefaultExtensionsProfileInitService', ProxyChannel.fromService(accessor.get(IDefaultExtensionsProfileInitService)));
}
private registerErrorHandler(logService: ILogService): void {
diff --git a/src/vs/code/electron-browser/workbench/workbench.html b/src/vs/code/electron-browser/workbench/workbench.html
deleted file mode 100644
index 47066f520be..00000000000
--- a/src/vs/code/electron-browser/workbench/workbench.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<!-- Copyright (C) Microsoft Corporation. All rights reserved. -->
-<!DOCTYPE html>
-<html>
- <head>
- <meta charset="utf-8" />
- <meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' https: data: blob: vscode-remote-resource:; media-src 'self'; frame-src 'self' vscode-webview:; object-src 'self'; script-src 'self' 'unsafe-eval' blob:; style-src 'self' 'unsafe-inline'; connect-src 'self' https: ws:; font-src 'self' https: vscode-remote-resource:;">
- <meta http-equiv="Content-Security-Policy" content="require-trusted-types-for 'script'; trusted-types amdLoader cellRendererEditorText defaultWorkerFactory diffEditorWidget editorGhostText domLineBreaksComputer editorViewLayer diffReview dompurify notebookRenderer safeInnerHtml standaloneColorizer tokenizeToString;">
- </head>
- <body aria-label="">
- </body>
-
- <!-- Init Bootstrap Helpers -->
- <script src="../../../../bootstrap.js"></script>
- <script src="../../../../vs/loader.js"></script>
- <script src="../../../../bootstrap-window.js"></script>
-
- <!-- Startup via workbench.js -->
- <script src="workbench.js"></script>
-</html>
diff --git a/src/vs/code/electron-browser/workbench/workbench.js b/src/vs/code/electron-browser/workbench/workbench.js
deleted file mode 100644
index 7ceef489fcc..00000000000
--- a/src/vs/code/electron-browser/workbench/workbench.js
+++ /dev/null
@@ -1,213 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-/// <reference path="../../../../typings/require.d.ts" />
-
-//@ts-check
-(function () {
- 'use strict';
-
- const bootstrapWindow = bootstrapWindowLib();
-
- // Add a perf entry right from the top
- performance.mark('code/didStartRenderer');
-
- // Load workbench main JS, CSS and NLS all in parallel. This is an
- // optimization to prevent a waterfall of loading to happen, because
- // we know for a fact that workbench.desktop.main will depend on
- // the related CSS and NLS counterparts.
- bootstrapWindow.load([
- 'vs/workbench/workbench.desktop.main',
- 'vs/nls!vs/workbench/workbench.desktop.main',
- 'vs/css!vs/workbench/workbench.desktop.main'
- ],
- function (_, configuration) {
-
- // Mark start of workbench
- performance.mark('code/didLoadWorkbenchMain');
-
- // @ts-ignore
- return require('vs/workbench/electron-sandbox/desktop.main').main(configuration);
- },
- {
- configureDeveloperSettings: function (windowConfig) {
- return {
- // disable automated devtools opening on error when running extension tests
- // as this can lead to nondeterministic test execution (devtools steals focus)
- forceDisableShowDevtoolsOnError: typeof windowConfig.extensionTestsPath === 'string',
- // enable devtools keybindings in extension development window
- forceEnableDeveloperKeybindings: Array.isArray(windowConfig.extensionDevelopmentPath) && windowConfig.extensionDevelopmentPath.length > 0,
- removeDeveloperKeybindingsAfterLoad: true
- };
- },
- canModifyDOM: function (windowConfig) {
- showSplash(windowConfig);
- },
- beforeLoaderConfig: function (loaderConfig) {
- loaderConfig.recordStats = true;
- },
- beforeRequire: function () {
- performance.mark('code/willLoadWorkbenchMain');
-
- // It looks like browsers only lazily enable
- // the <canvas> element when needed. Since we
- // leverage canvas elements in our code in many
- // locations, we try to help the browser to
- // initialize canvas when it is idle, right
- // before we wait for the scripts to be loaded.
- // @ts-ignore
- window.requestIdleCallback(() => {
- const canvas = document.createElement('canvas');
- const context = canvas.getContext('2d');
- context.clearRect(0, 0, canvas.width, canvas.height);
- canvas.remove();
- }, { timeout: 50 });
- }
- }
- );
-
- //#region Helpers
-
- /**
- * @typedef {import('../../../platform/window/common/window').INativeWindowConfiguration} INativeWindowConfiguration
- * @typedef {import('../../../platform/environment/common/argv').NativeParsedArgs} NativeParsedArgs
- *
- * @returns {{
- * load: (
- * modules: string[],
- * resultCallback: (result, configuration: INativeWindowConfiguration & NativeParsedArgs) => unknown,
- * options?: {
- * configureDeveloperSettings?: (config: INativeWindowConfiguration & NativeParsedArgs) => {
- * forceDisableShowDevtoolsOnError?: boolean,
- * forceEnableDeveloperKeybindings?: boolean,
- * disallowReloadKeybinding?: boolean,
- * removeDeveloperKeybindingsAfterLoad?: boolean
- * },
- * canModifyDOM?: (config: INativeWindowConfiguration & NativeParsedArgs) => void,
- * beforeLoaderConfig?: (loaderConfig: object) => void,
- * beforeRequire?: () => void
- * }
- * ) => Promise<unknown>
- * }}
- */
- function bootstrapWindowLib() {
- // @ts-ignore (defined in bootstrap-window.js)
- return window.MonacoBootstrapWindow;
- }
-
- /**
- * @param {INativeWindowConfiguration & NativeParsedArgs} configuration
- */
- function showSplash(configuration) {
- performance.mark('code/willShowPartsSplash');
-
- let data = configuration.partsSplash;
-
- if (data) {
- // high contrast mode has been turned by the OS -> ignore stored colors and layouts
- if (configuration.autoDetectHighContrast && configuration.colorScheme.highContrast) {
- if ((configuration.colorScheme.dark && data.baseTheme !== 'hc-black') || (!configuration.colorScheme.dark && data.baseTheme !== 'hc-light')) {
- data = undefined;
- }
- } else if (configuration.autoDetectColorScheme) {
- // OS color scheme is tracked and has changed
- if ((configuration.colorScheme.dark && data.baseTheme !== 'vs-dark') || (!configuration.colorScheme.dark && data.baseTheme !== 'vs')) {
- data = undefined;
- }
- }
- }
-
- // developing an extension -> ignore stored layouts
- if (data && configuration.extensionDevelopmentPath) {
- data.layoutInfo = undefined;
- }
-
- // minimal color configuration (works with or without persisted data)
- let baseTheme, shellBackground, shellForeground;
- if (data) {
- baseTheme = data.baseTheme;
- shellBackground = data.colorInfo.editorBackground;
- shellForeground = data.colorInfo.foreground;
- } else if (configuration.autoDetectHighContrast && configuration.colorScheme.highContrast) {
- if (configuration.colorScheme.dark) {
- baseTheme = 'hc-black';
- shellBackground = '#000000';
- shellForeground = '#FFFFFF';
- } else {
- baseTheme = 'hc-light';
- shellBackground = '#FFFFFF';
- shellForeground = '#000000';
- }
- } else if (configuration.autoDetectColorScheme) {
- if (configuration.colorScheme.dark) {
- baseTheme = 'vs-dark';
- shellBackground = '#1E1E1E';
- shellForeground = '#CCCCCC';
- } else {
- baseTheme = 'vs';
- shellBackground = '#FFFFFF';
- shellForeground = '#000000';
- }
- }
-
- const style = document.createElement('style');
- style.className = 'initialShellColors';
- document.head.appendChild(style);
- style.textContent = `body { background-color: ${shellBackground}; color: ${shellForeground}; margin: 0; padding: 0; }`;
-
- // restore parts if possible (we might not always store layout info)
- if (data?.layoutInfo) {
- const { layoutInfo, colorInfo } = data;
-
- const splash = document.createElement('div');
- splash.id = 'monaco-parts-splash';
- splash.className = baseTheme;
-
- if (layoutInfo.windowBorder) {
- splash.style.position = 'relative';
- splash.style.height = 'calc(100vh - 2px)';
- splash.style.width = 'calc(100vw - 2px)';
- splash.style.border = '1px solid var(--window-border-color)';
- splash.style.setProperty('--window-border-color', colorInfo.windowBorder);
-
- if (layoutInfo.windowBorderRadius) {
- splash.style.borderRadius = layoutInfo.windowBorderRadius;
- }
- }
-
- // ensure there is enough space
- layoutInfo.sideBarWidth = Math.min(layoutInfo.sideBarWidth, window.innerWidth - (layoutInfo.activityBarWidth + layoutInfo.editorPartMinWidth));
-
- // part: title
- const titleDiv = document.createElement('div');
- titleDiv.setAttribute('style', `position: absolute; width: 100%; left: 0; top: 0; height: ${layoutInfo.titleBarHeight}px; background-color: ${colorInfo.titleBarBackground}; -webkit-app-region: drag;`);
- splash.appendChild(titleDiv);
-
- // part: activity bar
- const activityDiv = document.createElement('div');
- activityDiv.setAttribute('style', `position: absolute; height: calc(100% - ${layoutInfo.titleBarHeight}px); top: ${layoutInfo.titleBarHeight}px; ${layoutInfo.sideBarSide}: 0; width: ${layoutInfo.activityBarWidth}px; background-color: ${colorInfo.activityBarBackground};`);
- splash.appendChild(activityDiv);
-
- // part: side bar (only when opening workspace/folder)
- // folder or workspace -> status bar color, sidebar
- if (configuration.workspace) {
- const sideDiv = document.createElement('div');
- sideDiv.setAttribute('style', `position: absolute; height: calc(100% - ${layoutInfo.titleBarHeight}px); top: ${layoutInfo.titleBarHeight}px; ${layoutInfo.sideBarSide}: ${layoutInfo.activityBarWidth}px; width: ${layoutInfo.sideBarWidth}px; background-color: ${colorInfo.sideBarBackground};`);
- splash.appendChild(sideDiv);
- }
-
- // part: statusbar
- const statusDiv = document.createElement('div');
- statusDiv.setAttribute('style', `position: absolute; width: 100%; bottom: 0; left: 0; height: ${layoutInfo.statusBarHeight}px; background-color: ${configuration.workspace ? colorInfo.statusBarBackground : colorInfo.statusBarNoFolderBackground};`);
- splash.appendChild(statusDiv);
-
- document.body.appendChild(splash);
- }
-
- performance.mark('code/didShowPartsSplash');
- }
-
- //#endregion
-}());
diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts
index 7b912a581c3..14346728268 100644
--- a/src/vs/code/electron-main/app.ts
+++ b/src/vs/code/electron-main/app.ts
@@ -102,6 +102,8 @@ import { CredentialsNativeMainService } from 'vs/platform/credentials/electron-m
import { IPolicyService } from 'vs/platform/policy/common/policy';
import { PolicyChannel } from 'vs/platform/policy/common/policyIpc';
import { IUserDataProfilesMainService } from 'vs/platform/userDataProfile/electron-main/userDataProfile';
+import { IDefaultExtensionsProfileInitService } from 'vs/platform/extensionManagement/common/extensionManagement';
+import { DefaultExtensionsProfileInitHandler } from 'vs/platform/extensionManagement/electron-main/defaultExtensionsProfileInit';
/**
* The main VS Code application. There will only ever be one instance,
@@ -525,8 +527,8 @@ export class CodeApplication extends Disposable {
// Services
const appInstantiationService = await this.initServices(machineId, sharedProcess, sharedProcessReady);
- // Setup Auth Handler
- this._register(appInstantiationService.createInstance(ProxyAuthHandler));
+ // Setup Handlers
+ this.setUpHandlers(appInstantiationService);
// Init Channels
appInstantiationService.invokeFunction(accessor => this.initChannels(accessor, mainProcessElectronServer, sharedProcessClient));
@@ -543,6 +545,14 @@ export class CodeApplication extends Disposable {
}
}
+ private setUpHandlers(instantiationService: IInstantiationService): void {
+ // Auth Handler
+ this._register(instantiationService.createInstance(ProxyAuthHandler));
+
+ // Default Extensions Profile Init Handler
+ this._register(instantiationService.createInstance(DefaultExtensionsProfileInitHandler));
+ }
+
private async resolveMachineId(): Promise<string> {
// We cache the machineId for faster lookups on startup
@@ -679,6 +689,9 @@ export class CodeApplication extends Disposable {
services.set(ITelemetryService, NullTelemetryService);
}
+ // Default Extensions Profile Init
+ services.set(IDefaultExtensionsProfileInitService, ProxyChannel.toService(getDelayedChannel(sharedProcessReady.then(client => client.getChannel('IDefaultExtensionsProfileInitService')))));
+
// Init services that require it
await backupMainService.initialize();
diff --git a/src/vs/code/electron-sandbox/issue/issueReporterMain.ts b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts
index 4037fbb3441..046cb3929ff 100644
--- a/src/vs/code/electron-sandbox/issue/issueReporterMain.ts
+++ b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts
@@ -140,6 +140,7 @@ export class IssueReporter extends Disposable {
this.handleExtensionData(configuration.data.enabledExtensions);
this.updateExperimentsInfo(configuration.data.experiments);
this.updateRestrictedMode(configuration.data.restrictedMode);
+ this.updateUnsupportedMode(configuration.data.isUnsupported);
}
render(): void {
@@ -1154,6 +1155,10 @@ export class IssueReporter extends Disposable {
this.issueReporterModel.update({ restrictedMode });
}
+ private updateUnsupportedMode(isUnsupported: boolean) {
+ this.issueReporterModel.update({ isUnsupported });
+ }
+
private updateExperimentsInfo(experimentInfo: string | undefined) {
this.issueReporterModel.update({ experimentInfo });
const target = document.querySelector<HTMLElement>('.block-experiments .block-info');
diff --git a/src/vs/code/electron-sandbox/issue/issueReporterModel.ts b/src/vs/code/electron-sandbox/issue/issueReporterModel.ts
index 37a1a40beb0..b0cc736e46e 100644
--- a/src/vs/code/electron-sandbox/issue/issueReporterModel.ts
+++ b/src/vs/code/electron-sandbox/issue/issueReporterModel.ts
@@ -33,6 +33,7 @@ export interface IssueReporterData {
filterResultCount?: number;
experimentInfo?: string;
restrictedMode?: boolean;
+ isUnsupported?: boolean;
}
export class IssueReporterModel {
@@ -61,14 +62,21 @@ export class IssueReporterModel {
}
serialize(): string {
+ const modes = [];
+ if (this._data.restrictedMode) {
+ modes.push('Restricted');
+ }
+ if (this._data.isUnsupported) {
+ modes.push('Unsupported');
+ }
return `
-Issue Type: <b>${this.getIssueTypeTitle()}</b>
+Type: <b>${this.getIssueTypeTitle()}</b>
${this._data.issueDescription}
${this.getExtensionVersion()}
VS Code version: ${this._data.versionInfo && this._data.versionInfo.vscodeVersion}
OS version: ${this._data.versionInfo && this._data.versionInfo.os}
-Restricted Mode: ${this._data.restrictedMode ? 'Yes' : 'No'}
+Modes:${modes.length ? ' ' + modes.join(', ') : ''}
${this.getRemoteOSes()}
${this.getInfos()}
<!-- generated by issue reporter -->`;
diff --git a/src/vs/code/electron-sandbox/workbench/workbench.js b/src/vs/code/electron-sandbox/workbench/workbench.js
index e271fdf1bf4..0af36c6e1ac 100644
--- a/src/vs/code/electron-sandbox/workbench/workbench.js
+++ b/src/vs/code/electron-sandbox/workbench/workbench.js
@@ -16,10 +16,10 @@
// Load workbench main JS, CSS and NLS all in parallel. This is an
// optimization to prevent a waterfall of loading to happen, because
- // we know for a fact that workbench.desktop.sandbox.main will depend on
+ // we know for a fact that workbench.desktop.main will depend on
// the related CSS and NLS counterparts.
bootstrapWindow.load([
- 'vs/workbench/workbench.desktop.sandbox.main',
+ 'vs/workbench/workbench.desktop.main',
'vs/nls!vs/workbench/workbench.desktop.main',
'vs/css!vs/workbench/workbench.desktop.main'
],
@@ -61,7 +61,7 @@
window.requestIdleCallback(() => {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
- context.clearRect(0, 0, canvas.width, canvas.height);
+ context?.clearRect(0, 0, canvas.width, canvas.height);
canvas.remove();
}, { timeout: 50 });
}
diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts
index bc4ca25f492..79c5b02e686 100644
--- a/src/vs/code/node/cliProcessMain.ts
+++ b/src/vs/code/node/cliProcessMain.ts
@@ -53,7 +53,7 @@ import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProp
import { ITelemetryService, machineIdKey } from 'vs/platform/telemetry/common/telemetry';
import { ITelemetryServiceConfig, TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
import { supportsTelemetry, NullTelemetryService, getPiiPathsFromEnvironment } from 'vs/platform/telemetry/common/telemetryUtils';
-import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender';
+import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender';
import { buildTelemetryMessage } from 'vs/platform/telemetry/node/telemetry';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService';
@@ -104,7 +104,7 @@ class CliMain extends Disposable {
});
}
- private async initServices(): Promise<[IInstantiationService, AppInsightsAppender[]]> {
+ private async initServices(): Promise<[IInstantiationService, OneDataSystemAppender[]]> {
const services = new ServiceCollection();
// Product
@@ -186,10 +186,10 @@ class CliMain extends Disposable {
services.set(ILanguagePackService, new SyncDescriptor(NativeLanguagePackService));
// Telemetry
- const appenders: AppInsightsAppender[] = [];
+ const appenders: OneDataSystemAppender[] = [];
if (supportsTelemetry(productService, environmentService)) {
- if (productService.aiConfig && productService.aiConfig.asimovKey) {
- appenders.push(new AppInsightsAppender('monacoworkbench', null, productService.aiConfig.asimovKey));
+ if (productService.aiConfig && productService.aiConfig.ariaKey) {
+ appenders.push(new OneDataSystemAppender(configurationService, 'monacoworkbench', null, productService.aiConfig.ariaKey));
}
const { installSourcePath } = environmentService;
diff --git a/src/vs/code/test/electron-sandbox/issue/testReporterModel.test.ts b/src/vs/code/test/electron-sandbox/issue/testReporterModel.test.ts
index 87bca75fedf..e2280b2338e 100644
--- a/src/vs/code/test/electron-sandbox/issue/testReporterModel.test.ts
+++ b/src/vs/code/test/electron-sandbox/issue/testReporterModel.test.ts
@@ -27,13 +27,13 @@ suite('IssueReporter', () => {
const issueReporterModel = new IssueReporterModel({});
assert.strictEqual(issueReporterModel.serialize(),
`
-Issue Type: <b>Bug</b>
+Type: <b>Bug</b>
undefined
VS Code version: undefined
OS version: undefined
-Restricted Mode: No
+Modes:
Extensions: none
<!-- generated by issue reporter -->`);
@@ -58,13 +58,13 @@ Extensions: none
});
assert.strictEqual(issueReporterModel.serialize(),
`
-Issue Type: <b>Bug</b>
+Type: <b>Bug</b>
undefined
VS Code version: undefined
OS version: undefined
-Restricted Mode: No
+Modes:
<details>
<summary>System Info</summary>
@@ -102,13 +102,13 @@ Restricted Mode: No
});
assert.strictEqual(issueReporterModel.serialize(),
`
-Issue Type: <b>Bug</b>
+Type: <b>Bug</b>
undefined
VS Code version: undefined
OS version: undefined
-Restricted Mode: No
+Modes:
<details>
<summary>System Info</summary>
@@ -157,13 +157,13 @@ vsins829:30139715
});
assert.strictEqual(issueReporterModel.serialize(),
`
-Issue Type: <b>Bug</b>
+Type: <b>Bug</b>
undefined
VS Code version: undefined
OS version: undefined
-Restricted Mode: No
+Modes:
<details>
<summary>System Info</summary>
@@ -214,13 +214,13 @@ Restricted Mode: No
});
assert.strictEqual(issueReporterModel.serialize(),
`
-Issue Type: <b>Bug</b>
+Type: <b>Bug</b>
undefined
VS Code version: undefined
OS version: undefined
-Restricted Mode: No
+Modes:
Remote OS version: Linux x64 4.18.0
<details>
@@ -263,13 +263,13 @@ Remote OS version: Linux x64 4.18.0
});
assert.strictEqual(issueReporterModel.serialize(),
`
-Issue Type: <b>Bug</b>
+Type: <b>Bug</b>
undefined
VS Code version: undefined
OS version: undefined
-Restricted Mode: No
+Modes:
<details>
<summary>System Info</summary>
@@ -287,6 +287,24 @@ Restricted Mode: No
<!-- generated by issue reporter -->`);
});
+ test('should supply mode if applicable', () => {
+ const issueReporterModel = new IssueReporterModel({
+ isUnsupported: true,
+ restrictedMode: true
+ });
+ assert.strictEqual(issueReporterModel.serialize(),
+ `
+Type: <b>Bug</b>
+
+undefined
+
+VS Code version: undefined
+OS version: undefined
+Modes: Restricted, Unsupported
+
+Extensions: none
+<!-- generated by issue reporter -->`);
+ });
test('should normalize GitHub urls', () => {
[
'https://github.com/repo',
diff --git a/src/vs/editor/browser/config/migrateOptions.ts b/src/vs/editor/browser/config/migrateOptions.ts
index 49d6a503a84..42f1bc526a8 100644
--- a/src/vs/editor/browser/config/migrateOptions.ts
+++ b/src/vs/editor/browser/config/migrateOptions.ts
@@ -3,7 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { forEach } from 'vs/base/common/collections';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
export interface ISettingsReader {
@@ -152,14 +151,14 @@ const suggestFilteredTypesMapping: Record<string, string> = {
registerEditorSettingMigration('suggest.filteredTypes', (value, read, write) => {
if (value && typeof value === 'object') {
- forEach(suggestFilteredTypesMapping, entry => {
- const v = value[entry.key];
+ for (const entry of Object.entries(suggestFilteredTypesMapping)) {
+ const v = value[entry[0]];
if (v === false) {
- if (typeof read(`suggest.${entry.value}`) === 'undefined') {
- write(`suggest.${entry.value}`, false);
+ if (typeof read(`suggest.${entry[1]}`) === 'undefined') {
+ write(`suggest.${entry[1]}`, false);
}
}
- });
+ }
write('suggest.filteredTypes', undefined);
}
});
diff --git a/src/vs/editor/browser/services/abstractCodeEditorService.ts b/src/vs/editor/browser/services/abstractCodeEditorService.ts
index 761c4eee60f..aaa31ee4ce4 100644
--- a/src/vs/editor/browser/services/abstractCodeEditorService.ts
+++ b/src/vs/editor/browser/services/abstractCodeEditorService.ts
@@ -5,11 +5,12 @@
import * as dom from 'vs/base/browser/dom';
import { Emitter, Event } from 'vs/base/common/event';
-import { IDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle';
+import { IDisposable, DisposableStore, Disposable, toDisposable } from 'vs/base/common/lifecycle';
+import { LinkedList } from 'vs/base/common/linkedList';
import * as strings from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser';
-import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
+import { ICodeEditorOpenHandler, ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { IContentDecorationRenderOptions, IDecorationRenderOptions, IThemeDecorationRenderOptions, isThemeColor } from 'vs/editor/common/editorCommon';
import { IModelDecorationOptions, IModelDecorationOverviewRulerOptions, InjectedTextOptions, ITextModel, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model';
import { IResourceEditorInput } from 'vs/platform/editor/common/editor';
@@ -42,6 +43,7 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC
protected _globalStyleSheet: GlobalStyleSheet | null;
private readonly _decorationOptionProviders = new Map<string, IModelDecorationOptionsProvider>();
private readonly _editorStyleSheets = new Map<string, RefCountedStyleSheet>();
+ private readonly _codeEditorOpenHandlers = new LinkedList<ICodeEditorOpenHandler>();
constructor(
@IThemeService private readonly _themeService: IThemeService,
@@ -247,7 +249,21 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC
}
abstract getActiveCodeEditor(): ICodeEditor | null;
- abstract openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null>;
+
+ async openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null> {
+ for (const handler of this._codeEditorOpenHandlers) {
+ const candidate = await handler(input, source, sideBySide);
+ if (candidate !== null) {
+ return candidate;
+ }
+ }
+ return null;
+ }
+
+ registerCodeEditorOpenHandler(handler: ICodeEditorOpenHandler): IDisposable {
+ const rm = this._codeEditorOpenHandlers.unshift(handler);
+ return toDisposable(rm);
+ }
}
export class ModelTransientSettingWatcher {
diff --git a/src/vs/editor/browser/services/bulkEditService.ts b/src/vs/editor/browser/services/bulkEditService.ts
index 1fa168aedb1..1ec6fde3bfb 100644
--- a/src/vs/editor/browser/services/bulkEditService.ts
+++ b/src/vs/editor/browser/services/bulkEditService.ts
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
-import { TextEdit, WorkspaceEdit, WorkspaceEditMetadata, WorkspaceFileEdit, WorkspaceFileEditOptions, WorkspaceTextEdit } from 'vs/editor/common/languages';
+import { TextEdit, WorkspaceEdit, WorkspaceEditMetadata, IWorkspaceFileEdit, WorkspaceFileEditOptions, IWorkspaceTextEdit } from 'vs/editor/common/languages';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IProgress, IProgressStep } from 'vs/platform/progress/common/progress';
import { IDisposable } from 'vs/base/common/lifecycle';
@@ -15,49 +15,77 @@ import { CancellationToken } from 'vs/base/common/cancellation';
export const IBulkEditService = createDecorator<IBulkEditService>('IWorkspaceEditService');
-function isWorkspaceFileEdit(thing: any): thing is WorkspaceFileEdit {
- return isObject(thing) && (Boolean((<WorkspaceFileEdit>thing).newUri) || Boolean((<WorkspaceFileEdit>thing).oldUri));
-}
-
-function isWorkspaceTextEdit(thing: any): thing is WorkspaceTextEdit {
- return isObject(thing) && URI.isUri((<WorkspaceTextEdit>thing).resource) && isObject((<WorkspaceTextEdit>thing).edit);
-}
-
export class ResourceEdit {
protected constructor(readonly metadata?: WorkspaceEditMetadata) { }
static convert(edit: WorkspaceEdit): ResourceEdit[] {
-
return edit.edits.map(edit => {
- if (isWorkspaceTextEdit(edit)) {
- return new ResourceTextEdit(edit.resource, edit.edit, edit.modelVersionId, edit.metadata);
+ if (ResourceTextEdit.is(edit)) {
+ return ResourceTextEdit.lift(edit);
}
- if (isWorkspaceFileEdit(edit)) {
- return new ResourceFileEdit(edit.oldUri, edit.newUri, edit.options, edit.metadata);
+
+ if (ResourceFileEdit.is(edit)) {
+ return ResourceFileEdit.lift(edit);
}
throw new Error('Unsupported edit');
});
}
}
-export class ResourceTextEdit extends ResourceEdit {
+export class ResourceTextEdit extends ResourceEdit implements IWorkspaceTextEdit {
+
+ static is(candidate: any): candidate is IWorkspaceTextEdit {
+ if (candidate instanceof ResourceTextEdit) {
+ return true;
+ }
+ return isObject(candidate)
+ && URI.isUri((<IWorkspaceTextEdit>candidate).resource)
+ && isObject((<IWorkspaceTextEdit>candidate).textEdit);
+ }
+
+ static lift(edit: IWorkspaceTextEdit): ResourceTextEdit {
+ if (edit instanceof ResourceTextEdit) {
+ return edit;
+ } else {
+ return new ResourceTextEdit(edit.resource, edit.textEdit, edit.versionId, edit.metadata);
+ }
+ }
+
constructor(
readonly resource: URI,
readonly textEdit: TextEdit & { insertAsSnippet?: boolean },
- readonly versionId?: number,
+ readonly versionId: number | undefined = undefined,
metadata?: WorkspaceEditMetadata,
) {
super(metadata);
}
}
-export class ResourceFileEdit extends ResourceEdit {
+export class ResourceFileEdit extends ResourceEdit implements IWorkspaceFileEdit {
+
+ static is(candidate: any): candidate is IWorkspaceFileEdit {
+ if (candidate instanceof ResourceFileEdit) {
+ return true;
+ } else {
+ return isObject(candidate)
+ && (Boolean((<IWorkspaceFileEdit>candidate).newResource) || Boolean((<IWorkspaceFileEdit>candidate).oldResource));
+ }
+ }
+
+ static lift(edit: IWorkspaceFileEdit): ResourceFileEdit {
+ if (edit instanceof ResourceFileEdit) {
+ return edit;
+ } else {
+ return new ResourceFileEdit(edit.oldResource, edit.newResource, edit.options, edit.metadata);
+ }
+ }
+
constructor(
readonly oldResource: URI | undefined,
readonly newResource: URI | undefined,
- readonly options?: WorkspaceFileEditOptions,
+ readonly options: WorkspaceFileEditOptions = {},
metadata?: WorkspaceEditMetadata
) {
super(metadata);
diff --git a/src/vs/editor/browser/services/codeEditorService.ts b/src/vs/editor/browser/services/codeEditorService.ts
index b56596939a8..40d7947efcd 100644
--- a/src/vs/editor/browser/services/codeEditorService.ts
+++ b/src/vs/editor/browser/services/codeEditorService.ts
@@ -10,6 +10,7 @@ import { IModelDecorationOptions, ITextModel } from 'vs/editor/common/model';
import { ITextResourceEditorInput } from 'vs/platform/editor/common/editor';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { URI } from 'vs/base/common/uri';
+import { IDisposable } from 'vs/base/common/lifecycle';
export const ICodeEditorService = createDecorator<ICodeEditorService>('codeEditorService');
@@ -53,4 +54,9 @@ export interface ICodeEditorService {
getActiveCodeEditor(): ICodeEditor | null;
openCodeEditor(input: ITextResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null>;
+ registerCodeEditorOpenHandler(handler: ICodeEditorOpenHandler): IDisposable;
+}
+
+export interface ICodeEditorOpenHandler {
+ (input: ITextResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null>;
}
diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts
index 1ba384d520f..f4e8c0d10db 100644
--- a/src/vs/editor/browser/widget/codeEditorWidget.ts
+++ b/src/vs/editor/browser/widget/codeEditorWidget.ts
@@ -564,27 +564,43 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
return this._modelData.viewModel.viewLayout.getWhitespaces();
}
- private static _getVerticalOffsetForPosition(modelData: ModelData, modelLineNumber: number, modelColumn: number): number {
+ private static _getVerticalOffsetAfterPosition(modelData: ModelData, modelLineNumber: number, modelColumn: number, includeViewZones: boolean): number {
const modelPosition = modelData.model.validatePosition({
lineNumber: modelLineNumber,
column: modelColumn
});
const viewPosition = modelData.viewModel.coordinatesConverter.convertModelPositionToViewPosition(modelPosition);
- return modelData.viewModel.viewLayout.getVerticalOffsetForLineNumber(viewPosition.lineNumber);
+ return modelData.viewModel.viewLayout.getVerticalOffsetAfterLineNumber(viewPosition.lineNumber, includeViewZones);
}
- public getTopForLineNumber(lineNumber: number): number {
+ public getTopForLineNumber(lineNumber: number, includeViewZones: boolean = false): number {
if (!this._modelData) {
return -1;
}
- return CodeEditorWidget._getVerticalOffsetForPosition(this._modelData, lineNumber, 1);
+ return CodeEditorWidget._getVerticalOffsetForPosition(this._modelData, lineNumber, 1, includeViewZones);
}
public getTopForPosition(lineNumber: number, column: number): number {
if (!this._modelData) {
return -1;
}
- return CodeEditorWidget._getVerticalOffsetForPosition(this._modelData, lineNumber, column);
+ return CodeEditorWidget._getVerticalOffsetForPosition(this._modelData, lineNumber, column, false);
+ }
+
+ private static _getVerticalOffsetForPosition(modelData: ModelData, modelLineNumber: number, modelColumn: number, includeViewZones: boolean = false): number {
+ const modelPosition = modelData.model.validatePosition({
+ lineNumber: modelLineNumber,
+ column: modelColumn
+ });
+ const viewPosition = modelData.viewModel.coordinatesConverter.convertModelPositionToViewPosition(modelPosition);
+ return modelData.viewModel.viewLayout.getVerticalOffsetForLineNumber(viewPosition.lineNumber, includeViewZones);
+ }
+
+ public getBottomForLineNumber(lineNumber: number, includeViewZones: boolean = false): number {
+ if (!this._modelData) {
+ return -1;
+ }
+ return CodeEditorWidget._getVerticalOffsetAfterPosition(this._modelData, lineNumber, 1, includeViewZones);
}
public setHiddenAreas(ranges: IRange[]): void {
diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts
index bafc557e3e4..7292a7e132b 100644
--- a/src/vs/editor/common/config/editorOptions.ts
+++ b/src/vs/editor/common/config/editorOptions.ts
@@ -563,7 +563,7 @@ export interface IEditorOptions {
* Controls whether the fold actions in the gutter stay always visible or hide unless the mouse is over the gutter.
* Defaults to 'mouseover'.
*/
- showFoldingControls?: 'always' | 'mouseover';
+ showFoldingControls?: 'always' | 'never' | 'mouseover';
/**
* Controls whether clicking on the empty content after a folded line will unfold the line.
* Defaults to false.
@@ -2331,6 +2331,7 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
const rawLineDecorationsWidth = options.get(EditorOption.lineDecorationsWidth);
const folding = options.get(EditorOption.folding);
+ const showFoldingDecoration = options.get(EditorOption.showFoldingControls) !== 'never';
let lineDecorationsWidth: number;
if (typeof rawLineDecorationsWidth === 'string' && /^\d+(\.\d+)?ch$/.test(rawLineDecorationsWidth)) {
@@ -2339,7 +2340,7 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
} else {
lineDecorationsWidth = EditorIntOption.clampedInt(rawLineDecorationsWidth, 0, 0, 1000);
}
- if (folding) {
+ if (folding && showFoldingDecoration) {
lineDecorationsWidth += 16;
}
@@ -2561,12 +2562,12 @@ class EditorInlayHints extends BaseEditorOption<EditorOption.inlayHints, IEditor
'editor.inlayHints.fontSize': {
type: 'number',
default: defaults.fontSize,
- markdownDescription: nls.localize('inlayHints.fontSize', "Controls font size of inlay hints in the editor. As default the `#editor.fontSize#` is used when the configured value is less than `5` or greater than the editor font size.")
+ markdownDescription: nls.localize('inlayHints.fontSize', "Controls font size of inlay hints in the editor. As default the {0} is used when the configured value is less than {1} or greater than the editor font size.", '`#editor.fontSize#`', '`5`')
},
'editor.inlayHints.fontFamily': {
type: 'string',
default: defaults.fontFamily,
- markdownDescription: nls.localize('inlayHints.fontFamily', "Controls font family of inlay hints in the editor. When set to empty, the `#editor.fontFamily#` is used.")
+ markdownDescription: nls.localize('inlayHints.fontFamily', "Controls font family of inlay hints in the editor. When set to empty, the {0} is used.", '`#editor.fontFamily#`')
},
'editor.inlayHints.padding': {
type: 'boolean',
@@ -3642,7 +3643,7 @@ class BracketPairColorization extends BaseEditorOption<EditorOption.bracketPairC
'editor.bracketPairColorization.enabled': {
type: 'boolean',
default: defaults.enabled,
- markdownDescription: nls.localize('bracketPairColorization.enabled', "Controls whether bracket pair colorization is enabled or not. Use `#workbench.colorCustomizations#` to override the bracket highlight colors.")
+ markdownDescription: nls.localize('bracketPairColorization.enabled', "Controls whether bracket pair colorization is enabled or not. Use {0} to override the bracket highlight colors.", '`#workbench.colorCustomizations#`')
},
'editor.bracketPairColorization.independentColorPoolPerBracketType': {
type: 'boolean',
@@ -4896,7 +4897,7 @@ export const EditorOptions = {
'- `ctrlCmd` refers to a value the setting can take and should not be localized.',
'- `Control` and `Command` refer to the modifier keys Ctrl or Cmd on the keyboard and can be localized.'
]
- }, "The modifier to be used to add multiple cursors with the mouse. The Go to Definition and Open Link mouse gestures will adapt such that they do not conflict with the multicursor modifier. [Read more](https://code.visualstudio.com/docs/editor/codebasics#_multicursor-modifier).")
+ }, "The modifier to be used to add multiple cursors with the mouse. The Go to Definition and Open Link mouse gestures will adapt such that they do not conflict with the [multicursor modifier](https://code.visualstudio.com/docs/editor/codebasics#_multicursor-modifier).")
}
)),
multiCursorPaste: register(new EditorStringEnumOption(
@@ -5039,11 +5040,12 @@ export const EditorOptions = {
)),
showFoldingControls: register(new EditorStringEnumOption(
EditorOption.showFoldingControls, 'showFoldingControls',
- 'mouseover' as 'always' | 'mouseover',
- ['always', 'mouseover'] as const,
+ 'mouseover' as 'always' | 'never' | 'mouseover',
+ ['always', 'never', 'mouseover'] as const,
{
enumDescriptions: [
nls.localize('showFoldingControls.always', "Always show the folding controls."),
+ nls.localize('showFoldingControls.never', "Never show the folding controls and reduce the gutter size."),
nls.localize('showFoldingControls.mouseover', "Only show the folding controls when the mouse is over the gutter."),
],
description: nls.localize('showFoldingControls', "Controls when the folding controls on the gutter are shown.")
@@ -5086,12 +5088,12 @@ export const EditorOptions = {
suggestFontSize: register(new EditorIntOption(
EditorOption.suggestFontSize, 'suggestFontSize',
0, 0, 1000,
- { markdownDescription: nls.localize('suggestFontSize', "Font size for the suggest widget. When set to `0`, the value of `#editor.fontSize#` is used.") }
+ { markdownDescription: nls.localize('suggestFontSize', "Font size for the suggest widget. When set to {0}, the value of {1} is used.", '`0`', '`#editor.fontSize#`') }
)),
suggestLineHeight: register(new EditorIntOption(
EditorOption.suggestLineHeight, 'suggestLineHeight',
0, 0, 1000,
- { markdownDescription: nls.localize('suggestLineHeight', "Line height for the suggest widget. When set to `0`, the value of `#editor.lineHeight#` is used. The minimum value is 8.") }
+ { markdownDescription: nls.localize('suggestLineHeight', "Line height for the suggest widget. When set to {0}, the value of {1} is used. The minimum value is 8.", '`0`', '`#editor.lineHeight#`') }
)),
suggestOnTriggerCharacters: register(new EditorBooleanOption(
EditorOption.suggestOnTriggerCharacters, 'suggestOnTriggerCharacters', true,
diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts
index 0d97ad7329e..10baf2667c9 100644
--- a/src/vs/editor/common/languages.ts
+++ b/src/vs/editor/common/languages.ts
@@ -1412,22 +1412,22 @@ export interface WorkspaceFileEditOptions {
maxSize?: number;
}
-export interface WorkspaceFileEdit {
- oldUri?: URI;
- newUri?: URI;
+export interface IWorkspaceFileEdit {
+ oldResource?: URI;
+ newResource?: URI;
options?: WorkspaceFileEditOptions;
metadata?: WorkspaceEditMetadata;
}
-export interface WorkspaceTextEdit {
+export interface IWorkspaceTextEdit {
resource: URI;
- edit: TextEdit;
- modelVersionId?: number;
+ textEdit: TextEdit & { insertAsSnippet?: boolean };
+ versionId: number | undefined;
metadata?: WorkspaceEditMetadata;
}
export interface WorkspaceEdit {
- edits: Array<WorkspaceTextEdit | WorkspaceFileEdit>;
+ edits: Array<IWorkspaceTextEdit | IWorkspaceFileEdit>;
}
export interface Rejection {
diff --git a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/brackets.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/brackets.ts
index 2673aeacb66..db90a64334e 100644
--- a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/brackets.ts
+++ b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/brackets.ts
@@ -98,10 +98,16 @@ export class BracketTokens {
}
function prepareBracketForRegExp(str: string): string {
- const escaped = escapeRegExpCharacters(str);
- // This bracket pair uses letters like e.g. "begin" - "end" (see https://github.com/microsoft/vscode/issues/132162)
- const needsWordBoundaries = (/^[\w ]+$/.test(str));
- return (needsWordBoundaries ? `\\b${escaped}\\b` : escaped);
+ let escaped = escapeRegExpCharacters(str);
+ // These bracket pair delimiters start or end with letters
+ // see https://github.com/microsoft/vscode/issues/132162 https://github.com/microsoft/vscode/issues/150440
+ if (/^[\w ]+/.test(str)) {
+ escaped = `\\b${escaped}`;
+ }
+ if (/[\w ]+$/.test(str)) {
+ escaped = `${escaped}\\b`;
+ }
+ return escaped;
}
export class LanguageAgnosticBracketTokens {
diff --git a/src/vs/editor/common/viewLayout/linesLayout.ts b/src/vs/editor/common/viewLayout/linesLayout.ts
index 01e2d26cf5a..7bb55aeef6e 100644
--- a/src/vs/editor/common/viewLayout/linesLayout.ts
+++ b/src/vs/editor/common/viewLayout/linesLayout.ts
@@ -482,7 +482,7 @@ export class LinesLayout {
* @param lineNumber The line number
* @return The sum of heights for all objects above `lineNumber`.
*/
- public getVerticalOffsetForLineNumber(lineNumber: number): number {
+ public getVerticalOffsetForLineNumber(lineNumber: number, includeViewZones = false): number {
this._checkPendingChanges();
lineNumber = lineNumber | 0;
@@ -493,12 +493,26 @@ export class LinesLayout {
previousLinesHeight = 0;
}
- const previousWhitespacesHeight = this.getWhitespaceAccumulatedHeightBeforeLineNumber(lineNumber);
+ const previousWhitespacesHeight = this.getWhitespaceAccumulatedHeightBeforeLineNumber(lineNumber - (includeViewZones ? 1 : 0));
return previousLinesHeight + previousWhitespacesHeight + this._paddingTop;
}
/**
+ * Get the vertical offset (the sum of heights for all objects above) a certain line number.
+ *
+ * @param lineNumber The line number
+ * @return The sum of heights for all objects above `lineNumber`.
+ */
+ public getVerticalOffsetAfterLineNumber(lineNumber: number, includeViewZones = false): number {
+ this._checkPendingChanges();
+ lineNumber = lineNumber | 0;
+ const previousLinesHeight = this._lineHeight * lineNumber;
+ const previousWhitespacesHeight = this.getWhitespaceAccumulatedHeightBeforeLineNumber(lineNumber + (includeViewZones ? 1 : 0));
+ return previousLinesHeight + previousWhitespacesHeight + this._paddingTop;
+ }
+
+ /**
* Returns if there is any whitespace in the document.
*/
public hasWhitespace(): boolean {
diff --git a/src/vs/editor/common/viewLayout/viewLayout.ts b/src/vs/editor/common/viewLayout/viewLayout.ts
index fa5013cac62..0acbe29204f 100644
--- a/src/vs/editor/common/viewLayout/viewLayout.ts
+++ b/src/vs/editor/common/viewLayout/viewLayout.ts
@@ -362,8 +362,11 @@ export class ViewLayout extends Disposable implements IViewLayout {
}
return hadAChange;
}
- public getVerticalOffsetForLineNumber(lineNumber: number): number {
- return this._linesLayout.getVerticalOffsetForLineNumber(lineNumber);
+ public getVerticalOffsetForLineNumber(lineNumber: number, includeViewZones: boolean = false): number {
+ return this._linesLayout.getVerticalOffsetForLineNumber(lineNumber, includeViewZones);
+ }
+ public getVerticalOffsetAfterLineNumber(lineNumber: number, includeViewZones: boolean = false): number {
+ return this._linesLayout.getVerticalOffsetAfterLineNumber(lineNumber, includeViewZones);
}
public isAfterLines(verticalOffset: number): boolean {
return this._linesLayout.isAfterLines(verticalOffset);
diff --git a/src/vs/editor/contrib/codeAction/test/browser/codeAction.test.ts b/src/vs/editor/contrib/codeAction/test/browser/codeAction.test.ts
index d1be6b9d645..93005d11387 100644
--- a/src/vs/editor/contrib/codeAction/test/browser/codeAction.test.ts
+++ b/src/vs/editor/contrib/codeAction/test/browser/codeAction.test.ts
@@ -73,7 +73,7 @@ suite('CodeAction', () => {
bcd: {
diagnostics: <IMarkerData[]>[],
edit: new class implements languages.WorkspaceEdit {
- edits!: languages.WorkspaceTextEdit[];
+ edits!: languages.IWorkspaceTextEdit[];
},
title: 'abc'
}
diff --git a/src/vs/editor/contrib/folding/browser/folding.ts b/src/vs/editor/contrib/folding/browser/folding.ts
index 391249b2436..64339450d95 100644
--- a/src/vs/editor/contrib/folding/browser/folding.ts
+++ b/src/vs/editor/contrib/folding/browser/folding.ts
@@ -26,15 +26,14 @@ import { ILanguageConfigurationService } from 'vs/editor/common/languages/langua
import { CollapseMemento, FoldingModel, getNextFoldLine, getParentFoldLine as getParentFoldLine, getPreviousFoldLine, setCollapseStateAtLevel, setCollapseStateForMatchingLines, setCollapseStateForRest, setCollapseStateForType, setCollapseStateLevelsDown, setCollapseStateLevelsUp, setCollapseStateUp, toggleCollapseState } from 'vs/editor/contrib/folding/browser/foldingModel';
import { HiddenRangeModel } from 'vs/editor/contrib/folding/browser/hiddenRangeModel';
import { IndentRangeProvider } from 'vs/editor/contrib/folding/browser/indentRangeProvider';
-import { ID_INIT_PROVIDER, InitializingRangeProvider } from 'vs/editor/contrib/folding/browser/intializingRangeProvider';
import * as nls from 'vs/nls';
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { editorSelectionBackground, iconForeground, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry';
import { registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService';
-import { foldingCollapsedIcon, FoldingDecorationProvider, foldingExpandedIcon } from './foldingDecorations';
-import { FoldingRegion, FoldingRegions } from './foldingRanges';
-import { ID_SYNTAX_PROVIDER, SyntaxRangeProvider } from './syntaxRangeProvider';
+import { foldingCollapsedIcon, FoldingDecorationProvider, foldingExpandedIcon, foldingManualIcon } from './foldingDecorations';
+import { FoldingRegion, FoldingRegions, FoldRange } from './foldingRanges';
+import { SyntaxRangeProvider } from './syntaxRangeProvider';
import { INotificationService } from 'vs/platform/notification/common/notification';
import Severity from 'vs/base/common/severity';
import { IFeatureDebounceInformation, ILanguageFeatureDebounceService } from 'vs/editor/common/services/languageFeatureDebounce';
@@ -84,8 +83,6 @@ export class FoldingController extends Disposable implements IEditorContribution
private rangeProvider: RangeProvider | null;
private foldingRegionPromise: CancelablePromise<FoldingRegions | null> | null;
- private foldingStateMemento: FoldingStateMemento | null;
-
private foldingModelPromise: Promise<FoldingModel | null> | null;
private updateScheduler: Delayer<FoldingModel | null> | null;
private readonly updateDebounceInfo: IFeatureDebounceInformation;
@@ -120,14 +117,13 @@ export class FoldingController extends Disposable implements IEditorContribution
this.hiddenRangeModel = null;
this.rangeProvider = null;
this.foldingRegionPromise = null;
- this.foldingStateMemento = null;
this.foldingModelPromise = null;
this.updateScheduler = null;
this.cursorChangedScheduler = null;
this.mouseDownInfo = null;
this.foldingDecorationProvider = new FoldingDecorationProvider(editor);
- this.foldingDecorationProvider.autoHideFoldingControls = options.get(EditorOption.showFoldingControls) === 'mouseover';
+ this.foldingDecorationProvider.showFoldingControls = options.get(EditorOption.showFoldingControls);
this.foldingDecorationProvider.showFoldingHighlights = options.get(EditorOption.foldingHighlight);
this.foldingEnabled = CONTEXT_FOLDING_ENABLED.bindTo(this.contextKeyService);
this.foldingEnabled.set(this._isEnabled);
@@ -159,7 +155,7 @@ export class FoldingController extends Disposable implements IEditorContribution
}
if (e.hasChanged(EditorOption.showFoldingControls) || e.hasChanged(EditorOption.foldingHighlight)) {
const options = this.editor.getOptions();
- this.foldingDecorationProvider.autoHideFoldingControls = options.get(EditorOption.showFoldingControls) === 'mouseover';
+ this.foldingDecorationProvider.showFoldingControls = options.get(EditorOption.showFoldingControls);
this.foldingDecorationProvider.showFoldingHighlights = options.get(EditorOption.foldingHighlight);
this.triggerFoldingModelChanged();
}
@@ -186,7 +182,7 @@ export class FoldingController extends Disposable implements IEditorContribution
return {};
}
if (this.foldingModel) { // disposed ?
- const collapsedRegions = this.foldingModel.isInitialized ? this.foldingModel.getMemento() : this.hiddenRangeModel!.getMemento();
+ const collapsedRegions = this.foldingModel.getMemento();
const provider = this.rangeProvider ? this.rangeProvider.id : undefined;
return { collapsedRegions, lineCount: model.getLineCount(), provider, foldedImports: this._currentModelHasFoldedImports };
}
@@ -206,29 +202,12 @@ export class FoldingController extends Disposable implements IEditorContribution
}
this._currentModelHasFoldedImports = !!state.foldedImports;
- if (!state.collapsedRegions) {
- return;
- }
-
- if (state.provider === ID_SYNTAX_PROVIDER || state.provider === ID_INIT_PROVIDER) {
- this.foldingStateMemento = state;
- }
-
- const collapsedRegions = state.collapsedRegions;
- // set the hidden ranges right away, before waiting for the folding model.
- if (this.hiddenRangeModel.applyMemento(collapsedRegions)) {
- const foldingModel = this.getFoldingModel();
- if (foldingModel) {
- foldingModel.then(foldingModel => {
- if (foldingModel) {
- this._restoringViewState = true;
- try {
- foldingModel.applyMemento(collapsedRegions);
- } finally {
- this._restoringViewState = false;
- }
- }
- }).then(undefined, onUnexpectedError);
+ if (state.collapsedRegions && state.collapsedRegions.length > 0 && this.foldingModel) {
+ this._restoringViewState = true;
+ try {
+ this.foldingModel.applyMemento(state.collapsedRegions!);
+ } finally {
+ this._restoringViewState = false;
}
}
}
@@ -243,7 +222,7 @@ export class FoldingController extends Disposable implements IEditorContribution
}
this._currentModelHasFoldedImports = false;
- this.foldingModel = new FoldingModel(model, this.foldingDecorationProvider);
+ this.foldingModel = new FoldingModel(model, this.foldingDecorationProvider, this.triggerFoldingModelChanged.bind(this));
this.localToDispose.add(this.foldingModel);
this.hiddenRangeModel = new HiddenRangeModel(this.foldingModel);
@@ -274,7 +253,6 @@ export class FoldingController extends Disposable implements IEditorContribution
this.foldingModelPromise = null;
this.hiddenRangeModel = null;
this.cursorChangedScheduler = null;
- this.foldingStateMemento = null;
if (this.rangeProvider) {
this.rangeProvider.dispose();
}
@@ -297,21 +275,12 @@ export class FoldingController extends Disposable implements IEditorContribution
return this.rangeProvider;
}
this.rangeProvider = new IndentRangeProvider(editorModel, this.languageConfigurationService, this._maxFoldingRegions); // fallback
-
if (this._useFoldingProviders && this.foldingModel) {
const foldingProviders = this.languageFeaturesService.foldingRangeProvider.ordered(this.foldingModel.textModel);
- if (foldingProviders.length === 0 && this.foldingStateMemento && this.foldingStateMemento.collapsedRegions) {
- const rangeProvider = this.rangeProvider = new InitializingRangeProvider(editorModel, this.foldingStateMemento.collapsedRegions, () => {
- // if after 30 the InitializingRangeProvider is still not replaced, force a refresh
- this.foldingStateMemento = null;
- this.onFoldingStrategyChanged();
- }, 30000);
- return rangeProvider; // keep memento in case there are still no foldingProviders on the next request.
- } else if (foldingProviders.length > 0) {
+ if (foldingProviders.length > 0) {
this.rangeProvider = new SyntaxRangeProvider(editorModel, foldingProviders, () => this.triggerFoldingModelChanged(), this._maxFoldingRegions);
}
}
- this.foldingStateMemento = null;
return this.rangeProvider;
}
@@ -1097,6 +1066,59 @@ class GotoNextFoldAction extends FoldingAction<void> {
}
}
+class FoldSelectedAction extends FoldingAction<void> {
+
+ constructor() {
+ super({
+ id: 'editor.foldSelected',
+ label: nls.localize('foldSelectedAction.label', "Fold Selected Lines"),
+ alias: 'Fold Selected Lines',
+ precondition: CONTEXT_FOLDING_ENABLED,
+ kbOpts: {
+ kbExpr: EditorContextKeys.editorTextFocus,
+ primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Period),
+ weight: KeybindingWeight.EditorContrib
+ }
+ });
+ }
+
+ invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void {
+ const collapseRanges: FoldRange[] = [];
+ const selections = editor.getSelections();
+ if (selections) {
+ for (const selection of selections) {
+ let endLineNumber = selection.endLineNumber;
+ if (selection.endColumn === 1) {
+ --endLineNumber;
+ }
+ if (endLineNumber > selection.startLineNumber) {
+ collapseRanges.push(<FoldRange>{
+ startLineNumber: selection.startLineNumber,
+ endLineNumber: endLineNumber,
+ type: undefined,
+ isCollapsed: true,
+ isManualSelection: true
+ });
+ editor.setSelection({
+ startLineNumber: selection.startLineNumber,
+ startColumn: 1,
+ endLineNumber: selection.startLineNumber,
+ endColumn: 1
+ });
+ }
+ }
+ if (collapseRanges.length > 0) {
+ collapseRanges.sort((a, b) => {
+ return a.startLineNumber - b.startLineNumber;
+ });
+ const newRanges = FoldingRegions.sanitizeAndMerge(foldingModel.regions, collapseRanges, editor.getModel()?.getLineCount());
+ foldingModel.updatePost(FoldingRegions.fromFoldRanges(newRanges));
+ }
+ }
+ }
+}
+
+
registerEditorContribution(FoldingController.ID, FoldingController);
registerEditorAction(UnfoldAction);
registerEditorAction(UnFoldRecursivelyAction);
@@ -1113,6 +1135,7 @@ registerEditorAction(ToggleFoldAction);
registerEditorAction(GotoParentFoldAction);
registerEditorAction(GotoPreviousFoldAction);
registerEditorAction(GotoNextFoldAction);
+registerEditorAction(FoldSelectedAction);
for (let i = 1; i <= 7; i++) {
registerInstantiatedEditorAction(
@@ -1143,7 +1166,8 @@ registerThemingParticipant((theme, collector) => {
if (editorFoldColor) {
collector.addRule(`
.monaco-editor .cldr${ThemeIcon.asCSSSelector(foldingExpandedIcon)},
- .monaco-editor .cldr${ThemeIcon.asCSSSelector(foldingCollapsedIcon)} {
+ .monaco-editor .cldr${ThemeIcon.asCSSSelector(foldingCollapsedIcon)},
+ .monaco-editor .cldr${ThemeIcon.asCSSSelector(foldingManualIcon)} {
color: ${editorFoldColor} !important;
}
`);
diff --git a/src/vs/editor/contrib/folding/browser/foldingDecorations.ts b/src/vs/editor/contrib/folding/browser/foldingDecorations.ts
index bdb63ab19e9..132dd19a9fa 100644
--- a/src/vs/editor/contrib/folding/browser/foldingDecorations.ts
+++ b/src/vs/editor/contrib/folding/browser/foldingDecorations.ts
@@ -5,7 +5,7 @@
import { Codicon } from 'vs/base/common/codicons';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
-import { IModelDecorationsChangeAccessor, TrackedRangeStickiness } from 'vs/editor/common/model';
+import { IModelDecorationOptions, IModelDecorationsChangeAccessor, TrackedRangeStickiness } from 'vs/editor/common/model';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { IDecorationProvider } from 'vs/editor/contrib/folding/browser/foldingModel';
import { localize } from 'vs/nls';
@@ -14,6 +14,7 @@ import { ThemeIcon } from 'vs/platform/theme/common/themeService';
export const foldingExpandedIcon = registerIcon('folding-expanded', Codicon.chevronDown, localize('foldingExpandedIcon', 'Icon for expanded ranges in the editor glyph margin.'));
export const foldingCollapsedIcon = registerIcon('folding-collapsed', Codicon.chevronRight, localize('foldingCollapsedIcon', 'Icon for collapsed ranges in the editor glyph margin.'));
+export const foldingManualIcon = registerIcon('folding-manual', Codicon.ellipsis, localize('foldingManualIcon', 'Icon for manually collapsed ranges in the editor glyph margin.'));
export class FoldingDecorationProvider implements IDecorationProvider {
private static readonly COLLAPSED_VISUAL_DECORATION = ModelDecorationOptions.register({
@@ -33,6 +34,23 @@ export class FoldingDecorationProvider implements IDecorationProvider {
firstLineDecorationClassName: ThemeIcon.asClassName(foldingCollapsedIcon)
});
+ private static readonly MANUALLY_COLLAPSED_VISUAL_DECORATION = ModelDecorationOptions.register({
+ description: 'folding-manually-collapsed-visual-decoration',
+ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
+ afterContentClassName: 'inline-folded',
+ isWholeLine: true,
+ firstLineDecorationClassName: ThemeIcon.asClassName(foldingManualIcon)
+ });
+
+ private static readonly MANUALLY_COLLAPSED_HIGHLIGHTED_VISUAL_DECORATION = ModelDecorationOptions.register({
+ description: 'folding-manually-collapsed-highlighted-visual-decoration',
+ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
+ afterContentClassName: 'inline-folded',
+ className: 'folded-background',
+ isWholeLine: true,
+ firstLineDecorationClassName: ThemeIcon.asClassName(foldingManualIcon)
+ });
+
private static readonly EXPANDED_AUTO_HIDE_VISUAL_DECORATION = ModelDecorationOptions.register({
description: 'folding-expanded-auto-hide-visual-decoration',
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
@@ -52,20 +70,23 @@ export class FoldingDecorationProvider implements IDecorationProvider {
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
});
- public autoHideFoldingControls: boolean = true;
+ public showFoldingControls: 'always' | 'never' | 'mouseover' = 'mouseover';
public showFoldingHighlights: boolean = true;
constructor(private readonly editor: ICodeEditor) {
}
- getDecorationOption(isCollapsed: boolean, isHidden: boolean): ModelDecorationOptions {
- if (isHidden) {
+ getDecorationOption(isCollapsed: boolean, isHidden: boolean, isManualSelection: boolean): IModelDecorationOptions {
+ if (isHidden // is inside another collapsed region
+ || this.showFoldingControls === 'never' || (isManualSelection && !isCollapsed)) { //
return FoldingDecorationProvider.HIDDEN_RANGE_DECORATION;
}
if (isCollapsed) {
- return this.showFoldingHighlights ? FoldingDecorationProvider.COLLAPSED_HIGHLIGHTED_VISUAL_DECORATION : FoldingDecorationProvider.COLLAPSED_VISUAL_DECORATION;
- } else if (this.autoHideFoldingControls) {
+ return isManualSelection ?
+ (this.showFoldingHighlights ? FoldingDecorationProvider.MANUALLY_COLLAPSED_HIGHLIGHTED_VISUAL_DECORATION : FoldingDecorationProvider.MANUALLY_COLLAPSED_VISUAL_DECORATION)
+ : (this.showFoldingHighlights ? FoldingDecorationProvider.COLLAPSED_HIGHLIGHTED_VISUAL_DECORATION : FoldingDecorationProvider.COLLAPSED_VISUAL_DECORATION);
+ } else if (this.showFoldingControls === 'mouseover') {
return FoldingDecorationProvider.EXPANDED_AUTO_HIDE_VISUAL_DECORATION;
} else {
return FoldingDecorationProvider.EXPANDED_VISUAL_DECORATION;
diff --git a/src/vs/editor/contrib/folding/browser/foldingModel.ts b/src/vs/editor/contrib/folding/browser/foldingModel.ts
index ba52acfdea8..59083ae1d3e 100644
--- a/src/vs/editor/contrib/folding/browser/foldingModel.ts
+++ b/src/vs/editor/contrib/folding/browser/foldingModel.ts
@@ -5,10 +5,11 @@
import { Emitter, Event } from 'vs/base/common/event';
import { IModelDecorationOptions, IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model';
-import { FoldingRegion, FoldingRegions, ILineRange } from './foldingRanges';
+import { FoldingRegion, FoldingRegions, ILineRange, FoldRange } from './foldingRanges';
+import { hash } from 'vs/base/common/hash';
export interface IDecorationProvider {
- getDecorationOption(isCollapsed: boolean, isHidden: boolean): IModelDecorationOptions;
+ getDecorationOption(isCollapsed: boolean, isHidden: boolean, isManualSelection: boolean): IModelDecorationOptions;
changeDecorations<T>(callback: (changeAccessor: IModelDecorationsChangeAccessor) => T): T | null;
removeDecorations(decorationIds: string[]): void;
}
@@ -18,30 +19,33 @@ export interface FoldingModelChangeEvent {
collapseStateChanged?: FoldingRegion[];
}
-export type CollapseMemento = ILineRange[];
+interface ILineMemento extends ILineRange {
+ checksum?: number;
+}
+
+export type CollapseMemento = ILineMemento[];
export class FoldingModel {
private readonly _textModel: ITextModel;
private readonly _decorationProvider: IDecorationProvider;
+ private readonly _triggerRecomputeRanges: (() => void) | undefined;
private _regions: FoldingRegions;
private _editorDecorationIds: string[];
- private _isInitialized: boolean;
private readonly _updateEventEmitter = new Emitter<FoldingModelChangeEvent>();
public readonly onDidChange: Event<FoldingModelChangeEvent> = this._updateEventEmitter.event;
public get regions(): FoldingRegions { return this._regions; }
public get textModel() { return this._textModel; }
- public get isInitialized() { return this._isInitialized; }
public get decorationProvider() { return this._decorationProvider; }
- constructor(textModel: ITextModel, decorationProvider: IDecorationProvider) {
+ constructor(textModel: ITextModel, decorationProvider: IDecorationProvider, triggerRecomputeRanges?: () => void) {
this._textModel = textModel;
this._decorationProvider = decorationProvider;
+ this._triggerRecomputeRanges = triggerRecomputeRanges;
this._regions = new FoldingRegions(new Uint32Array(0), new Uint32Array(0));
this._editorDecorationIds = [];
- this._isInitialized = false;
}
public toggleCollapseState(toggledRegions: FoldingRegion[]) {
@@ -51,6 +55,7 @@ export class FoldingModel {
toggledRegions = toggledRegions.sort((r1, r2) => r1.regionIndex - r2.regionIndex);
const processed: { [key: string]: boolean | undefined } = {};
+ const manualExpanded = false;
this._decorationProvider.changeDecorations(accessor => {
let k = 0; // index from [0 ... this.regions.length]
let dirtyRegionEndLine = -1; // end of the range where decorations need to be updated
@@ -59,8 +64,9 @@ export class FoldingModel {
while (k < index) {
const endLineNumber = this._regions.getEndLineNumber(k);
const isCollapsed = this._regions.isCollapsed(k);
+ const isManualSelection = this.regions.isManualSelection(k);
if (endLineNumber <= dirtyRegionEndLine) {
- accessor.changeDecorationOptions(this._editorDecorationIds[k], this._decorationProvider.getDecorationOption(isCollapsed, endLineNumber <= lastHiddenLine));
+ accessor.changeDecorationOptions(this._editorDecorationIds[k], this._decorationProvider.getDecorationOption(isCollapsed, endLineNumber <= lastHiddenLine, isManualSelection));
}
if (isCollapsed && endLineNumber > lastHiddenLine) {
lastHiddenLine = endLineNumber;
@@ -85,110 +91,94 @@ export class FoldingModel {
updateDecorationsUntil(this._regions.length);
});
this._updateEventEmitter.fire({ model: this, collapseStateChanged: toggledRegions });
+ if (manualExpanded && this._triggerRecomputeRanges) {
+ // expanding a range which didn't originate from range provider might now enable ranges
+ // from the provider which were previously dropped due to the collapsed range
+ this._triggerRecomputeRanges();
+ }
}
public update(newRegions: FoldingRegions, blockedLineNumers: number[] = []): void {
- const newEditorDecorations: IModelDeltaDecoration[] = [];
-
- const isBlocked = (startLineNumber: number, endLineNumber: number) => {
- for (const blockedLineNumber of blockedLineNumers) {
- if (startLineNumber < blockedLineNumber && blockedLineNumber <= endLineNumber) { // first line is visible
- return true;
- }
- }
- return false;
- };
+ const hiddenRanges = this._currentHiddenRegions(blockedLineNumers);
+ const newRanges = FoldingRegions.sanitizeAndMerge(newRegions, hiddenRanges, this._textModel.getLineCount());
+ this.updatePost(FoldingRegions.fromFoldRanges(newRanges));
+ }
+ public updatePost(newRegions: FoldingRegions) {
+ const newEditorDecorations: IModelDeltaDecoration[] = [];
let lastHiddenLine = -1;
-
- const initRange = (index: number, isCollapsed: boolean) => {
+ for (let index = 0, limit = newRegions.length; index < limit; index++) {
const startLineNumber = newRegions.getStartLineNumber(index);
const endLineNumber = newRegions.getEndLineNumber(index);
- if (!isCollapsed) {
- isCollapsed = newRegions.isCollapsed(index);
- }
- if (isCollapsed && isBlocked(startLineNumber, endLineNumber)) {
- isCollapsed = false;
- }
- newRegions.setCollapsed(index, isCollapsed);
-
- const maxColumn = this._textModel.getLineMaxColumn(startLineNumber);
+ const isCollapsed = newRegions.isCollapsed(index);
+ const isManualSelection = newRegions.isManualSelection(index);
const decorationRange = {
startLineNumber: startLineNumber,
- startColumn: Math.max(maxColumn - 1, 1), // make it length == 1 to detect deletions
- endLineNumber: startLineNumber,
- endColumn: maxColumn
+ startColumn: this._textModel.getLineMaxColumn(startLineNumber),
+ endLineNumber: endLineNumber,
+ endColumn: this._textModel.getLineMaxColumn(endLineNumber) + 1
};
- newEditorDecorations.push({ range: decorationRange, options: this._decorationProvider.getDecorationOption(isCollapsed, endLineNumber <= lastHiddenLine) });
+ newEditorDecorations.push({ range: decorationRange, options: this._decorationProvider.getDecorationOption(isCollapsed, endLineNumber <= lastHiddenLine, isManualSelection) });
if (isCollapsed && endLineNumber > lastHiddenLine) {
lastHiddenLine = endLineNumber;
}
- };
- let i = 0;
- const nextCollapsed = () => {
- while (i < this._regions.length) {
- const isCollapsed = this._regions.isCollapsed(i);
- i++;
- if (isCollapsed) {
- return i - 1;
+ }
+ this._decorationProvider.changeDecorations(accessor => this._editorDecorationIds = accessor.deltaDecorations(this._editorDecorationIds, newEditorDecorations));
+ this._regions = newRegions;
+ this._updateEventEmitter.fire({ model: this });
+ }
+
+ private _currentHiddenRegions(blockedLineNumers: number[] = []): FoldRange[] {
+
+ const isBlocked = (startLineNumber: number, endLineNumber: number) => {
+ for (const blockedLineNumber of blockedLineNumers) {
+ if (startLineNumber < blockedLineNumber && blockedLineNumber <= endLineNumber) { // first line is visible
+ return true;
}
}
- return -1;
+ return false;
};
- let k = 0;
- let collapsedIndex = nextCollapsed();
- while (collapsedIndex !== -1 && k < newRegions.length) {
- // get the latest range
- const decRange = this._textModel.getDecorationRange(this._editorDecorationIds[collapsedIndex]);
- if (decRange) {
- const collapsedStartLineNumber = decRange.startLineNumber;
- if (decRange.startColumn === Math.max(decRange.endColumn - 1, 1) && this._textModel.getLineMaxColumn(collapsedStartLineNumber) === decRange.endColumn) { // test that the decoration is still covering the full line else it got deleted
- while (k < newRegions.length) {
- const startLineNumber = newRegions.getStartLineNumber(k);
- if (collapsedStartLineNumber >= startLineNumber) {
- initRange(k, collapsedStartLineNumber === startLineNumber);
- k++;
- } else {
- break;
- }
- }
+ const hiddenRanges: FoldRange[] = [];
+ for (let i = 0, limit = this._regions.length; i < limit; i++) {
+ if (this.regions.isCollapsed(i)) {
+ const hiddenRange = this._regions.toFoldRange(i);
+ const decRange = this._textModel.getDecorationRange(this._editorDecorationIds[i]);
+ if (decRange
+ && !isBlocked(decRange.startLineNumber, decRange.endLineNumber)
+ // if not same length user has modified it, skip and auto-expand
+ && decRange.endLineNumber - decRange.startLineNumber
+ === hiddenRange.endLineNumber - hiddenRange.startLineNumber) {
+ hiddenRanges.push({
+ startLineNumber: decRange.startLineNumber,
+ endLineNumber: decRange.endLineNumber,
+ type: hiddenRange.type,
+ isCollapsed: true,
+ isManualSelection: hiddenRange.isManualSelection
+ });
}
}
- collapsedIndex = nextCollapsed();
- }
- while (k < newRegions.length) {
- initRange(k, false);
- k++;
}
- this._decorationProvider.changeDecorations((changeAccessor) => {
- this._editorDecorationIds = changeAccessor.deltaDecorations(this._editorDecorationIds, newEditorDecorations);
- });
- this._regions = newRegions;
- this._isInitialized = true;
- this._updateEventEmitter.fire({ model: this });
+ return hiddenRanges;
}
/**
* Collapse state memento, for persistence only
*/
public getMemento(): CollapseMemento | undefined {
- const collapsedRanges: ILineRange[] = [];
- for (let i = 0; i < this._regions.length; i++) {
- if (this._regions.isCollapsed(i)) {
- const range = this._textModel.getDecorationRange(this._editorDecorationIds[i]);
- if (range) {
- const startLineNumber = range.startLineNumber;
- const endLineNumber = range.endLineNumber + this._regions.getEndLineNumber(i) - this._regions.getStartLineNumber(i);
- collapsedRanges.push({ startLineNumber, endLineNumber });
- }
- }
+ const hiddenRegions = this._currentHiddenRegions();
+ const result: ILineMemento[] = [];
+ for (let i = 0, limit = hiddenRegions.length; i < limit; i++) {
+ const range = hiddenRegions[i];
+ const checksum = this._getLinesChecksum(range.startLineNumber + 1, range.endLineNumber);
+ result.push({
+ startLineNumber: range.startLineNumber,
+ endLineNumber: range.endLineNumber,
+ checksum: checksum
+ });
}
- if (collapsedRanges.length > 0) {
- return collapsedRanges;
- }
- return undefined;
+ return (result.length > 0) ? result : undefined;
}
/**
@@ -198,14 +188,32 @@ export class FoldingModel {
if (!Array.isArray(state)) {
return;
}
- const toToogle: FoldingRegion[] = [];
+ const hiddenRanges: FoldRange[] = [];
+ const maxLineNumber = this._textModel.getLineCount();
for (const range of state) {
- const region = this.getRegionAtLine(range.startLineNumber);
- if (region && !region.isCollapsed) {
- toToogle.push(region);
+ if (range.startLineNumber >= range.endLineNumber || range.startLineNumber < 1 || range.endLineNumber > maxLineNumber) {
+ continue;
+ }
+ const checksum = this._getLinesChecksum(range.startLineNumber + 1, range.endLineNumber);
+ if (!range.checksum || checksum === range.checksum) {
+ hiddenRanges.push({
+ startLineNumber: range.startLineNumber,
+ endLineNumber: range.endLineNumber,
+ type: undefined,
+ isCollapsed: true,
+ isManualSelection: true // converts to false when provider sends a match
+ });
}
}
- this.toggleCollapseState(toToogle);
+
+ const newRanges = FoldingRegions.sanitizeAndMerge(this._regions, hiddenRanges, maxLineNumber);
+ this.updatePost(FoldingRegions.fromFoldRanges(newRanges));
+ }
+
+ private _getLinesChecksum(lineNumber1: number, lineNumber2: number): number {
+ const h = hash(this._textModel.getLineContent(lineNumber1)
+ + this._textModel.getLineContent(lineNumber2));
+ return h % 1000000; // 6 digits is plenty
}
public dispose() {
diff --git a/src/vs/editor/contrib/folding/browser/foldingRanges.ts b/src/vs/editor/contrib/folding/browser/foldingRanges.ts
index 0d94a26fdad..b45c3222349 100644
--- a/src/vs/editor/contrib/folding/browser/foldingRanges.ts
+++ b/src/vs/editor/contrib/folding/browser/foldingRanges.ts
@@ -8,6 +8,14 @@ export interface ILineRange {
endLineNumber: number;
}
+export interface FoldRange {
+ startLineNumber: number;
+ endLineNumber: number;
+ type: string | undefined;
+ isCollapsed: boolean;
+ isManualSelection: boolean;
+}
+
export const MAX_FOLDING_REGIONS = 0xFFFF;
export const MAX_LINE_NUMBER = 0xFFFFFF;
@@ -17,6 +25,7 @@ export class FoldingRegions {
private readonly _startIndexes: Uint32Array;
private readonly _endIndexes: Uint32Array;
private readonly _collapseStates: Uint32Array;
+ private readonly _manualStates: Uint32Array;
private _parentsComputed: boolean;
private readonly _types: Array<string | undefined> | undefined;
@@ -26,7 +35,9 @@ export class FoldingRegions {
}
this._startIndexes = startIndexes;
this._endIndexes = endIndexes;
- this._collapseStates = new Uint32Array(Math.ceil(startIndexes.length / 32));
+ const numWords = Math.ceil(startIndexes.length / 32);
+ this._collapseStates = new Uint32Array(numWords);
+ this._manualStates = new Uint32Array(numWords);
this._types = types;
this._parentsComputed = false;
}
@@ -93,6 +104,23 @@ export class FoldingRegions {
}
}
+ public isManualSelection(index: number): boolean {
+ const arrayIndex = (index / 32) | 0;
+ const bit = index % 32;
+ return (this._manualStates[arrayIndex] & (1 << bit)) !== 0;
+ }
+
+ public setManualSelection(index: number, newState: boolean) {
+ const arrayIndex = (index / 32) | 0;
+ const bit = index % 32;
+ const value = this._manualStates[arrayIndex];
+ if (newState) {
+ this._manualStates[arrayIndex] = value | (1 << bit);
+ } else {
+ this._manualStates[arrayIndex] = value & ~(1 << bit);
+ }
+ }
+
public setCollapsedAllOfType(type: string, newState: boolean) {
let hasChanged = false;
if (this._types) {
@@ -160,30 +188,174 @@ export class FoldingRegions {
public toString() {
const res: string[] = [];
for (let i = 0; i < this.length; i++) {
- res[i] = `[${this.isCollapsed(i) ? '+' : '-'}] ${this.getStartLineNumber(i)}/${this.getEndLineNumber(i)}`;
+ res[i] = `[${this.isManualSelection(i) ? '*' : ' '}${this.isCollapsed(i) ? '+' : '-'}] ${this.getStartLineNumber(i)}/${this.getEndLineNumber(i)}`;
}
return res.join(', ');
}
- public equals(b: FoldingRegions) {
- if (this.length !== b.length) {
- return false;
- }
+ public toFoldRange(index: number): FoldRange {
+ return <FoldRange>{
+ startLineNumber: this._startIndexes[index] & MAX_LINE_NUMBER,
+ endLineNumber: this._endIndexes[index] & MAX_LINE_NUMBER,
+ type: this._types ? this._types[index] : undefined,
+ isCollapsed: this.isCollapsed(index),
+ isManualSelection: this.isManualSelection(index)
+ };
+ }
- for (let i = 0; i < this.length; i++) {
- if (this.getStartLineNumber(i) !== b.getStartLineNumber(i)) {
- return false;
+ public static fromFoldRanges(ranges: FoldRange[]): FoldingRegions {
+ const rangesLength = ranges.length;
+ const startIndexes = new Uint32Array(rangesLength);
+ const endIndexes = new Uint32Array(rangesLength);
+ let types: Array<string | undefined> | undefined = [];
+ let gotTypes = false;
+ for (let i = 0; i < rangesLength; i++) {
+ const range = ranges[i];
+ startIndexes[i] = range.startLineNumber;
+ endIndexes[i] = range.endLineNumber;
+ types.push(range.type);
+ if (range.type) {
+ gotTypes = true;
}
- if (this.getEndLineNumber(i) !== b.getEndLineNumber(i)) {
- return false;
+ }
+ if (!gotTypes) {
+ types = undefined;
+ }
+ const regions = new FoldingRegions(startIndexes, endIndexes, types);
+ for (let i = 0; i < rangesLength; i++) {
+ if (ranges[i].isCollapsed) {
+ regions.setCollapsed(i, true);
}
- if (this.getType(i) !== b.getType(i)) {
- return false;
+ if (ranges[i].isManualSelection) {
+ regions.setManualSelection(i, true);
}
}
+ return regions;
+ }
- return true;
+ /**
+ * Two inputs, each a FoldingRegions or a FoldRange[], are merged.
+ * Each input must be pre-sorted on startLineNumber.
+ * The first list is assumed to always include all regions currently defined by range providers.
+ * The second list only contains hidden ranges.
+ * When an entry in one list overlaps an entry in the other, the second list's entry "wins" and
+ * overlapping entries in the first list are discarded. With one exception: when there is just
+ * one such second list entry and it is not manual it is discarded, on the assumption that
+ * user editing has resulted in the range no longer existing.
+ * Invalid entries are discarded. An entry is invalid if:
+ * the start and end line numbers aren't a valid range of line numbers,
+ * it is out of sequence or has the same start line as a preceding entry,
+ * it overlaps a preceding entry and is not fully contained by that entry.
+ */
+ public static sanitizeAndMerge(
+ rangesA: FoldingRegions | FoldRange[],
+ rangesB: FoldingRegions | FoldRange[],
+ maxLineNumber: number | undefined): FoldRange[] {
+ maxLineNumber = maxLineNumber ?? Number.MAX_VALUE;
+ let result = this._trySanitizeAndMerge(1, rangesA, rangesB, maxLineNumber);
+ if (!result) { // try again, converting hidden ranges to manually selected
+ result = this._trySanitizeAndMerge(2, rangesA, rangesB, maxLineNumber);
+ }
+ return result!;
}
+
+ private static _trySanitizeAndMerge(
+ passNumber: number, // it can take two passes to get this done
+ rangesA: FoldingRegions | FoldRange[],
+ rangesB: FoldingRegions | FoldRange[],
+ maxLineNumber: number): FoldRange[] | null {
+
+ const getIndexedFunction = (r: FoldingRegions | FoldRange[], limit: number) => {
+ return Array.isArray(r)
+ ? ((i: number) => { return (i < limit) ? r[i] : undefined; })
+ : ((i: number) => { return (i < limit) ? r.toFoldRange(i) : undefined; });
+ };
+ const getA = getIndexedFunction(rangesA, rangesA.length);
+ const getB = getIndexedFunction(rangesB, rangesB.length);
+ let indexA = 0;
+ let indexB = 0;
+ let nextA = getA(0);
+ let nextB = getB(0);
+
+ const stackedRanges: FoldRange[] = [];
+ let topStackedRange: FoldRange | undefined;
+ let prevLineNumber = 0;
+ const resultRanges: FoldRange[] = [];
+ let numberAutoExpand = 0;
+
+ while (nextA || nextB) {
+
+ let useRange: FoldRange | undefined = undefined;
+ if (nextB && (!nextA || nextA.startLineNumber >= nextB.startLineNumber)) {
+ // nextB is next
+ if (nextA
+ && nextA.startLineNumber === nextB.startLineNumber
+ && nextA.endLineNumber === nextB.endLineNumber) {
+ // same range in both lists, merge the details
+ useRange = nextB;
+ useRange.isCollapsed = useRange.isCollapsed || nextA.isCollapsed;
+ // next line removes manual flag when range provider has matching range
+ useRange.isManualSelection = nextA.isManualSelection && nextB.isManualSelection;
+ if (!useRange.type) {
+ useRange.type = nextA.type;
+ }
+ nextA = getA(++indexA); // not necessary, just for speed
+ } else if (nextB.isCollapsed && !nextB.isManualSelection && passNumber === 1) {
+ if (++numberAutoExpand > 1) {
+ // do second pass keeping these, assuming something like an unmatched /*
+ return null;
+ }
+ // skip nextB (auto expand) by not setting useRange, assuming it was edited
+ } else { // use nextB
+ useRange = nextB;
+ if (useRange.isCollapsed) {
+ // doesn't match nextA, convert to a manual selection if it wasn't already
+ useRange.isManualSelection = true;
+ }
+ }
+ nextB = getB(++indexB);
+ } else {
+ // nextA is next. The B set takes precedence and we sometimes need to look
+ // ahead in it to check for an upcoming conflict.
+ let scanIndex = indexB;
+ let prescanB = nextB;
+ while (true) {
+ if (!prescanB || prescanB.startLineNumber > nextA!.endLineNumber) {
+ useRange = nextA;
+ break; // no conflict, use this nextA
+ }
+ if (prescanB.endLineNumber > nextA!.endLineNumber
+ && (!prescanB.isCollapsed || prescanB.isManualSelection || passNumber === 2)) {
+ break; // without setting nextResult, so this nextA gets skipped
+ }
+ prescanB = getB(++scanIndex);
+ }
+ nextA = getA(++indexA);
+ }
+
+ if (useRange) {
+ while (topStackedRange
+ && topStackedRange.endLineNumber < useRange.startLineNumber) {
+ topStackedRange = stackedRanges.pop();
+ }
+ if (useRange.endLineNumber > useRange.startLineNumber
+ && useRange.startLineNumber > prevLineNumber
+ && useRange.endLineNumber <= maxLineNumber
+ && (!topStackedRange
+ || topStackedRange.endLineNumber >= useRange.endLineNumber)) {
+ resultRanges.push(useRange);
+ prevLineNumber = useRange.startLineNumber;
+ if (topStackedRange) {
+ stackedRanges.push(topStackedRange);
+ }
+ topStackedRange = useRange;
+ }
+ }
+
+ }
+ return resultRanges;
+ }
+
}
export class FoldingRegion {
diff --git a/src/vs/editor/contrib/folding/browser/hiddenRangeModel.ts b/src/vs/editor/contrib/folding/browser/hiddenRangeModel.ts
index a9e6f07a1bd..ea8ff076531 100644
--- a/src/vs/editor/contrib/folding/browser/hiddenRangeModel.ts
+++ b/src/vs/editor/contrib/folding/browser/hiddenRangeModel.ts
@@ -11,7 +11,7 @@ import { IRange, Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import { IModelContentChangedEvent } from 'vs/editor/common/textModelEvents';
import { countEOL } from 'vs/editor/common/core/eolCounter';
-import { CollapseMemento, FoldingModel } from 'vs/editor/contrib/folding/browser/foldingModel';
+import { FoldingModel } from 'vs/editor/contrib/folding/browser/foldingModel';
export class HiddenRangeModel {
@@ -79,28 +79,6 @@ export class HiddenRangeModel {
}
}
- public applyMemento(state: CollapseMemento): boolean {
- if (!Array.isArray(state) || state.length === 0) {
- return false;
- }
- const hiddenRanges: IRange[] = [];
- for (const r of state) {
- if (!r.startLineNumber || !r.endLineNumber) {
- return false;
- }
- hiddenRanges.push(new Range(r.startLineNumber + 1, 1, r.endLineNumber, 1));
- }
- this.applyHiddenRanges(hiddenRanges);
- return true;
- }
-
- /**
- * Collapse state memento, for persistence only, only used if folding model is not yet initialized
- */
- public getMemento(): CollapseMemento {
- return this._hiddenRanges.map(r => ({ startLineNumber: r.startLineNumber - 1, endLineNumber: r.endLineNumber }));
- }
-
private applyHiddenRanges(newHiddenAreas: IRange[]) {
this._hiddenRanges = newHiddenAreas;
this._hasLineChanges = false;
diff --git a/src/vs/editor/contrib/folding/browser/intializingRangeProvider.ts b/src/vs/editor/contrib/folding/browser/intializingRangeProvider.ts
deleted file mode 100644
index 8d8a0ec1901..00000000000
--- a/src/vs/editor/contrib/folding/browser/intializingRangeProvider.ts
+++ /dev/null
@@ -1,65 +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 { CancellationToken } from 'vs/base/common/cancellation';
-import { IModelDeltaDecoration, ITextModel, TrackedRangeStickiness } from 'vs/editor/common/model';
-import { FoldingRegions, ILineRange } from 'vs/editor/contrib/folding/browser/foldingRanges';
-import { IFoldingRangeData, sanitizeRanges } from 'vs/editor/contrib/folding/browser/syntaxRangeProvider';
-import { RangeProvider } from './folding';
-
-export const ID_INIT_PROVIDER = 'init';
-
-export class InitializingRangeProvider implements RangeProvider {
- readonly id = ID_INIT_PROVIDER;
-
- private decorationIds: string[] | undefined;
- private timeout: any;
-
- constructor(private readonly editorModel: ITextModel, initialRanges: ILineRange[], onTimeout: () => void, timeoutTime: number) {
- if (initialRanges.length) {
- const toDecorationRange = (range: ILineRange): IModelDeltaDecoration => {
- return {
- range: {
- startLineNumber: range.startLineNumber,
- startColumn: 0,
- endLineNumber: range.endLineNumber,
- endColumn: editorModel.getLineLength(range.endLineNumber)
- },
- options: {
- description: 'folding-initializing-range-provider',
- stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
- }
- };
- };
- this.decorationIds = editorModel.deltaDecorations([], initialRanges.map(toDecorationRange));
- this.timeout = setTimeout(onTimeout, timeoutTime);
- }
- }
-
- dispose(): void {
- if (this.decorationIds) {
- this.editorModel.deltaDecorations(this.decorationIds, []);
- this.decorationIds = undefined;
- }
- if (typeof this.timeout === 'number') {
- clearTimeout(this.timeout);
- this.timeout = undefined;
- }
- }
-
- compute(cancelationToken: CancellationToken): Promise<FoldingRegions> {
- const foldingRangeData: IFoldingRangeData[] = [];
- if (this.decorationIds) {
- for (const id of this.decorationIds) {
- const range = this.editorModel.getDecorationRange(id);
- if (range) {
- foldingRangeData.push({ start: range.startLineNumber, end: range.endLineNumber, rank: 1 });
- }
- }
- }
- return Promise.resolve(sanitizeRanges(foldingRangeData, Number.MAX_VALUE));
- }
-}
-
diff --git a/src/vs/editor/contrib/folding/test/browser/foldingRanges.test.ts b/src/vs/editor/contrib/folding/test/browser/foldingRanges.test.ts
index 8fbfc240fe6..a1979da3c22 100644
--- a/src/vs/editor/contrib/folding/test/browser/foldingRanges.test.ts
+++ b/src/vs/editor/contrib/folding/test/browser/foldingRanges.test.ts
@@ -5,7 +5,7 @@
import * as assert from 'assert';
import { FoldingMarkers } from 'vs/editor/common/languages/languageConfiguration';
-import { MAX_FOLDING_REGIONS } from 'vs/editor/contrib/folding/browser/foldingRanges';
+import { MAX_FOLDING_REGIONS, FoldRange, FoldingRegions } from 'vs/editor/contrib/folding/browser/foldingRanges';
import { computeRanges } from 'vs/editor/contrib/folding/browser/indentRangeProvider';
import { createTextModel } from 'vs/editor/test/common/testTextModel';
@@ -14,9 +14,24 @@ const markers: FoldingMarkers = {
end: /^\s*#endregion\b/
};
-
suite('FoldingRanges', () => {
+ const foldRange = (from: number, to: number, collapsed: boolean | undefined = undefined, manual: boolean | undefined = undefined, type: string | undefined = undefined) =>
+ <FoldRange>{
+ startLineNumber: from,
+ endLineNumber: to,
+ type: type,
+ isCollapsed: collapsed || false,
+ isManualSelection: manual || false
+ };
+ const assertEqualRanges = (range1: FoldRange, range2: FoldRange, msg: string) => {
+ assert.strictEqual(range1.startLineNumber, range2.startLineNumber, msg + ' start');
+ assert.strictEqual(range1.endLineNumber, range2.endLineNumber, msg + ' end');
+ assert.strictEqual(range1.type, range2.type, msg + ' type');
+ assert.strictEqual(range1.isCollapsed, range2.isCollapsed, msg + ' collapsed');
+ assert.strictEqual(range1.isManualSelection, range2.isManualSelection, msg + ' manual');
+ };
+
test('test max folding regions', () => {
const lines: string[] = [];
const nRegions = MAX_FOLDING_REGIONS;
@@ -103,4 +118,114 @@ suite('FoldingRanges', () => {
}
model.dispose();
});
+
+ test('sanitizeAndMerge1', () => {
+ const regionSet1: FoldRange[] = [
+ foldRange(0, 100), // invalid, should be removed
+ foldRange(1, 100, false, false, 'A'), // valid
+ foldRange(1, 100, false, false, 'Z'), // invalid, duplicate start
+ foldRange(10, 10, false), // invalid, should be removed
+ foldRange(20, 80, false, false, 'C1'), // valid inside 'B'
+ foldRange(22, 80, true, false, 'D1'), // valid inside 'C1'
+ foldRange(90, 101), // invalid, should be removed
+ ];
+ const regionSet2: FoldRange[] = [
+ foldRange(2, 100, false, false, 'B'), // valid, inside 'A'
+ foldRange(20, 80, true), // should merge with C1
+ foldRange(18, 80, true), // invalid, out of order
+ foldRange(21, 81, true, false, 'Z'), // invalid, overlapping
+ foldRange(22, 80, false, false, 'D2'), // should merge with D1
+ ];
+ let result = FoldingRegions.sanitizeAndMerge(regionSet1, regionSet2, 100);
+ assert.strictEqual(result.length, 4, 'result length1');
+ assertEqualRanges(result[0], foldRange(1, 100, false, false, 'A'), 'A1');
+ assertEqualRanges(result[1], foldRange(2, 100, false, false, 'B'), 'B1');
+ assertEqualRanges(result[2], foldRange(20, 80, true, false, 'C1'), 'C1');
+ assertEqualRanges(result[3], foldRange(22, 80, true, false, 'D2'), 'D1');
+ const regionClass1 = FoldingRegions.fromFoldRanges(regionSet1);
+ const regionClass2 = FoldingRegions.fromFoldRanges(regionSet2);
+ // same tests again with inputs as FoldingRegions instead of FoldRange[]
+ result = FoldingRegions.sanitizeAndMerge(regionClass1, regionClass2, 100);
+ assert.strictEqual(result.length, 4, 'result length2');
+ assertEqualRanges(result[0], foldRange(1, 100, false, false, 'A'), 'A2');
+ assertEqualRanges(result[1], foldRange(2, 100, false, false, 'B'), 'B2');
+ assertEqualRanges(result[2], foldRange(20, 80, true, false, 'C1'), 'C2');
+ assertEqualRanges(result[3], foldRange(22, 80, true, false, 'D2'), 'D2');
+ });
+
+ test('sanitizeAndMerge2', () => {
+ const regionSet1: FoldRange[] = [
+ foldRange(1, 100, false, false, 'a1'), // valid
+ foldRange(2, 100, false, false, 'a2'), // valid
+ foldRange(3, 19, false, false, 'a3'), // valid
+ foldRange(20, 71, false, false, 'a4'), // overlaps b3
+ foldRange(21, 29, false, false, 'a5'), // valid
+ foldRange(81, 91, false, false, 'a6'), // overlaps b4
+ ];
+ const regionSet2: FoldRange[] = [
+ foldRange(30, 39, false, false, 'b1'), // valid
+ foldRange(40, 49, false, false, 'b2'), // valid
+ foldRange(50, 100, false, false, 'b3'), // overlaps a4
+ foldRange(80, 90, false, false, 'b4'), // overlaps a6
+ foldRange(92, 100, false, false, 'b5'), // valid
+ ];
+ let result = FoldingRegions.sanitizeAndMerge(regionSet1, regionSet2, 100);
+ assert.strictEqual(result.length, 9, 'result length1');
+ assertEqualRanges(result[0], foldRange(1, 100, false, false, 'a1'), 'P1');
+ assertEqualRanges(result[1], foldRange(2, 100, false, false, 'a2'), 'P2');
+ assertEqualRanges(result[2], foldRange(3, 19, false, false, 'a3'), 'P3');
+ assertEqualRanges(result[3], foldRange(21, 29, false, false, 'a5'), 'P4');
+ assertEqualRanges(result[4], foldRange(30, 39, false, false, 'b1'), 'P5');
+ assertEqualRanges(result[5], foldRange(40, 49, false, false, 'b2'), 'P6');
+ assertEqualRanges(result[6], foldRange(50, 100, false, false, 'b3'), 'P7');
+ assertEqualRanges(result[7], foldRange(80, 90, false, false, 'b4'), 'P8');
+ assertEqualRanges(result[8], foldRange(92, 100, false, false, 'b5'), 'P9');
+ // reverse the two inputs
+ result = FoldingRegions.sanitizeAndMerge(regionSet2, regionSet1, 100);
+ assert.strictEqual(result.length, 9, 'result length2');
+ assertEqualRanges(result[0], foldRange(1, 100, false, false, 'a1'), 'Q1');
+ assertEqualRanges(result[1], foldRange(2, 100, false, false, 'a2'), 'Q2');
+ assertEqualRanges(result[2], foldRange(3, 19, false, false, 'a3'), 'Q3');
+ assertEqualRanges(result[3], foldRange(20, 71, false, false, 'a4'), 'Q4');
+ assertEqualRanges(result[4], foldRange(21, 29, false, false, 'a5'), 'Q5');
+ assertEqualRanges(result[5], foldRange(30, 39, false, false, 'b1'), 'Q6');
+ assertEqualRanges(result[6], foldRange(40, 49, false, false, 'b2'), 'Q7');
+ assertEqualRanges(result[7], foldRange(81, 91, false, false, 'a6'), 'Q8');
+ assertEqualRanges(result[8], foldRange(92, 100, false, false, 'b5'), 'Q9');
+ });
+
+ test('sanitizeAndMerge3', () => {
+ const regionSet1: FoldRange[] = [
+ foldRange(1, 100, false, false, 'a1'), // valid
+ foldRange(10, 29, false, false, 'a2'), // matches manual hidden
+ foldRange(35, 39, true, true, 'a3'), // valid
+ ];
+ const regionSet2: FoldRange[] = [
+ foldRange(10, 29, true, true, 'b1'), // matches a
+ foldRange(20, 28, true, false, 'b2'), // should get dropped
+ foldRange(30, 39, true, true, 'b3'), // should remain
+ ];
+ const result = FoldingRegions.sanitizeAndMerge(regionSet1, regionSet2, 100);
+ assert.strictEqual(result.length, 4, 'result length3');
+ assertEqualRanges(result[0], foldRange(1, 100, false, false, 'a1'), 'R1');
+ assertEqualRanges(result[1], foldRange(10, 29, true, false, 'b1'), 'R2');
+ assertEqualRanges(result[2], foldRange(30, 39, true, true, 'b3'), 'R3');
+ assertEqualRanges(result[3], foldRange(35, 39, true, true, 'a3'), 'R4');
+ });
+
+ test('sanitizeAndMerge4', () => {
+ const regionSet1: FoldRange[] = [
+ foldRange(1, 100, false, false, 'a1'), // valid
+ ];
+ const regionSet2: FoldRange[] = [
+ foldRange(20, 28, true, false, 'b1'), // hidden
+ foldRange(30, 38, true, false, 'b2'), // hidden
+ ];
+ const result = FoldingRegions.sanitizeAndMerge(regionSet1, regionSet2, 100);
+ assert.strictEqual(result.length, 3, 'result length4');
+ assertEqualRanges(result[0], foldRange(1, 100, false, false, 'a1'), 'R1');
+ assertEqualRanges(result[1], foldRange(20, 28, true, true, 'b1'), 'R2');
+ assertEqualRanges(result[2], foldRange(30, 38, true, true, 'b2'), 'R3');
+ });
+
});
diff --git a/src/vs/editor/contrib/markdownRenderer/browser/markdownRenderer.ts b/src/vs/editor/contrib/markdownRenderer/browser/markdownRenderer.ts
index 7220dbf1a4f..f72e2293aa7 100644
--- a/src/vs/editor/contrib/markdownRenderer/browser/markdownRenderer.ts
+++ b/src/vs/editor/contrib/markdownRenderer/browser/markdownRenderer.ts
@@ -10,7 +10,7 @@ import { ILanguageService } from 'vs/editor/common/languages/language';
import { onUnexpectedError } from 'vs/base/common/errors';
import { tokenizeToString } from 'vs/editor/common/languages/textToHtmlTokenizer';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
-import { Emitter } from 'vs/base/common/event';
+import { DebounceEmitter } from 'vs/base/common/event';
import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo';
@@ -38,7 +38,10 @@ export class MarkdownRenderer {
}
});
- private readonly _onDidRenderAsync = new Emitter<void>();
+ private readonly _onDidRenderAsync = new DebounceEmitter<void>({
+ delay: 50,
+ merge: arr => { }
+ });
readonly onDidRenderAsync = this._onDidRenderAsync.event;
constructor(
diff --git a/src/vs/editor/contrib/quickAccess/browser/gotoSymbolQuickAccess.ts b/src/vs/editor/contrib/quickAccess/browser/gotoSymbolQuickAccess.ts
index 1801ab78538..0702c58a85c 100644
--- a/src/vs/editor/contrib/quickAccess/browser/gotoSymbolQuickAccess.ts
+++ b/src/vs/editor/contrib/quickAccess/browser/gotoSymbolQuickAccess.ts
@@ -19,6 +19,8 @@ import { AbstractEditorNavigationQuickAccessProvider, IEditorNavigationQuickAcce
import { localize } from 'vs/nls';
import { IQuickPick, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
+import { Position } from 'vs/editor/common/core/position';
+import { findLast } from 'vs/base/common/arrays';
export interface IGotoSymbolQuickPickItem extends IQuickPickItem {
kind: SymbolKind;
@@ -155,7 +157,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
// Set initial picks and update on type
let picksCts: CancellationTokenSource | undefined = undefined;
- const updatePickerItems = async () => {
+ const updatePickerItems = async (positionToEnclose: Position | undefined) => {
// Cancel any previous ask for picks and busy
picksCts?.dispose(true);
@@ -175,6 +177,13 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
if (items.length > 0) {
picker.items = items;
+ if (positionToEnclose && query.original.length === 0) {
+ const candidate = <IGotoSymbolQuickPickItem>findLast(items, item => Boolean(item.type !== 'separator' && item.range && Range.containsPosition(item.range.decoration, positionToEnclose)));
+ if (candidate) {
+ picker.activeItems = [candidate];
+ }
+ }
+
} else {
if (query.original.length > 0) {
this.provideLabelPick(picker, localize('noMatchingSymbolResults', "No matching editor symbols"));
@@ -188,19 +197,19 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
}
}
};
- disposables.add(picker.onDidChangeValue(() => updatePickerItems()));
- updatePickerItems();
+ disposables.add(picker.onDidChangeValue(() => updatePickerItems(undefined)));
+ updatePickerItems(editor.getSelection()?.getPosition());
+
// Reveal and decorate when active item changes
- // However, ignore the very first event so that
+ // However, ignore the very first two events so that
// opening the picker is not immediately revealing
// and decorating the first entry.
- let ignoreFirstActiveEvent = true;
+ let ignoreFirstActiveEvent = 2;
disposables.add(picker.onDidChangeActive(() => {
const [item] = picker.activeItems;
if (item && item.range) {
- if (ignoreFirstActiveEvent) {
- ignoreFirstActiveEvent = false;
+ if (ignoreFirstActiveEvent-- > 0) {
return;
}
diff --git a/src/vs/editor/contrib/snippet/browser/snippetController2.ts b/src/vs/editor/contrib/snippet/browser/snippetController2.ts
index 43e72f6ce57..862f3a20493 100644
--- a/src/vs/editor/contrib/snippet/browser/snippetController2.ts
+++ b/src/vs/editor/contrib/snippet/browser/snippetController2.ts
@@ -5,25 +5,26 @@
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { DisposableStore } from 'vs/base/common/lifecycle';
+import { assertType } from 'vs/base/common/types';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorCommand, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
-import { ISelection } from 'vs/editor/common/core/selection';
+import { ISelection, Selection } from 'vs/editor/common/core/selection';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { CompletionItem, CompletionItemKind, CompletionItemProvider } from 'vs/editor/common/languages';
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { ITextModel } from 'vs/editor/common/model';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
-import { Choice, SnippetParser } from 'vs/editor/contrib/snippet/browser/snippetParser';
+import { Choice } from 'vs/editor/contrib/snippet/browser/snippetParser';
import { showSimpleSuggestions } from 'vs/editor/contrib/suggest/browser/suggest';
import { OvertypingCapturer } from 'vs/editor/contrib/suggest/browser/suggestOvertypingCapturer';
import { localize } from 'vs/nls';
import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { ILogService } from 'vs/platform/log/common/log';
-import { SnippetSession } from './snippetSession';
+import { ISnippetEdit, SnippetSession } from './snippetSession';
export interface ISnippetInsertOptions {
overwriteBefore: number;
@@ -88,6 +89,19 @@ export class SnippetController2 implements IEditorContribution {
this._snippetListener.dispose();
}
+ apply(edits: ISnippetEdit[], opts?: Partial<ISnippetInsertOptions>) {
+ try {
+ this._doInsert(edits, typeof opts === 'undefined' ? _defaultOptions : { ..._defaultOptions, ...opts });
+
+ } catch (e) {
+ this.cancel();
+ this._logService.error(e);
+ this._logService.error('snippet_error');
+ this._logService.error('insert_edits=', edits);
+ this._logService.error('existing_template=', this._session ? this._session._logInfo() : '<no_session>');
+ }
+ }
+
insert(
template: string,
opts?: Partial<ISnippetInsertOptions>
@@ -108,7 +122,7 @@ export class SnippetController2 implements IEditorContribution {
}
private _doInsert(
- template: string,
+ template: string | ISnippetEdit[],
opts: ISnippetInsertOptions
): void {
if (!this._editor.hasModel()) {
@@ -123,11 +137,17 @@ export class SnippetController2 implements IEditorContribution {
this._editor.getModel().pushStackElement();
}
+ // don't merge
+ if (this._session && typeof template !== 'string') {
+ this.cancel();
+ }
+
if (!this._session) {
this._modelVersionId = this._editor.getModel().getAlternativeVersionId();
this._session = new SnippetSession(this._editor, template, opts, this._languageConfigurationService);
this._session.insert();
} else {
+ assertType(typeof template === 'string');
this._session.merge(template, opts);
}
@@ -342,50 +362,11 @@ export function performSnippetEdit(editor: ICodeEditor, snippet: string, selecti
return false;
}
editor.focus();
- editor.setSelections(selections ?? []);
- controller.insert(snippet);
- return controller.isInSnippet();
-}
-
-
-export type ISnippetEdit = {
- range: Range;
- snippet: string;
-};
-
-// ---
-
-export function performSnippetEdits(editor: ICodeEditor, edits: ISnippetEdit[]) {
-
- if (!editor.hasModel()) {
- return false;
- }
- if (edits.length === 0) {
- return false;
- }
-
- const model = editor.getModel();
- let newText = '';
- let last: ISnippetEdit | undefined;
- edits.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range));
-
- for (const item of edits) {
- if (last) {
- const between = Range.fromPositions(last.range.getEndPosition(), item.range.getStartPosition());
- const text = model.getValueInRange(between);
- newText += SnippetParser.escape(text);
- }
- newText += item.snippet;
- last = item;
- }
-
- const controller = SnippetController2.get(editor);
- if (!controller) {
- return false;
- }
- model.pushStackElement();
- const range = Range.plusRange(edits[0].range, edits[edits.length - 1].range);
- editor.setSelection(range);
- controller.insert(newText, { undoStopBefore: false });
+ controller.apply(selections.map(selection => {
+ return {
+ range: Selection.liftSelection(selection),
+ template: snippet
+ };
+ }));
return controller.isInSnippet();
}
diff --git a/src/vs/editor/contrib/snippet/browser/snippetParser.ts b/src/vs/editor/contrib/snippet/browser/snippetParser.ts
index 6faf65e0d4d..f68dcbfa953 100644
--- a/src/vs/editor/contrib/snippet/browser/snippetParser.ts
+++ b/src/vs/editor/contrib/snippet/browser/snippetParser.ts
@@ -613,11 +613,17 @@ export class SnippetParser {
}
parse(value: string, insertFinalTabstop?: boolean, enforceFinalTabstop?: boolean): TextmateSnippet {
+ const snippet = new TextmateSnippet();
+ this.parseFragment(value, snippet);
+ this.ensureFinalTabstop(snippet, enforceFinalTabstop ?? false, insertFinalTabstop ?? false);
+ return snippet;
+ }
+ parseFragment(value: string, snippet: TextmateSnippet): readonly Marker[] {
+
+ const offset = snippet.children.length;
this._scanner.text(value);
this._token = this._scanner.next();
-
- const snippet = new TextmateSnippet();
while (this._parse(snippet)) {
// nothing
}
@@ -626,10 +632,8 @@ export class SnippetParser {
// that has a value defines the value for all placeholders with that index
const placeholderDefaultValues = new Map<number, Marker[] | undefined>();
const incompletePlaceholders: Placeholder[] = [];
- let placeholderCount = 0;
snippet.walk(marker => {
if (marker instanceof Placeholder) {
- placeholderCount += 1;
if (marker.isFinalTabstop) {
placeholderDefaultValues.set(0, undefined);
} else if (!placeholderDefaultValues.has(marker.index) && marker.children.length > 0) {
@@ -640,6 +644,7 @@ export class SnippetParser {
}
return true;
});
+
for (const placeholder of incompletePlaceholders) {
const defaultValues = placeholderDefaultValues.get(placeholder.index);
if (defaultValues) {
@@ -652,17 +657,20 @@ export class SnippetParser {
}
}
- if (!enforceFinalTabstop) {
- enforceFinalTabstop = placeholderCount > 0 && insertFinalTabstop;
- }
+ return snippet.children.slice(offset);
+ }
- if (!placeholderDefaultValues.has(0) && enforceFinalTabstop) {
- // the snippet uses placeholders but has no
- // final tabstop defined -> insert at the end
- snippet.appendChild(new Placeholder(0));
+ ensureFinalTabstop(snippet: TextmateSnippet, enforceFinalTabstop: boolean, insertFinalTabstop: boolean) {
+
+ if (enforceFinalTabstop || insertFinalTabstop && snippet.placeholders.length > 0) {
+ const finalTabstop = snippet.placeholders.find(p => p.index === 0);
+ if (!finalTabstop) {
+ // the snippet uses placeholders but has no
+ // final tabstop defined -> insert at the end
+ snippet.appendChild(new Placeholder(0));
+ }
}
- return snippet;
}
private _accept(type?: TokenType): boolean;
diff --git a/src/vs/editor/contrib/snippet/browser/snippetSession.ts b/src/vs/editor/contrib/snippet/browser/snippetSession.ts
index 00e8268b0b6..74eaa5847f7 100644
--- a/src/vs/editor/contrib/snippet/browser/snippetSession.ts
+++ b/src/vs/editor/contrib/snippet/browser/snippetSession.ts
@@ -359,6 +359,11 @@ const _defaultOptions: ISnippetSessionInsertOptions = {
overtypingCapturer: undefined
};
+export interface ISnippetEdit {
+ range: Range;
+ template: string;
+}
+
export class SnippetSession {
static adjustWhitespace(model: ITextModel, position: IPosition, snippet: TextmateSnippet, adjustIndentation: boolean, adjustNewlines: boolean): string {
@@ -434,7 +439,7 @@ export class SnippetSession {
return selection;
}
- static createEditsAndSnippets(editor: IActiveCodeEditor, template: string, overwriteBefore: number, overwriteAfter: number, enforceFinalTabstop: boolean, adjustWhitespace: boolean, clipboardText: string | undefined, overtypingCapturer: OvertypingCapturer | undefined, languageConfigurationService: ILanguageConfigurationService): { edits: IIdentifiedSingleEditOperation[]; snippets: OneSnippet[] } {
+ static createEditsAndSnippetsFromSelections(editor: IActiveCodeEditor, template: string, overwriteBefore: number, overwriteAfter: number, enforceFinalTabstop: boolean, adjustWhitespace: boolean, clipboardText: string | undefined, overtypingCapturer: OvertypingCapturer | undefined, languageConfigurationService: ILanguageConfigurationService): { edits: IIdentifiedSingleEditOperation[]; snippets: OneSnippet[] } {
const edits: IIdentifiedSingleEditOperation[] = [];
const snippets: OneSnippet[] = [];
@@ -518,22 +523,79 @@ export class SnippetSession {
return { edits, snippets };
}
- private readonly _editor: IActiveCodeEditor;
- private readonly _template: string;
- private readonly _templateMerges: [number, number, string][] = [];
- private readonly _options: ISnippetSessionInsertOptions;
+ static createEditsAndSnippetsFromEdits(editor: IActiveCodeEditor, snippetEdits: ISnippetEdit[], enforceFinalTabstop: boolean, adjustWhitespace: boolean, clipboardText: string | undefined, overtypingCapturer: OvertypingCapturer | undefined, languageConfigurationService: ILanguageConfigurationService): { edits: IIdentifiedSingleEditOperation[]; snippets: OneSnippet[] } {
+
+ if (!editor.hasModel() || snippetEdits.length === 0) {
+ return { edits: [], snippets: [] };
+ }
+
+ const edits: IIdentifiedSingleEditOperation[] = [];
+ const model = editor.getModel();
+
+ const parser = new SnippetParser();
+ const snippet = new TextmateSnippet();
+
+ //
+ snippetEdits = snippetEdits.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range));
+ let offset = 0;
+ for (let i = 0; i < snippetEdits.length; i++) {
+
+ const { range, template } = snippetEdits[i];
+
+ // gaps between snippet edits are appended as text nodes. this
+ // ensures placeholder-offsets are later correct
+ if (i > 0) {
+ const lastRange = snippetEdits[i - 1].range;
+ const textRange = Range.fromPositions(lastRange.getEndPosition(), range.getStartPosition());
+ const textNode = new Text(model.getValueInRange(textRange));
+ snippet.appendChild(textNode);
+ offset += textNode.value.length;
+ }
+
+ parser.parseFragment(template, snippet);
+
+ const snippetText = snippet.toString();
+ const snippetFragmentText = snippetText.slice(offset);
+ offset = snippetText.length;
+
+ // make edit
+ const edit: IIdentifiedSingleEditOperation = EditOperation.replace(range, snippetFragmentText);
+ edit.identifier = { major: i, minor: 0 }; // mark the edit so only our undo edits will be used to generate end cursors
+ edit._isTracked = true;
+ edits.push(edit);
+ }
+
+ //
+ parser.ensureFinalTabstop(snippet, enforceFinalTabstop, true);
+
+ // snippet variables resolver
+ const resolver = new CompositeSnippetVariableResolver([
+ editor.invokeWithinContext(accessor => new ModelBasedVariableResolver(accessor.get(ILabelService), model)),
+ new ClipboardBasedVariableResolver(() => clipboardText, 0, editor.getSelections().length, editor.getOption(EditorOption.multiCursorPaste) === 'spread'),
+ new SelectionBasedVariableResolver(model, editor.getSelection(), 0, overtypingCapturer),
+ new CommentBasedVariableResolver(model, editor.getSelection(), languageConfigurationService),
+ new TimeBasedVariableResolver,
+ new WorkspaceBasedVariableResolver(editor.invokeWithinContext(accessor => accessor.get(IWorkspaceContextService))),
+ new RandomBasedVariableResolver,
+ ]);
+ snippet.resolveVariables(resolver);
+
+
+ return {
+ edits,
+ snippets: [new OneSnippet(editor, snippet, '')]
+ };
+ }
+
+ private readonly _templateMerges: [number, number, string | ISnippetEdit[]][] = [];
private _snippets: OneSnippet[] = [];
constructor(
- editor: IActiveCodeEditor,
- template: string,
- options: ISnippetSessionInsertOptions = _defaultOptions,
+ private readonly _editor: IActiveCodeEditor,
+ private readonly _template: string | ISnippetEdit[],
+ private readonly _options: ISnippetSessionInsertOptions = _defaultOptions,
@ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService
- ) {
- this._editor = editor;
- this._template = template;
- this._options = options;
- }
+ ) { }
dispose(): void {
dispose(this._snippets);
@@ -549,7 +611,10 @@ export class SnippetSession {
}
// make insert edit and start with first selections
- const { edits, snippets } = SnippetSession.createEditsAndSnippets(this._editor, this._template, this._options.overwriteBefore, this._options.overwriteAfter, false, this._options.adjustWhitespace, this._options.clipboardText, this._options.overtypingCapturer, this._languageConfigurationService);
+ const { edits, snippets } = typeof this._template === 'string'
+ ? SnippetSession.createEditsAndSnippetsFromSelections(this._editor, this._template, this._options.overwriteBefore, this._options.overwriteAfter, false, this._options.adjustWhitespace, this._options.clipboardText, this._options.overtypingCapturer, this._languageConfigurationService)
+ : SnippetSession.createEditsAndSnippetsFromEdits(this._editor, this._template, false, this._options.adjustWhitespace, this._options.clipboardText, this._options.overtypingCapturer, this._languageConfigurationService);
+
this._snippets = snippets;
this._editor.executeEdits('snippet', edits, _undoEdits => {
@@ -576,7 +641,7 @@ export class SnippetSession {
return;
}
this._templateMerges.push([this._snippets[0]._nestingLevel, this._snippets[0]._placeholderGroupsIdx, template]);
- const { edits, snippets } = SnippetSession.createEditsAndSnippets(this._editor, template, options.overwriteBefore, options.overwriteAfter, true, options.adjustWhitespace, options.clipboardText, options.overtypingCapturer, this._languageConfigurationService);
+ const { edits, snippets } = SnippetSession.createEditsAndSnippetsFromSelections(this._editor, template, options.overwriteBefore, options.overwriteAfter, true, options.adjustWhitespace, options.clipboardText, options.overtypingCapturer, this._languageConfigurationService);
this._editor.executeEdits('snippet', edits, _undoEdits => {
// Sometimes, the text buffer will remove automatic whitespace when doing any edits,
diff --git a/src/vs/editor/contrib/snippet/test/browser/snippetController2.test.ts b/src/vs/editor/contrib/snippet/test/browser/snippetController2.test.ts
index 3560a2c2be4..fce4d2c432b 100644
--- a/src/vs/editor/contrib/snippet/test/browser/snippetController2.test.ts
+++ b/src/vs/editor/contrib/snippet/test/browser/snippetController2.test.ts
@@ -7,6 +7,7 @@ import { mock } from 'vs/base/test/common/mock';
import { CoreEditingCommands } from 'vs/editor/browser/coreCommands';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { Selection } from 'vs/editor/common/core/selection';
+import { Range } from 'vs/editor/common/core/range';
import { Handler } from 'vs/editor/common/editorCommon';
import { TextModel } from 'vs/editor/common/model/textModel';
import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2';
@@ -23,6 +24,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace
suite('SnippetController2', function () {
+ /** @deprecated */
function assertSelections(editor: ICodeEditor, ...s: Selection[]) {
for (const selection of editor.getSelections()!) {
const actual = s.shift()!;
@@ -31,10 +33,20 @@ suite('SnippetController2', function () {
assert.strictEqual(s.length, 0);
}
+ /** @deprecated */
function assertContextKeys(service: MockContextKeyService, inSnippet: boolean, hasPrev: boolean, hasNext: boolean): void {
- assert.strictEqual(SnippetController2.InSnippetMode.getValue(service), inSnippet, `inSnippetMode`);
- assert.strictEqual(SnippetController2.HasPrevTabstop.getValue(service), hasPrev, `HasPrevTabstop`);
- assert.strictEqual(SnippetController2.HasNextTabstop.getValue(service), hasNext, `HasNextTabstop`);
+ const state = getContextState(service);
+ assert.strictEqual(state.inSnippet, inSnippet, `inSnippetMode`);
+ assert.strictEqual(state.hasPrev, hasPrev, `HasPrevTabstop`);
+ assert.strictEqual(state.hasNext, hasNext, `HasNextTabstop`);
+ }
+
+ function getContextState(service: MockContextKeyService = contextKeys) {
+ return {
+ inSnippet: SnippetController2.InSnippetMode.getValue(service),
+ hasPrev: SnippetController2.HasPrevTabstop.getValue(service),
+ hasNext: SnippetController2.HasNextTabstop.getValue(service),
+ };
}
let editor: ICodeEditor;
@@ -531,4 +543,153 @@ suite('SnippetController2', function () {
assert.strictEqual(model.getValue(), `foo: number;\n\nfoo: 'number',`);
// editor.trigger('test', 'type', { text: ';' });
});
+
+ suite('createEditsAndSnippetsFromEdits', function () {
+
+ test('apply, tab, done', function () {
+
+ const ctrl = instaService.createInstance(SnippetController2, editor);
+
+ model.setValue('foo("bar")');
+
+ ctrl.apply([
+ { range: new Range(1, 5, 1, 10), template: '$1' },
+ { range: new Range(1, 1, 1, 1), template: 'const ${1:new_const} = "bar";\n' }
+ ]);
+
+ assert.strictEqual(model.getValue(), "const new_const = \"bar\";\nfoo(new_const)");
+ assertContextKeys(contextKeys, true, false, true);
+ assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 7, 1, 16), new Selection(2, 5, 2, 14)]);
+
+ ctrl.next();
+ assertContextKeys(contextKeys, false, false, false);
+ assert.deepStrictEqual(editor.getSelections(), [new Selection(2, 14, 2, 14)]);
+ });
+
+ test('apply, tab, done with special final tabstop', function () {
+
+ model.setValue('foo("bar")');
+
+ const ctrl = instaService.createInstance(SnippetController2, editor);
+ ctrl.apply([
+ { range: new Range(1, 5, 1, 10), template: '$1' },
+ { range: new Range(1, 1, 1, 1), template: 'const ${1:new_const}$0 = "bar";\n' }
+ ]);
+
+ assert.strictEqual(model.getValue(), "const new_const = \"bar\";\nfoo(new_const)");
+ assertContextKeys(contextKeys, true, false, true);
+ assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 7, 1, 16), new Selection(2, 5, 2, 14)]);
+
+ ctrl.next();
+ assertContextKeys(contextKeys, false, false, false);
+ assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 16, 1, 16)]);
+ });
+
+ test('apply, tab, tab, done', function () {
+
+ model.setValue('foo\nbar');
+
+ const ctrl = instaService.createInstance(SnippetController2, editor);
+ ctrl.apply([
+ { range: new Range(1, 4, 1, 4), template: '${3}' },
+ { range: new Range(2, 4, 2, 4), template: '$3' },
+ { range: new Range(1, 1, 1, 1), template: '### ${2:Header}\n' }
+ ]);
+
+ assert.strictEqual(model.getValue(), "### Header\nfoo\nbar");
+ assert.deepStrictEqual(getContextState(), { inSnippet: true, hasPrev: false, hasNext: true });
+ assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 5, 1, 11)]);
+
+ ctrl.next();
+ assert.deepStrictEqual(getContextState(), { inSnippet: true, hasPrev: true, hasNext: true });
+ assert.deepStrictEqual(editor.getSelections(), [new Selection(2, 4, 2, 4), new Selection(3, 4, 3, 4)]);
+
+ ctrl.next();
+ assert.deepStrictEqual(getContextState(), { inSnippet: false, hasPrev: false, hasNext: false });
+ assert.deepStrictEqual(editor.getSelections(), [new Selection(3, 4, 3, 4)]);
+ });
+
+ test('nested into apply works', function () {
+
+ const ctrl = instaService.createInstance(SnippetController2, editor);
+ model.setValue('onetwo');
+
+ editor.setSelections([new Selection(1, 1, 1, 1), new Selection(2, 1, 2, 1)]);
+
+ ctrl.apply([{
+ range: new Range(1, 7, 1, 7),
+ template: '$0${1:three}'
+ }]);
+
+ assert.strictEqual(model.getValue(), 'onetwothree');
+ assert.deepStrictEqual(getContextState(), { inSnippet: true, hasPrev: false, hasNext: true });
+ assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 7, 1, 12)]);
+
+ ctrl.insert('foo$1bar$1');
+ assert.strictEqual(model.getValue(), 'onetwofoobar');
+ assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 10, 1, 10), new Selection(1, 13, 1, 13)]);
+ assert.deepStrictEqual(getContextState(), ({ inSnippet: true, hasPrev: false, hasNext: true }));
+
+ ctrl.next();
+ assert.deepStrictEqual(getContextState(), ({ inSnippet: true, hasPrev: true, hasNext: true }));
+ assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 13, 1, 13)]);
+
+ ctrl.next();
+ assert.deepStrictEqual(getContextState(), { inSnippet: false, hasPrev: false, hasNext: false });
+ assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 7, 1, 7)]);
+
+ });
+
+ test('nested into insert abort "outer" snippet', function () {
+
+ const ctrl = instaService.createInstance(SnippetController2, editor);
+ model.setValue('one\ntwo');
+
+ editor.setSelections([new Selection(1, 1, 1, 1), new Selection(2, 1, 2, 1)]);
+
+ ctrl.insert('foo${1:bar}bazz${1:bang}');
+ assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 4, 1, 7), new Selection(1, 11, 1, 14), new Selection(2, 4, 2, 7), new Selection(2, 11, 2, 14)]);
+ assert.deepStrictEqual(getContextState(), { inSnippet: true, hasPrev: false, hasNext: true });
+
+ ctrl.apply([{
+ range: new Range(1, 4, 1, 7),
+ template: '$0A'
+ }]);
+
+ assert.strictEqual(model.getValue(), 'fooAbazzbarone\nfoobarbazzbartwo');
+ assert.deepStrictEqual(getContextState(), { inSnippet: false, hasPrev: false, hasNext: false });
+ assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 4, 1, 4)]);
+ });
+
+ test('nested into "insert" abort "outer" snippet (2)', function () {
+
+ const ctrl = instaService.createInstance(SnippetController2, editor);
+ model.setValue('one\ntwo');
+
+ editor.setSelections([new Selection(1, 1, 1, 1), new Selection(2, 1, 2, 1)]);
+
+ ctrl.insert('foo${1:bar}bazz${1:bang}');
+ assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 4, 1, 7), new Selection(1, 11, 1, 14), new Selection(2, 4, 2, 7), new Selection(2, 11, 2, 14)]);
+ assert.deepStrictEqual(getContextState(), { inSnippet: true, hasPrev: false, hasNext: true });
+
+ const edits = [{
+ range: new Range(1, 4, 1, 7),
+ template: 'A'
+ }, {
+ range: new Range(1, 11, 1, 14),
+ template: 'B'
+ }, {
+ range: new Range(2, 4, 2, 7),
+ template: 'C'
+ }, {
+ range: new Range(2, 11, 2, 14),
+ template: 'D'
+ }];
+ ctrl.apply(edits);
+
+ assert.strictEqual(model.getValue(), "fooAbazzBone\nfooCbazzDtwo");
+ assert.deepStrictEqual(getContextState(), { inSnippet: false, hasPrev: false, hasNext: false });
+ assert.deepStrictEqual(editor.getSelections(), [new Selection(1, 5, 1, 5), new Selection(1, 10, 1, 10), new Selection(2, 5, 2, 5), new Selection(2, 10, 2, 10)]);
+ });
+ });
});
diff --git a/src/vs/editor/contrib/snippet/test/browser/snippetSession.test.ts b/src/vs/editor/contrib/snippet/test/browser/snippetSession.test.ts
index 7ce812e34ee..c9b7d0f2aa1 100644
--- a/src/vs/editor/contrib/snippet/test/browser/snippetSession.test.ts
+++ b/src/vs/editor/contrib/snippet/test/browser/snippetSession.test.ts
@@ -743,4 +743,36 @@ suite('SnippetSession', function () {
'}',
].join('\n'));
});
+
+
+ suite('createEditsAndSnippetsFromEdits', function () {
+
+ test('empty', function () {
+
+ const result = SnippetSession.createEditsAndSnippetsFromEdits(editor, [], true, true, undefined, undefined, languageConfigurationService);
+
+ assert.deepStrictEqual(result.edits, []);
+ assert.deepStrictEqual(result.snippets, []);
+ });
+
+ test('basic', function () {
+
+ editor.getModel().setValue('foo("bar")');
+
+ const result = SnippetSession.createEditsAndSnippetsFromEdits(
+ editor,
+ [{ range: new Range(1, 5, 1, 9), template: '$1' }, { range: new Range(1, 1, 1, 1), template: 'const ${1:new_const} = "bar"' }],
+ true, true, undefined, undefined, languageConfigurationService
+ );
+
+ assert.strictEqual(result.edits.length, 2);
+ assert.deepStrictEqual(result.edits[0].range, new Range(1, 1, 1, 1));
+ assert.deepStrictEqual(result.edits[0].text, 'const new_const = "bar"');
+ assert.deepStrictEqual(result.edits[1].range, new Range(1, 5, 1, 9));
+ assert.deepStrictEqual(result.edits[1].text, 'new_const');
+
+ assert.strictEqual(result.snippets.length, 1);
+ assert.strictEqual(result.snippets[0].isTrivialSnippet, false);
+ });
+ });
});
diff --git a/src/vs/editor/contrib/suggest/browser/suggest.ts b/src/vs/editor/contrib/suggest/browser/suggest.ts
index 59e61786894..805c58df531 100644
--- a/src/vs/editor/contrib/suggest/browser/suggest.ts
+++ b/src/vs/editor/contrib/suggest/browser/suggest.ts
@@ -31,6 +31,7 @@ import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes';
export const Context = {
Visible: historyNavigationVisible,
+ HasFocusedSuggestion: new RawContextKey<boolean>('suggestWidgetHasFocusedSuggestion', false, localize('suggestWidgetHasSelection', "Whether any suggestion is focused")),
DetailsVisible: new RawContextKey<boolean>('suggestWidgetDetailsVisible', false, localize('suggestWidgetDetailsVisible', "Whether suggestion details are visible")),
MultipleSuggestions: new RawContextKey<boolean>('suggestWidgetMultipleSuggestions', false, localize('suggestWidgetMultipleSuggestions', "Whether there are multiple suggestions to pick from")),
MakesTextEdit: new RawContextKey<boolean>('suggestionMakesTextEdit', true, localize('suggestionMakesTextEdit', "Whether inserting the current suggestion yields in a change or has everything already been typed")),
diff --git a/src/vs/editor/contrib/suggest/browser/suggestController.ts b/src/vs/editor/contrib/suggest/browser/suggestController.ts
index c3b80a8c5e9..14045fe6bec 100644
--- a/src/vs/editor/contrib/suggest/browser/suggestController.ts
+++ b/src/vs/editor/contrib/suggest/browser/suggestController.ts
@@ -226,8 +226,11 @@ export class SuggestController implements IEditorContribution {
this._lineSuffix.value = new LineSuffix(this.editor.getModel()!, e.position);
}));
this._toDispose.add(this.model.onDidSuggest(e => {
- if (!e.shy) {
- let index = -1;
+ if (e.shy) {
+ return;
+ }
+ let index = -1;
+ if (!e.noSelect) {
for (const selector of this._selectors.itemsOrderedByPriorityDesc) {
index = selector.select(this.editor.getModel()!, this.editor.getPosition()!, e.completionModel.items);
if (index !== -1) {
@@ -237,8 +240,8 @@ export class SuggestController implements IEditorContribution {
if (index === -1) {
index = this._memoryService.select(this.editor.getModel()!, this.editor.getPosition()!, e.completionModel.items);
}
- this.widget.value.showSuggestions(e.completionModel, index, e.isFrozen, e.auto);
}
+ this.widget.value.showSuggestions(e.completionModel, index, e.isFrozen, e.auto);
}));
this._toDispose.add(this.model.onDidCancel(e => {
if (!e.retrigger) {
@@ -400,7 +403,7 @@ export class SuggestController implements IEditorContribution {
} else if (item.completion.command.id === TriggerSuggestAction.id) {
// retigger
- this.model.trigger({ auto: true, shy: false }, true);
+ this.model.trigger({ auto: true, shy: false, noSelect: false }, true);
} else {
// exec command, done
@@ -494,9 +497,9 @@ export class SuggestController implements IEditorContribution {
}
}
- triggerSuggest(onlyFrom?: Set<CompletionItemProvider>, auto?: boolean, noFilter?: boolean): void {
+ triggerSuggest(onlyFrom?: Set<CompletionItemProvider>, auto?: boolean, noFilter?: boolean, noSelect?: boolean): void {
if (this.editor.hasModel()) {
- this.model.trigger({ auto: auto ?? false, shy: false }, false, onlyFrom, undefined, noFilter);
+ this.model.trigger({ auto: auto ?? false, shy: false, noSelect: noSelect ?? false }, false, onlyFrom, undefined, noFilter);
this.editor.revealPosition(this.editor.getPosition(), ScrollType.Smooth);
this.editor.focus();
}
@@ -565,7 +568,7 @@ export class SuggestController implements IEditorContribution {
}, undefined, listener);
});
- this.model.trigger({ auto: false, shy: true });
+ this.model.trigger({ auto: false, shy: true, noSelect: false });
this.editor.revealPosition(positionNow, ScrollType.Smooth);
this.editor.focus();
}
@@ -706,15 +709,19 @@ export class TriggerSuggestAction extends EditorAction {
return;
}
- type TriggerArgs = { auto: boolean };
+ type TriggerArgs = { auto: boolean; noSelection: boolean };
let auto: boolean | undefined;
+ let noSelect: boolean | undefined;
if (args && typeof args === 'object') {
if ((<TriggerArgs>args).auto === true) {
auto = true;
}
+ if ((<TriggerArgs>args).noSelection === true) {
+ noSelect = true;
+ }
}
- controller.triggerSuggest(undefined, auto);
+ controller.triggerSuggest(undefined, auto, undefined, noSelect);
}
}
@@ -728,7 +735,7 @@ const SuggestCommand = EditorCommand.bindToContribution<SuggestController>(Sugge
registerEditorCommand(new SuggestCommand({
id: 'acceptSelectedSuggestion',
- precondition: SuggestContext.Visible,
+ precondition: ContextKeyExpr.and(SuggestContext.Visible, SuggestContext.HasFocusedSuggestion),
handler(x) {
x.acceptSelectedSuggestion(true, false);
},
@@ -766,7 +773,7 @@ registerEditorCommand(new SuggestCommand({
registerEditorCommand(new SuggestCommand({
id: 'acceptAlternativeSelectedSuggestion',
- precondition: ContextKeyExpr.and(SuggestContext.Visible, EditorContextKeys.textInputFocus),
+ precondition: ContextKeyExpr.and(SuggestContext.Visible, EditorContextKeys.textInputFocus, SuggestContext.HasFocusedSuggestion),
kbOpts: {
weight: weight,
kbExpr: EditorContextKeys.textInputFocus,
diff --git a/src/vs/editor/contrib/suggest/browser/suggestModel.ts b/src/vs/editor/contrib/suggest/browser/suggestModel.ts
index 62f7bebafe1..7159e6a10ec 100644
--- a/src/vs/editor/contrib/suggest/browser/suggestModel.ts
+++ b/src/vs/editor/contrib/suggest/browser/suggestModel.ts
@@ -44,11 +44,13 @@ export interface ISuggestEvent {
readonly isFrozen: boolean;
readonly auto: boolean;
readonly shy: boolean;
+ readonly noSelect: boolean;
}
export interface SuggestTriggerContext {
readonly auto: boolean;
readonly shy: boolean;
+ readonly noSelect: boolean;
readonly triggerKind?: CompletionTriggerKind;
readonly triggerCharacter?: string;
}
@@ -82,14 +84,16 @@ export class LineContext {
readonly leadingWord: IWordAtPosition;
readonly auto: boolean;
readonly shy: boolean;
+ readonly noSelect: boolean;
- constructor(model: ITextModel, position: Position, auto: boolean, shy: boolean) {
+ constructor(model: ITextModel, position: Position, auto: boolean, shy: boolean, noSelect: boolean) {
this.leadingLineContent = model.getLineContent(position.lineNumber).substr(0, position.column - 1);
this.leadingWord = model.getWordUntilPosition(position);
this.lineNumber = position.lineNumber;
this.column = position.column;
this.auto = auto;
this.shy = shy;
+ this.noSelect = noSelect;
}
}
@@ -279,7 +283,7 @@ export class SuggestModel implements IDisposable {
const existing = this._completionModel
? { items: this._completionModel.adopt(supports), clipboardText: this._completionModel.clipboardText }
: undefined;
- this.trigger({ auto: true, shy: false, triggerCharacter: lastChar }, Boolean(this._completionModel), supports, existing);
+ this.trigger({ auto: true, shy: false, noSelect: false, triggerCharacter: lastChar }, Boolean(this._completionModel), supports, existing);
}
};
@@ -314,7 +318,7 @@ export class SuggestModel implements IDisposable {
if (!this._editor.hasModel() || !this._languageFeaturesService.completionProvider.has(this._editor.getModel())) {
this.cancel();
} else {
- this.trigger({ auto: this._state === State.Auto, shy: false }, true);
+ this.trigger({ auto: this._state === State.Auto, shy: false, noSelect: false }, true);
}
}
}
@@ -413,7 +417,7 @@ export class SuggestModel implements IDisposable {
}
// we made it till here -> trigger now
- this.trigger({ auto: true, shy: false });
+ this.trigger({ auto: true, shy: false, noSelect: false });
}, this._editor.getOption(EditorOption.quickSuggestionsDelay));
}
@@ -433,7 +437,7 @@ export class SuggestModel implements IDisposable {
}
const model = this._editor.getModel();
const position = this._editor.getPosition();
- const ctx = new LineContext(model, position, this._state === State.Auto, false);
+ const ctx = new LineContext(model, position, this._state === State.Auto, false, false);
this._onNewContext(ctx);
});
}
@@ -445,7 +449,7 @@ export class SuggestModel implements IDisposable {
const model = this._editor.getModel();
const auto = context.auto;
- const ctx = new LineContext(model, this._editor.getPosition(), auto, context.shy);
+ const ctx = new LineContext(model, this._editor.getPosition(), auto, context.shy, context.noSelect);
// Cancel previous requests, change state & update UI
this.cancel(retrigger);
@@ -520,7 +524,7 @@ export class SuggestModel implements IDisposable {
items = items.concat(existing.items).sort(cmpFn);
}
- const ctx = new LineContext(model, this._editor.getPosition(), auto, context.shy);
+ const ctx = new LineContext(model, this._editor.getPosition(), auto, context.shy, context.noSelect);
this._completionModel = new CompletionModel(items, this._context!.column, {
leadingLineContent: ctx.leadingLineContent,
characterCountDelta: ctx.column - this._context!.column
@@ -630,7 +634,7 @@ export class SuggestModel implements IDisposable {
if (ctx.column < this._context.column) {
// typed -> moved cursor LEFT -> retrigger if still on a word
if (ctx.leadingWord.word) {
- this.trigger({ auto: this._context.auto, shy: false }, true);
+ this.trigger({ auto: this._context.auto, shy: false, noSelect: false }, true);
} else {
this.cancel();
}
@@ -652,7 +656,7 @@ export class SuggestModel implements IDisposable {
inactiveProvider.delete(provider);
}
const items = this._completionModel.adopt(new Set());
- this.trigger({ auto: this._context.auto, shy: false }, true, inactiveProvider, { items, clipboardText: this._completionModel.clipboardText });
+ this.trigger({ auto: this._context.auto, shy: false, noSelect: false }, true, inactiveProvider, { items, clipboardText: this._completionModel.clipboardText });
return;
}
@@ -660,7 +664,7 @@ export class SuggestModel implements IDisposable {
// typed -> moved cursor RIGHT & incomple model & still on a word -> retrigger
const { incomplete } = this._completionModel;
const items = this._completionModel.adopt(incomplete);
- this.trigger({ auto: this._state === State.Auto, shy: false, triggerKind: CompletionTriggerKind.TriggerForIncompleteCompletions }, true, incomplete, { items, clipboardText: this._completionModel.clipboardText });
+ this.trigger({ auto: this._state === State.Auto, shy: false, noSelect: false, triggerKind: CompletionTriggerKind.TriggerForIncompleteCompletions }, true, incomplete, { items, clipboardText: this._completionModel.clipboardText });
} else {
// typed -> moved cursor RIGHT -> update UI
@@ -676,7 +680,7 @@ export class SuggestModel implements IDisposable {
if (LineContext.shouldAutoTrigger(this._editor) && this._context.leadingWord.endColumn < ctx.leadingWord.startColumn) {
// retrigger when heading into a new word
- this.trigger({ auto: this._context.auto, shy: false }, true);
+ this.trigger({ auto: this._context.auto, shy: false, noSelect: false }, true);
return;
}
@@ -703,6 +707,7 @@ export class SuggestModel implements IDisposable {
completionModel: this._completionModel,
auto: this._context.auto,
shy: this._context.shy,
+ noSelect: this._context.noSelect,
isFrozen,
});
}
diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts
index 3a03d2da7dd..cd32e70c678 100644
--- a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts
+++ b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts
@@ -30,7 +30,7 @@ import { attachListStyler } from 'vs/platform/theme/common/styler';
import { isHighContrast } from 'vs/platform/theme/common/theme';
import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService';
import { CompletionModel } from './completionModel';
-import { ResizableHTMLElement } from './resizable';
+import { ResizableHTMLElement } from 'vs/base/browser/ui/resizable/resizable';
import { CompletionItem, Context as SuggestContext } from './suggest';
import { canExpandCompletionItem, SuggestDetailsOverlay, SuggestDetailsWidget } from './suggestWidgetDetails';
import { getAriaId, ItemRenderer } from './suggestWidgetRenderer';
@@ -124,6 +124,7 @@ export class SuggestWidget implements IDisposable {
private readonly _ctxSuggestWidgetVisible: IContextKey<boolean>;
private readonly _ctxSuggestWidgetDetailsVisible: IContextKey<boolean>;
private readonly _ctxSuggestWidgetMultipleSuggestions: IContextKey<boolean>;
+ private readonly _ctxSuggestWidgetHasFocusedSuggestion: IContextKey<boolean>;
private readonly _showTimeout = new TimeoutTimer();
private readonly _disposables = new DisposableStore();
@@ -283,7 +284,7 @@ export class SuggestWidget implements IDisposable {
this._ctxSuggestWidgetVisible = SuggestContext.Visible.bindTo(_contextKeyService);
this._ctxSuggestWidgetDetailsVisible = SuggestContext.DetailsVisible.bindTo(_contextKeyService);
this._ctxSuggestWidgetMultipleSuggestions = SuggestContext.MultipleSuggestions.bindTo(_contextKeyService);
-
+ this._ctxSuggestWidgetHasFocusedSuggestion = SuggestContext.HasFocusedSuggestion.bindTo(_contextKeyService);
this._disposables.add(dom.addStandardDisposableListener(this._details.widget.domNode, 'keydown', e => {
this._onDetailsKeydown.fire(e);
@@ -365,6 +366,7 @@ export class SuggestWidget implements IDisposable {
}
this.editor.setAriaOptions({ activeDescendant: undefined });
+ this._ctxSuggestWidgetHasFocusedSuggestion.set(false);
return;
}
@@ -372,6 +374,7 @@ export class SuggestWidget implements IDisposable {
return;
}
+ this._ctxSuggestWidgetHasFocusedSuggestion.set(true);
const item = e.elements[0];
const index = e.indexes[0];
@@ -440,6 +443,7 @@ export class SuggestWidget implements IDisposable {
this._contentWidget.hide();
this._ctxSuggestWidgetVisible.reset();
this._ctxSuggestWidgetMultipleSuggestions.reset();
+ this._ctxSuggestWidgetHasFocusedSuggestion.reset();
this._showTimeout.cancel();
this.element.domNode.classList.remove('visible');
this._list.splice(0, this._list.length);
@@ -538,8 +542,10 @@ export class SuggestWidget implements IDisposable {
this._focusedItem = undefined;
this._list.splice(0, this._list.length, this._completionModel.items);
this._setState(isFrozen ? State.Frozen : State.Open);
- this._list.reveal(selectionIndex, 0);
- this._list.setFocus([selectionIndex]);
+ if (selectionIndex >= 0) {
+ this._list.reveal(selectionIndex, 0);
+ this._list.setFocus([selectionIndex]);
+ }
this._layout(this.element.size);
// Reset focus border
diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts b/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts
index e9d6c4c5130..cd6e10771c0 100644
--- a/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts
+++ b/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts
@@ -12,7 +12,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle';
import { MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer';
import { ICodeEditor, IOverlayWidget } from 'vs/editor/browser/editorBrowser';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
-import { ResizableHTMLElement } from 'vs/editor/contrib/suggest/browser/resizable';
+import { ResizableHTMLElement } from 'vs/base/browser/ui/resizable/resizable';
import * as nls from 'vs/nls';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { CompletionItem } from './suggest';
diff --git a/src/vs/editor/contrib/suggest/test/browser/suggestModel.test.ts b/src/vs/editor/contrib/suggest/test/browser/suggestModel.test.ts
index f25e0b22040..0a1b3dda2de 100644
--- a/src/vs/editor/contrib/suggest/test/browser/suggestModel.test.ts
+++ b/src/vs/editor/contrib/suggest/test/browser/suggestModel.test.ts
@@ -253,7 +253,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
return Promise.all([
assertEvent(model.onDidTrigger, function () {
- model.trigger({ auto: true, shy: false });
+ model.trigger({ auto: true, shy: false, noSelect: false });
}, function (event) {
assert.strictEqual(event.auto, true);
@@ -265,13 +265,13 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
}),
assertEvent(model.onDidTrigger, function () {
- model.trigger({ auto: true, shy: false });
+ model.trigger({ auto: true, shy: false, noSelect: false });
}, function (event) {
assert.strictEqual(event.auto, true);
}),
assertEvent(model.onDidTrigger, function () {
- model.trigger({ auto: false, shy: false });
+ model.trigger({ auto: false, shy: false, noSelect: false });
}, function (event) {
assert.strictEqual(event.auto, false);
})
@@ -287,12 +287,12 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
return withOracle(model => {
return Promise.all([
assertEvent(model.onDidCancel, function () {
- model.trigger({ auto: true, shy: false });
+ model.trigger({ auto: true, shy: false, noSelect: false });
}, function (event) {
assert.strictEqual(event.retrigger, false);
}),
assertEvent(model.onDidSuggest, function () {
- model.trigger({ auto: false, shy: false });
+ model.trigger({ auto: false, shy: false, noSelect: false });
}, function (event) {
assert.strictEqual(event.auto, false);
assert.strictEqual(event.isFrozen, false);
@@ -343,7 +343,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
return assertEvent(model.onDidSuggest, () => {
// make sure completionModel starts here!
- model.trigger({ auto: true, shy: false });
+ model.trigger({ auto: true, shy: false, noSelect: false });
}, event => {
return assertEvent(model.onDidSuggest, () => {
@@ -443,7 +443,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
editor.setPosition({ lineNumber: 1, column: 3 });
return assertEvent(model.onDidSuggest, () => {
- model.trigger({ auto: false, shy: false });
+ model.trigger({ auto: false, shy: false, noSelect: false });
}, event => {
assert.strictEqual(event.auto, false);
assert.strictEqual(event.isFrozen, false);
@@ -468,7 +468,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
editor.setPosition({ lineNumber: 1, column: 3 });
return assertEvent(model.onDidSuggest, () => {
- model.trigger({ auto: false, shy: false });
+ model.trigger({ auto: false, shy: false, noSelect: false });
}, event => {
assert.strictEqual(event.auto, false);
assert.strictEqual(event.isFrozen, false);
@@ -505,7 +505,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
editor.setPosition({ lineNumber: 1, column: 4 });
return assertEvent(model.onDidSuggest, () => {
- model.trigger({ auto: false, shy: false });
+ model.trigger({ auto: false, shy: false, noSelect: false });
}, event => {
assert.strictEqual(event.auto, false);
assert.strictEqual(event.completionModel.incomplete.size, 1);
@@ -542,7 +542,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
editor.setPosition({ lineNumber: 1, column: 4 });
return assertEvent(model.onDidSuggest, () => {
- model.trigger({ auto: false, shy: false });
+ model.trigger({ auto: false, shy: false, noSelect: false });
}, event => {
assert.strictEqual(event.auto, false);
assert.strictEqual(event.completionModel.incomplete.size, 1);
@@ -701,7 +701,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
await assertEvent(sugget.onDidSuggest, () => {
editor.setPosition({ lineNumber: 1, column: 3 });
- sugget.trigger({ auto: false, shy: false });
+ sugget.trigger({ auto: false, shy: false, noSelect: false });
}, event => {
assert.strictEqual(event.completionModel.items.length, 1);
@@ -928,4 +928,19 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
});
});
});
+
+ test('noSelect-flag makes it from request to suggest event', function () {
+
+ disposables.add(registry.register({ scheme: 'test' }, alwaysSomethingSupport));
+
+ return withOracle((model, editor) => {
+ return assertEvent(model.onDidSuggest, () => {
+ model.trigger({ auto: false, noSelect: true, shy: false });
+ }, event => {
+ assert.strictEqual(event.noSelect, true);
+ assert.strictEqual(event.auto, false);
+ assert.strictEqual(event.shy, false);
+ });
+ });
+ });
});
diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditorService.ts b/src/vs/editor/standalone/browser/standaloneCodeEditorService.ts
index af167ba519d..2f9e648d52d 100644
--- a/src/vs/editor/standalone/browser/standaloneCodeEditorService.ts
+++ b/src/vs/editor/standalone/browser/standaloneCodeEditorService.ts
@@ -13,7 +13,7 @@ import { IRange } from 'vs/editor/common/core/range';
import { ScrollType } from 'vs/editor/common/editorCommon';
import { ITextModel } from 'vs/editor/common/model';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
-import { IResourceEditorInput, ITextResourceEditorInput } from 'vs/platform/editor/common/editor';
+import { ITextResourceEditorInput } from 'vs/platform/editor/common/editor';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IThemeService } from 'vs/platform/theme/common/themeService';
@@ -31,6 +31,13 @@ export class StandaloneCodeEditorService extends AbstractCodeEditorService {
this.onCodeEditorRemove(() => this._checkContextKey());
this._editorIsOpen = contextKeyService.createKey('editorIsOpen', false);
this._activeCodeEditor = null;
+
+ this.registerCodeEditorOpenHandler(async (input, source, sideBySide) => {
+ if (!source) {
+ return null;
+ }
+ return this.doOpenEditor(source, input);
+ });
}
private _checkContextKey(): void {
@@ -52,13 +59,6 @@ export class StandaloneCodeEditorService extends AbstractCodeEditorService {
return this._activeCodeEditor;
}
- public openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null> {
- if (!source) {
- return Promise.resolve(null);
- }
-
- return Promise.resolve(this.doOpenEditor(source, input));
- }
private doOpenEditor(editor: ICodeEditor, input: ITextResourceEditorInput): ICodeEditor | null {
const model = this.findModel(editor, input.resource);
diff --git a/src/vs/editor/standalone/browser/standaloneThemeService.ts b/src/vs/editor/standalone/browser/standaloneThemeService.ts
index 4690b299892..1366682b48f 100644
--- a/src/vs/editor/standalone/browser/standaloneThemeService.ts
+++ b/src/vs/editor/standalone/browser/standaloneThemeService.ts
@@ -17,13 +17,13 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { asCssVariableName, ColorIdentifier, Extensions, IColorRegistry } from 'vs/platform/theme/common/colorRegistry';
import { Extensions as ThemingExtensions, ICssStyleCollector, IFileIconTheme, IProductIconTheme, IThemingRegistry, ITokenStyle } from 'vs/platform/theme/common/themeService';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
-import { ColorScheme, isDark } from 'vs/platform/theme/common/theme';
+import { ColorScheme, isDark, isHighContrast } from 'vs/platform/theme/common/theme';
import { getIconsStyleSheet, UnthemedProductIconTheme } from 'vs/platform/theme/browser/iconsStyleSheet';
-const VS_THEME_NAME = 'vs';
-const VS_DARK_THEME_NAME = 'vs-dark';
-const HC_BLACK_THEME_NAME = 'hc-black';
-const HC_LIGHT_THEME_NAME = 'hc-light';
+export const VS_LIGHT_THEME_NAME = 'vs';
+export const VS_DARK_THEME_NAME = 'vs-dark';
+export const HC_BLACK_THEME_NAME = 'hc-black';
+export const HC_LIGHT_THEME_NAME = 'hc-light';
const colorRegistry = Registry.as<IColorRegistry>(Extensions.ColorContribution);
const themingRegistry = Registry.as<IThemingRegistry>(ThemingExtensions.ThemingContribution);
@@ -118,7 +118,7 @@ class StandaloneTheme implements IStandaloneTheme {
public get type(): ColorScheme {
switch (this.base) {
- case VS_THEME_NAME: return ColorScheme.LIGHT;
+ case VS_LIGHT_THEME_NAME: return ColorScheme.LIGHT;
case HC_BLACK_THEME_NAME: return ColorScheme.HIGH_CONTRAST_DARK;
case HC_LIGHT_THEME_NAME: return ColorScheme.HIGH_CONTRAST_LIGHT;
default: return ColorScheme.DARK;
@@ -182,7 +182,7 @@ class StandaloneTheme implements IStandaloneTheme {
function isBuiltinTheme(themeName: string): themeName is BuiltinTheme {
return (
- themeName === VS_THEME_NAME
+ themeName === VS_LIGHT_THEME_NAME
|| themeName === VS_DARK_THEME_NAME
|| themeName === HC_BLACK_THEME_NAME
|| themeName === HC_LIGHT_THEME_NAME
@@ -191,7 +191,7 @@ function isBuiltinTheme(themeName: string): themeName is BuiltinTheme {
function getBuiltinRules(builtinTheme: BuiltinTheme): IStandaloneThemeData {
switch (builtinTheme) {
- case VS_THEME_NAME:
+ case VS_LIGHT_THEME_NAME:
return vs;
case VS_DARK_THEME_NAME:
return vs_dark;
@@ -229,7 +229,6 @@ export class StandaloneThemeService extends Disposable implements IStandaloneThe
private _globalStyleElement: HTMLStyleElement | null;
private _styleElements: HTMLStyleElement[];
private _colorMapOverride: Color[] | null;
- private _desiredTheme!: IStandaloneTheme;
private _theme!: IStandaloneTheme;
private _builtInProductIconTheme = new UnthemedProductIconTheme();
@@ -240,7 +239,7 @@ export class StandaloneThemeService extends Disposable implements IStandaloneThe
this._autoDetectHighContrast = true;
this._knownThemes = new Map<string, StandaloneTheme>();
- this._knownThemes.set(VS_THEME_NAME, newBuiltInTheme(VS_THEME_NAME));
+ this._knownThemes.set(VS_LIGHT_THEME_NAME, newBuiltInTheme(VS_LIGHT_THEME_NAME));
this._knownThemes.set(VS_DARK_THEME_NAME, newBuiltInTheme(VS_DARK_THEME_NAME));
this._knownThemes.set(HC_BLACK_THEME_NAME, newBuiltInTheme(HC_BLACK_THEME_NAME));
this._knownThemes.set(HC_LIGHT_THEME_NAME, newBuiltInTheme(HC_LIGHT_THEME_NAME));
@@ -253,7 +252,8 @@ export class StandaloneThemeService extends Disposable implements IStandaloneThe
this._globalStyleElement = null;
this._styleElements = [];
this._colorMapOverride = null;
- this.setTheme(VS_THEME_NAME);
+ this.setTheme(VS_LIGHT_THEME_NAME);
+ this._onOSSchemeChanged();
iconsStyleSheet.onDidChange(() => {
this._codiconCSS = iconsStyleSheet.getCSS();
@@ -261,7 +261,7 @@ export class StandaloneThemeService extends Disposable implements IStandaloneThe
});
addMatchMediaChangeListener('(forced-colors: active)', () => {
- this._updateActualTheme();
+ this._onOSSchemeChanged();
});
}
@@ -331,41 +331,43 @@ export class StandaloneThemeService extends Disposable implements IStandaloneThe
}
public setTheme(themeName: string): void {
- let theme: StandaloneTheme;
+ let theme: StandaloneTheme | undefined;
if (this._knownThemes.has(themeName)) {
- theme = this._knownThemes.get(themeName)!;
+ theme = this._knownThemes.get(themeName);
} else {
- theme = this._knownThemes.get(VS_THEME_NAME)!;
+ theme = this._knownThemes.get(VS_LIGHT_THEME_NAME);
}
- this._desiredTheme = theme;
- this._updateActualTheme();
+ this._updateActualTheme(theme);
}
- private getHighContrastTheme() {
- if (isDark(this._desiredTheme.type)) {
- return HC_BLACK_THEME_NAME;
- } else {
- return HC_LIGHT_THEME_NAME;
- }
- }
-
- private _updateActualTheme(): void {
- const theme = (
- this._autoDetectHighContrast && window.matchMedia(`(forced-colors: active)`).matches
- ? this._knownThemes.get(this.getHighContrastTheme())!
- : this._desiredTheme
- );
- if (this._theme === theme) {
+ private _updateActualTheme(desiredTheme: IStandaloneTheme | undefined): void {
+ if (!desiredTheme || this._theme === desiredTheme) {
// Nothing to do
return;
}
- this._theme = theme;
+ this._theme = desiredTheme;
this._updateThemeOrColorMap();
}
+ private _onOSSchemeChanged() {
+ if (this._autoDetectHighContrast) {
+ const wantsHighContrast = window.matchMedia(`(forced-colors: active)`).matches;
+ if (wantsHighContrast !== isHighContrast(this._theme.type)) {
+ // switch to high contrast or non-high contrast but stick to dark or light
+ let newThemeName;
+ if (isDark(this._theme.type)) {
+ newThemeName = wantsHighContrast ? HC_BLACK_THEME_NAME : VS_DARK_THEME_NAME;
+ } else {
+ newThemeName = wantsHighContrast ? HC_LIGHT_THEME_NAME : VS_LIGHT_THEME_NAME;
+ }
+ this._updateActualTheme(this._knownThemes.get(newThemeName));
+ }
+ }
+ }
+
public setAutoDetectHighContrast(autoDetectHighContrast: boolean): void {
this._autoDetectHighContrast = autoDetectHighContrast;
- this._updateActualTheme();
+ this._onOSSchemeChanged();
}
private _updateThemeOrColorMap(): void {
diff --git a/src/vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast.ts b/src/vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast.ts
index d10e1ccafb9..4fe1552ebd5 100644
--- a/src/vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast.ts
+++ b/src/vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast.ts
@@ -7,7 +7,8 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorAction, ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions';
import { IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneTheme';
import { ToggleHighContrastNLS } from 'vs/editor/common/standaloneStrings';
-import { isHighContrast } from 'vs/platform/theme/common/theme';
+import { isDark, isHighContrast } from 'vs/platform/theme/common/theme';
+import { HC_BLACK_THEME_NAME, HC_LIGHT_THEME_NAME, VS_DARK_THEME_NAME, VS_LIGHT_THEME_NAME } from 'vs/editor/standalone/browser/standaloneThemeService';
class ToggleHighContrast extends EditorAction {
@@ -25,13 +26,14 @@ class ToggleHighContrast extends EditorAction {
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
const standaloneThemeService = accessor.get(IStandaloneThemeService);
- if (isHighContrast(standaloneThemeService.getColorTheme().type)) {
+ const currentTheme = standaloneThemeService.getColorTheme();
+ if (isHighContrast(currentTheme.type)) {
// We must toggle back to the integrator's theme
- standaloneThemeService.setTheme(this._originalThemeName || 'vs');
+ standaloneThemeService.setTheme(this._originalThemeName || (isDark(currentTheme.type) ? VS_DARK_THEME_NAME : VS_LIGHT_THEME_NAME));
this._originalThemeName = null;
} else {
- this._originalThemeName = standaloneThemeService.getColorTheme().themeName;
- standaloneThemeService.setTheme('hc-black');
+ standaloneThemeService.setTheme(isDark(currentTheme.type) ? HC_BLACK_THEME_NAME : HC_LIGHT_THEME_NAME);
+ this._originalThemeName = currentTheme.themeName;
}
}
}
diff --git a/src/vs/editor/test/browser/editorTestServices.ts b/src/vs/editor/test/browser/editorTestServices.ts
index 6eb1863c26e..3aad1a86547 100644
--- a/src/vs/editor/test/browser/editorTestServices.ts
+++ b/src/vs/editor/test/browser/editorTestServices.ts
@@ -22,7 +22,7 @@ export class TestCodeEditorService extends AbstractCodeEditorService {
return null;
}
public lastInput?: IResourceEditorInput;
- openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null> {
+ override openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null> {
this.lastInput = input;
return Promise.resolve(null);
}
diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts
index 713083bfe0c..f608381f9e4 100644
--- a/src/vs/monaco.d.ts
+++ b/src/vs/monaco.d.ts
@@ -3325,7 +3325,7 @@ declare namespace monaco.editor {
* Controls whether the fold actions in the gutter stay always visible or hide unless the mouse is over the gutter.
* Defaults to 'mouseover'.
*/
- showFoldingControls?: 'always' | 'mouseover';
+ showFoldingControls?: 'always' | 'never' | 'mouseover';
/**
* Controls whether clicking on the empty content after a folded line will unfold the line.
* Defaults to false.
@@ -4539,7 +4539,7 @@ declare namespace monaco.editor {
selectionClipboard: IEditorOption<EditorOption.selectionClipboard, boolean>;
selectionHighlight: IEditorOption<EditorOption.selectionHighlight, boolean>;
selectOnLineNumbers: IEditorOption<EditorOption.selectOnLineNumbers, boolean>;
- showFoldingControls: IEditorOption<EditorOption.showFoldingControls, 'always' | 'mouseover'>;
+ showFoldingControls: IEditorOption<EditorOption.showFoldingControls, 'always' | 'never' | 'mouseover'>;
showUnused: IEditorOption<EditorOption.showUnused, boolean>;
showDeprecated: IEditorOption<EditorOption.showDeprecated, boolean>;
inlayHints: IEditorOption<EditorOption.inlayHints, Readonly<Required<IEditorInlayHintsOptions>>>;
@@ -7015,22 +7015,24 @@ declare namespace monaco.languages {
maxSize?: number;
}
- export interface WorkspaceFileEdit {
- oldUri?: Uri;
- newUri?: Uri;
+ export interface IWorkspaceFileEdit {
+ oldResource?: Uri;
+ newResource?: Uri;
options?: WorkspaceFileEditOptions;
metadata?: WorkspaceEditMetadata;
}
- export interface WorkspaceTextEdit {
+ export interface IWorkspaceTextEdit {
resource: Uri;
- edit: TextEdit;
- modelVersionId?: number;
+ textEdit: TextEdit & {
+ insertAsSnippet?: boolean;
+ };
+ versionId: number | undefined;
metadata?: WorkspaceEditMetadata;
}
export interface WorkspaceEdit {
- edits: Array<WorkspaceTextEdit | WorkspaceFileEdit>;
+ edits: Array<IWorkspaceTextEdit | IWorkspaceFileEdit>;
}
export interface Rejection {
diff --git a/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts b/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts
index 64030e42b01..ec2a105e9cb 100644
--- a/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts
+++ b/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts
@@ -3,7 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { IContextMenuProvider } from 'vs/base/browser/contextmenu';
import * as DOM from 'vs/base/browser/dom';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { ActionViewItem, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
@@ -18,6 +17,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IThemeService } from 'vs/platform/theme/common/themeService';
+import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
export interface IDropdownWithPrimaryActionViewItemOptions {
getKeyBinding?: (action: IAction) => ResolvedKeybinding | undefined;
@@ -38,7 +38,7 @@ export class DropdownWithPrimaryActionViewItem extends BaseActionViewItem {
dropdownAction: IAction,
dropdownMenuActions: IAction[],
className: string,
- private readonly _contextMenuProvider: IContextMenuProvider,
+ private readonly _contextMenuProvider: IContextMenuService,
private readonly _options: IDropdownWithPrimaryActionViewItemOptions | undefined,
@IKeybindingService _keybindingService: IKeybindingService,
@INotificationService _notificationService: INotificationService,
@@ -46,7 +46,7 @@ export class DropdownWithPrimaryActionViewItem extends BaseActionViewItem {
@IThemeService _themeService: IThemeService
) {
super(null, primaryAction);
- this._primaryAction = new MenuEntryActionViewItem(primaryAction, undefined, _keybindingService, _notificationService, _contextKeyService, _themeService);
+ this._primaryAction = new MenuEntryActionViewItem(primaryAction, undefined, _keybindingService, _notificationService, _contextKeyService, _themeService, _contextMenuProvider);
this._dropdown = new DropdownMenuActionViewItem(dropdownAction, dropdownMenuActions, this._contextMenuProvider, {
menuAsChild: true,
classNames: ['codicon', 'codicon-chevron-down'],
diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts
index 8572310e90c..a5f2338647d 100644
--- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts
+++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts
@@ -141,7 +141,8 @@ export class MenuEntryActionViewItem extends ActionViewItem {
@IKeybindingService protected readonly _keybindingService: IKeybindingService,
@INotificationService protected _notificationService: INotificationService,
@IContextKeyService protected _contextKeyService: IContextKeyService,
- @IThemeService protected _themeService: IThemeService
+ @IThemeService protected _themeService: IThemeService,
+ @IContextMenuService protected _contextMenuService: IContextMenuService
) {
super(undefined, action, { icon: !!(action.class || action.item.icon), label: !action.class && !action.item.icon, draggable: options?.draggable, keybinding: options?.keybinding, hoverDelegate: options?.hoverDelegate });
this._altKey = ModifierKeyEmitter.getInstance();
@@ -202,6 +203,21 @@ export class MenuEntryActionViewItem extends ActionViewItem {
mouseOver = true;
updateAltState();
}));
+
+
+ this._register(addDisposableListener(container, 'contextmenu', event => {
+ if (!this._menuItemAction.hideActions) {
+ return;
+ }
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ this._contextMenuService.showContextMenu({
+ getAnchor: () => container,
+ getActions: () => this._menuItemAction.hideActions!.asList()
+ });
+ }, true));
}
override updateLabel(): void {
@@ -356,7 +372,7 @@ export class DropdownWithDefaultActionViewItem extends BaseActionViewItem {
) {
super(null, submenuAction);
this._options = options;
- this._storageKey = `${submenuAction.item.submenu._debugName}_lastActionId`;
+ this._storageKey = `${submenuAction.item.submenu.id}_lastActionId`;
// determine default action
let defaultAction: IAction | undefined;
diff --git a/src/vs/platform/actions/common/actions.contribution.ts b/src/vs/platform/actions/common/actions.contribution.ts
new file mode 100644
index 00000000000..6dfb3c99aaf
--- /dev/null
+++ b/src/vs/platform/actions/common/actions.contribution.ts
@@ -0,0 +1,14 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { IMenuService, registerAction2 } from 'vs/platform/actions/common/actions';
+import { MenuHiddenStatesReset } from 'vs/platform/actions/common/menuResetAction';
+import { MenuService } from 'vs/platform/actions/common/menuService';
+import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
+
+
+registerSingleton(IMenuService, MenuService, true);
+
+registerAction2(MenuHiddenStatesReset);
diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts
index d1cd1197cce..461df3221c1 100644
--- a/src/vs/platform/actions/common/actions.ts
+++ b/src/vs/platform/actions/common/actions.ts
@@ -45,7 +45,7 @@ export function isISubmenuItem(item: IMenuItem | ISubmenuItem): item is ISubmenu
export class MenuId {
- private static _idPool = 0;
+ private static readonly _instances = new Map<string, MenuId>();
static readonly CommandPalette = new MenuId('CommandPalette');
static readonly DebugBreakpointsContext = new MenuId('DebugBreakpointsContext');
@@ -162,12 +162,26 @@ export class MenuId {
static readonly NewFile = new MenuId('NewFile');
static readonly MergeToolbar = new MenuId('MergeToolbar');
- readonly id: number;
- readonly _debugName: string;
+ /**
+ * Create or reuse a `MenuId` with the given identifier
+ */
+ static for(identifier: string): MenuId {
+ return MenuId._instances.get(identifier) ?? new MenuId(identifier);
+ }
+
+ readonly id: string;
- constructor(debugName: string) {
- this.id = MenuId._idPool++;
- this._debugName = debugName;
+ /**
+ * Create a new `MenuId` with the unique identifier. Will throw if a menu
+ * with the identifier already exists, use `MenuId.for(ident)` or a unique
+ * identifier
+ */
+ constructor(identifier: string) {
+ if (MenuId._instances.has(identifier)) {
+ throw new TypeError(`MenuId with identifier '${identifier}' already exists. Use MenuId.for(ident) or a unique identifier`);
+ }
+ MenuId._instances.set(identifier, this);
+ this.id = identifier;
}
}
@@ -193,7 +207,18 @@ export interface IMenuService {
readonly _serviceBrand: undefined;
+ /**
+ * Create a new menu for the given menu identifier. A menu sends events when it's entries
+ * have changed (placement, enablement, checked-state). By default it does not send events for
+ * submenu entries. That is more expensive and must be explicitly enabled with the
+ * `emitEventsForSubmenuChanges` flag.
+ */
createMenu(id: MenuId, contextKeyService: IContextKeyService, options?: IMenuCreateOptions): IMenu;
+
+ /**
+ * Reset **all** menu item hidden states.
+ */
+ resetHiddenStates(): void;
}
export type ICommandsMap = Map<string, ICommandAction>;
@@ -350,6 +375,22 @@ export class SubmenuItemAction extends SubmenuAction {
}
}
+export class MenuItemActionManageActions {
+ constructor(
+ readonly hideThis: IAction,
+ readonly toggleAny: readonly IAction[][],
+ ) { }
+
+ asList(): IAction[] {
+ let result: IAction[] = [this.hideThis];
+ for (const n of this.toggleAny) {
+ result.push(new Separator());
+ result = result.concat(n);
+ }
+ return result;
+ }
+}
+
// implements IAction, does NOT extend Action, so that no one
// subscribes to events of Action or modified properties
export class MenuItemAction implements IAction {
@@ -370,6 +411,7 @@ export class MenuItemAction implements IAction {
item: ICommandAction,
alt: ICommandAction | undefined,
options: IMenuActionOptions | undefined,
+ readonly hideActions: MenuItemActionManageActions | undefined,
@IContextKeyService contextKeyService: IContextKeyService,
@ICommandService private _commandService: ICommandService
) {
@@ -396,7 +438,7 @@ export class MenuItemAction implements IAction {
}
this.item = item;
- this.alt = alt ? new MenuItemAction(alt, undefined, options, contextKeyService, _commandService) : undefined;
+ this.alt = alt ? new MenuItemAction(alt, undefined, options, hideActions, contextKeyService, _commandService) : undefined;
this._options = options;
if (ThemeIcon.isThemeIcon(item.icon)) {
this.class = CSSIcon.asClassName(item.icon);
diff --git a/src/vs/platform/actions/common/menuResetAction.ts b/src/vs/platform/actions/common/menuResetAction.ts
new file mode 100644
index 00000000000..84ee76e2b91
--- /dev/null
+++ b/src/vs/platform/actions/common/menuResetAction.ts
@@ -0,0 +1,29 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { localize } from 'vs/nls';
+import { Action2, IMenuService } from 'vs/platform/actions/common/actions';
+import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
+import { ILogService } from 'vs/platform/log/common/log';
+
+export class MenuHiddenStatesReset extends Action2 {
+
+ constructor() {
+ super({
+ id: 'menu.resetHiddenStates',
+ title: {
+ value: localize('title', 'Reset Hidden Menus'),
+ original: 'Reset Hidden Menus'
+ },
+ category: localize('cat', 'View'),
+ f1: true
+ });
+ }
+
+ run(accessor: ServicesAccessor): void {
+ accessor.get(IMenuService).resetHiddenStates();
+ accessor.get(ILogService).info('did RESET all menu hidden states');
+ }
+}
diff --git a/src/vs/platform/actions/common/menuService.ts b/src/vs/platform/actions/common/menuService.ts
index e0f60280e33..5d2092cc1c5 100644
--- a/src/vs/platform/actions/common/menuService.ts
+++ b/src/vs/platform/actions/common/menuService.ts
@@ -6,32 +6,123 @@
import { RunOnceScheduler } from 'vs/base/common/async';
import { Emitter, Event } from 'vs/base/common/event';
import { DisposableStore } from 'vs/base/common/lifecycle';
-import { IMenu, IMenuActionOptions, IMenuCreateOptions, IMenuItem, IMenuService, isIMenuItem, ISubmenuItem, MenuId, MenuItemAction, MenuRegistry, SubmenuItemAction } from 'vs/platform/actions/common/actions';
-import { ILocalizedString } from 'vs/platform/action/common/action';
+import { IMenu, IMenuActionOptions, IMenuCreateOptions, IMenuItem, IMenuService, isIMenuItem, ISubmenuItem, MenuId, MenuItemAction, MenuItemActionManageActions, MenuRegistry, SubmenuItemAction } from 'vs/platform/actions/common/actions';
+import { ICommandAction, ILocalizedString } from 'vs/platform/action/common/action';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { ContextKeyExpression, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
+import { IAction, SubmenuAction } from 'vs/base/common/actions';
+import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
+import { removeFastWithoutKeepingOrder } from 'vs/base/common/arrays';
+import { localize } from 'vs/nls';
export class MenuService implements IMenuService {
declare readonly _serviceBrand: undefined;
+ private readonly _hiddenStates: PersistedMenuHideState;
+
constructor(
- @ICommandService private readonly _commandService: ICommandService
+ @ICommandService private readonly _commandService: ICommandService,
+ @IStorageService storageService: IStorageService,
) {
- //
+ this._hiddenStates = new PersistedMenuHideState(storageService);
}
- /**
- * Create a new menu for the given menu identifier. A menu sends events when it's entries
- * have changed (placement, enablement, checked-state). By default it does send events for
- * sub menu entries. That is more expensive and must be explicitly enabled with the
- * `emitEventsForSubmenuChanges` flag.
- */
createMenu(id: MenuId, contextKeyService: IContextKeyService, options?: IMenuCreateOptions): IMenu {
- return new Menu(id, { emitEventsForSubmenuChanges: false, eventDebounceDelay: 50, ...options }, this._commandService, contextKeyService, this);
+ return new Menu(id, this._hiddenStates, { emitEventsForSubmenuChanges: false, eventDebounceDelay: 50, ...options }, this._commandService, contextKeyService, this);
+ }
+
+ resetHiddenStates(): void {
+ this._hiddenStates.reset();
}
}
+class PersistedMenuHideState {
+
+ private static readonly _key = 'menu.hiddenCommands';
+
+ private readonly _disposables = new DisposableStore();
+ private readonly _onDidChange = new Emitter<void>();
+ readonly onDidChange: Event<void> = this._onDidChange.event;
+
+ private _ignoreChangeEvent: boolean = false;
+ private _data: Record<string, string[] | undefined>;
+
+ constructor(@IStorageService private readonly _storageService: IStorageService) {
+ try {
+ const raw = _storageService.get(PersistedMenuHideState._key, StorageScope.PROFILE, '{}');
+ this._data = JSON.parse(raw);
+ } catch (err) {
+ this._data = Object.create(null);
+ }
+
+ this._disposables.add(_storageService.onDidChangeValue(e => {
+ if (e.key !== PersistedMenuHideState._key) {
+ return;
+ }
+ if (!this._ignoreChangeEvent) {
+ try {
+ const raw = _storageService.get(PersistedMenuHideState._key, StorageScope.PROFILE, '{}');
+ this._data = JSON.parse(raw);
+ } catch (err) {
+ console.log('FAILED to read storage after UPDATE', err);
+ }
+ }
+ this._onDidChange.fire();
+ }));
+ }
+
+ dispose() {
+ this._onDidChange.dispose();
+ this._disposables.dispose();
+ }
+
+ isHidden(menu: MenuId, commandId: string): boolean {
+ return this._data[menu.id]?.includes(commandId) ?? false;
+ }
+
+ updateHidden(menu: MenuId, commandId: string, hidden: boolean): void {
+ const entries = this._data[menu.id];
+ if (!hidden) {
+ // remove and cleanup
+ if (entries) {
+ const idx = entries.indexOf(commandId);
+ if (idx >= 0) {
+ removeFastWithoutKeepingOrder(entries, idx);
+ }
+ if (entries.length === 0) {
+ delete this._data[menu.id];
+ }
+ }
+ } else {
+ // add unless already added
+ if (!entries) {
+ this._data[menu.id] = [commandId];
+ } else {
+ const idx = entries.indexOf(commandId);
+ if (idx < 0) {
+ entries.push(commandId);
+ }
+ }
+ }
+ this._persist();
+ }
+
+ reset(): void {
+ this._data = Object.create(null);
+ this._persist();
+ }
+
+ private _persist(): void {
+ try {
+ this._ignoreChangeEvent = true;
+ const raw = JSON.stringify(this._data);
+ this._storageService.store(PersistedMenuHideState._key, raw, StorageScope.PROFILE, StorageTarget.USER);
+ } finally {
+ this._ignoreChangeEvent = false;
+ }
+ }
+}
type MenuItemGroup = [string, Array<IMenuItem | ISubmenuItem>];
@@ -47,6 +138,7 @@ class Menu implements IMenu {
constructor(
private readonly _id: MenuId,
+ private readonly _hiddenStates: PersistedMenuHideState,
private readonly _options: Required<IMenuCreateOptions>,
@ICommandService private readonly _commandService: ICommandService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@@ -68,24 +160,27 @@ class Menu implements IMenu {
}
}));
- // When context keys change we need to check if the menu also has changed. However,
- // we only do that when someone listens on this menu because (1) context key events are
+ // When context keys or storage state changes we need to check if the menu also has changed. However,
+ // we only do that when someone listens on this menu because (1) these events are
// firing often and (2) menu are often leaked
- const contextKeyListener = this._disposables.add(new DisposableStore());
- const startContextKeyListener = () => {
+ const lazyListener = this._disposables.add(new DisposableStore());
+ const startLazyListener = () => {
const fireChangeSoon = new RunOnceScheduler(() => this._onDidChange.fire(this), _options.eventDebounceDelay);
- contextKeyListener.add(fireChangeSoon);
- contextKeyListener.add(_contextKeyService.onDidChangeContext(e => {
+ lazyListener.add(fireChangeSoon);
+ lazyListener.add(_contextKeyService.onDidChangeContext(e => {
if (e.affectsSome(this._contextKeys)) {
fireChangeSoon.schedule();
}
}));
+ lazyListener.add(_hiddenStates.onDidChange(() => {
+ fireChangeSoon.schedule();
+ }));
};
this._onDidChange = new Emitter({
// start/stop context key listener
- onFirstListenerAdd: startContextKeyListener,
- onLastListenerRemove: contextKeyListener.clear.bind(contextKeyListener)
+ onFirstListenerAdd: startLazyListener,
+ onLastListenerRemove: lazyListener.clear.bind(lazyListener)
});
this.onDidChange = this._onDidChange.event;
@@ -145,20 +240,46 @@ class Menu implements IMenu {
getActions(options?: IMenuActionOptions): [string, Array<MenuItemAction | SubmenuItemAction>][] {
const result: [string, Array<MenuItemAction | SubmenuItemAction>][] = [];
+ const allToggleActions: IAction[][] = [];
+
for (const group of this._menuGroups) {
const [id, items] = group;
+
+ const toggleActions: IAction[] = [];
+
const activeActions: Array<MenuItemAction | SubmenuItemAction> = [];
for (const item of items) {
if (this._contextKeyService.contextMatchesRules(item.when)) {
let action: MenuItemAction | SubmenuItemAction | undefined;
if (isIMenuItem(item)) {
- action = new MenuItemAction(item.command, item.alt, options, this._contextKeyService, this._commandService);
+ if (!this._hiddenStates.isHidden(this._id, item.command.id)) {
+ action = new MenuItemAction(
+ item.command, item.alt, options,
+ new MenuItemActionManageActions(new HideMenuItemAction(this._id, item.command, this._hiddenStates), allToggleActions),
+ this._contextKeyService, this._commandService
+ );
+ }
+ // add toggle commmand
+ toggleActions.push(new ToggleMenuItemAction(this._id, item.command, this._hiddenStates));
} else {
action = new SubmenuItemAction(item, this._menuService, this._contextKeyService, options);
if (action.actions.length === 0) {
action.dispose();
action = undefined;
}
+ // add toggle submenu - this re-creates ToggleMenuItemAction-instances for submenus but that's OK...
+ if (action) {
+ const makeToggleCommand = (id: MenuId, action: IAction): IAction => {
+ if (action instanceof SubmenuItemAction) {
+ return new SubmenuAction(action.id, action.label, action.actions.map(a => makeToggleCommand(action.item.submenu, a)));
+ } else if (action instanceof MenuItemAction) {
+ return new ToggleMenuItemAction(id, action.item, this._hiddenStates);
+ } else {
+ return action;
+ }
+ };
+ toggleActions.push(makeToggleCommand(this._id, action));
+ }
}
if (action) {
@@ -169,6 +290,9 @@ class Menu implements IMenu {
if (activeActions.length > 0) {
result.push([id, activeActions]);
}
+ if (toggleActions.length > 0) {
+ allToggleActions.push(toggleActions);
+ }
}
return result;
}
@@ -231,3 +355,55 @@ class Menu implements IMenu {
return aStr.localeCompare(bStr);
}
}
+
+class ToggleMenuItemAction implements IAction {
+
+ readonly id: string;
+ readonly label: string;
+ readonly enabled: boolean = true;
+ readonly tooltip: string = '';
+
+ readonly checked: boolean;
+ readonly class: undefined;
+
+ run: () => void;
+
+ constructor(id: MenuId, command: ICommandAction, hiddenStates: PersistedMenuHideState) {
+ this.id = `toggle/${id.id}/${command.id}`;
+ this.label = typeof command.title === 'string' ? command.title : command.title.value;
+
+ let isHidden = hiddenStates.isHidden(id, command.id);
+ this.checked = !isHidden;
+ this.run = () => {
+ isHidden = !isHidden;
+ hiddenStates.updateHidden(id, command.id, isHidden);
+ };
+ }
+
+ dispose(): void {
+ // NOTHING
+ }
+}
+
+class HideMenuItemAction implements IAction {
+
+ readonly id: string;
+ readonly label: string;
+ readonly enabled: boolean = true;
+ readonly tooltip: string = '';
+
+ readonly checked: undefined;
+ readonly class: undefined;
+
+ run: () => void;
+
+ constructor(id: MenuId, command: ICommandAction, hiddenStates: PersistedMenuHideState) {
+ this.id = `hide/${id.id}/${command.id}`;
+ this.label = localize('hide.label', 'Hide \'{0}\'', typeof command.title === 'string' ? command.title : command.title.value);
+ this.run = () => { hiddenStates.updateHidden(id, command.id, true); };
+ }
+
+ dispose(): void {
+ // NOTHING
+ }
+}
diff --git a/src/vs/platform/actions/test/common/menuService.test.ts b/src/vs/platform/actions/test/common/menuService.test.ts
index 0a3807b9ef6..99b108e152e 100644
--- a/src/vs/platform/actions/test/common/menuService.test.ts
+++ b/src/vs/platform/actions/test/common/menuService.test.ts
@@ -5,10 +5,12 @@
import * as assert from 'assert';
import { DisposableStore } from 'vs/base/common/lifecycle';
+import { generateUuid } from 'vs/base/common/uuid';
import { isIMenuItem, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
import { MenuService } from 'vs/platform/actions/common/menuService';
import { NullCommandService } from 'vs/platform/commands/common/commands';
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
+import { InMemoryStorageService } from 'vs/platform/storage/common/storage';
// --- service instances
@@ -27,8 +29,8 @@ suite('MenuService', function () {
let testMenuId: MenuId;
setup(function () {
- menuService = new MenuService(NullCommandService);
- testMenuId = new MenuId('testo');
+ menuService = new MenuService(NullCommandService, new InMemoryStorageService());
+ testMenuId = new MenuId(`testo/${generateUuid()}`);
disposables.clear();
});
@@ -200,4 +202,13 @@ suite('MenuService', function () {
assert.strictEqual(foundA, true);
assert.strictEqual(foundB, true);
});
+
+ test('Extension contributed submenus missing with errors in output #155030', function () {
+
+ const id = generateUuid();
+ const menu = new MenuId(id);
+
+ assert.throws(() => new MenuId(id));
+ assert.ok(menu === MenuId.for(id));
+ });
});
diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts
index aa9c2407af0..0e8ccc14714 100644
--- a/src/vs/platform/environment/common/argv.ts
+++ b/src/vs/platform/environment/common/argv.ts
@@ -86,7 +86,6 @@ export interface NativeParsedArgs {
'force-user-env'?: boolean;
'force-disable-user-env'?: boolean;
'sync'?: 'on' | 'off';
- '__sandbox'?: boolean;
'logsPath'?: string;
'__enable-file-policy'?: boolean;
editSessionId?: string;
diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts
index 5ed69a092ef..32fcb903d4c 100644
--- a/src/vs/platform/environment/common/environment.ts
+++ b/src/vs/platform/environment/common/environment.ts
@@ -66,6 +66,7 @@ export interface IEnvironmentService {
// --- continue edit session
editSessionId?: string;
+ editSessionsLogResource: URI;
// --- extension development
debugExtensionHost: IExtensionHostDebugParams;
diff --git a/src/vs/platform/environment/common/environmentService.ts b/src/vs/platform/environment/common/environmentService.ts
index 992c7ed9a96..90b24b9c924 100644
--- a/src/vs/platform/environment/common/environmentService.ts
+++ b/src/vs/platform/environment/common/environmentService.ts
@@ -81,6 +81,9 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron
get userDataSyncLogResource(): URI { return URI.file(join(this.logsPath, 'userDataSync.log')); }
@memoize
+ get editSessionsLogResource(): URI { return URI.file(join(this.logsPath, 'editSessions.log')); }
+
+ @memoize
get sync(): 'on' | 'off' | undefined { return this.args.sync; }
@memoize
diff --git a/src/vs/platform/environment/electron-main/environmentMainService.ts b/src/vs/platform/environment/electron-main/environmentMainService.ts
index 6834b8115fe..53aabe4147c 100644
--- a/src/vs/platform/environment/electron-main/environmentMainService.ts
+++ b/src/vs/platform/environment/electron-main/environmentMainService.ts
@@ -34,7 +34,6 @@ export interface IEnvironmentMainService extends INativeEnvironmentService {
mainLockfile: string;
// --- config
- sandbox: boolean;
disableUpdates: boolean;
}
@@ -56,9 +55,6 @@ export class EnvironmentMainService extends NativeEnvironmentService implements
get mainLockfile(): string { return join(this.userDataPath, 'code.lock'); }
@memoize
- get sandbox(): boolean { return !!this.args['__sandbox']; }
-
- @memoize
get disableUpdates(): boolean { return !!this.args['disable-updates']; }
@memoize
diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts
index bb409289757..e27a89390c2 100644
--- a/src/vs/platform/environment/node/argv.ts
+++ b/src/vs/platform/environment/node/argv.ts
@@ -124,7 +124,6 @@ export const OPTIONS: OptionDescriptions<Required<NativeParsedArgs>> = {
'force-user-env': { type: 'boolean' },
'force-disable-user-env': { type: 'boolean' },
'open-devtools': { type: 'boolean' },
- '__sandbox': { type: 'boolean' },
'logsPath': { type: 'string' },
'__enable-file-policy': { type: 'boolean' },
'editSessionId': { type: 'string' },
diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts
index 66e250a2df1..0e19fa5b37b 100644
--- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts
+++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts
@@ -18,12 +18,10 @@ import {
ServerInstallOptions, ServerInstallVSIXOptions, ServerUninstallOptions, Metadata, ServerInstallExtensionEvent, ServerInstallExtensionResult, ServerUninstallExtensionEvent, ServerDidUninstallExtensionEvent
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions, ExtensionKey, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, getMaliciousExtensionsSet } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
-import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService';
-import { ExtensionType, IExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions';
+import { ExtensionType, IExtensionManifest, isApplicationScopedExtension, TargetPlatform } from 'vs/platform/extensions/common/extensions';
import { ILogService } from 'vs/platform/log/common/log';
import { IProductService } from 'vs/platform/product/common/productService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
-import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
export interface IInstallExtensionTask {
@@ -66,13 +64,11 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
private readonly participants: IExtensionManagementParticipant[] = [];
constructor(
- @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,
- @IUriIdentityService private readonly uriIdenityService: IUriIdentityService,
@IExtensionGalleryService protected readonly galleryService: IExtensionGalleryService,
- @IExtensionsProfileScannerService protected readonly extensionsProfileScannerService: IExtensionsProfileScannerService,
@ITelemetryService protected readonly telemetryService: ITelemetryService,
@ILogService protected readonly logService: ILogService,
- @IProductService protected readonly productService: IProductService
+ @IProductService protected readonly productService: IProductService,
+ @IUserDataProfilesService protected readonly userDataProfilesService: IUserDataProfilesService,
) {
super();
this._register(toDisposable(() => {
@@ -120,7 +116,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
throw new Error(nls.localize('Not a Marketplace extension', "Only Marketplace Extensions can be reinstalled"));
}
- await this.createDefaultUninstallExtensionTask(extension, { remove: true, versionOnly: true }).run();
+ await this.createUninstallExtensionTask(extension, { remove: true, versionOnly: true }).run();
await this.installFromGallery(galleryExtension);
}
@@ -140,13 +136,15 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
}
protected async installExtension(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: ServerInstallOptions & ServerInstallVSIXOptions): Promise<ILocalExtension> {
+
+ const getInstallExtensionTaskKey = (extension: IGalleryExtension) => `${ExtensionKey.create(extension).toString()}${options.profileLocation ? `-${options.profileLocation.toString()}` : ''}`;
+
// only cache gallery extensions tasks
if (!URI.isUri(extension)) {
- const installExtensionTask = this.installingExtensions.get(ExtensionKey.create(extension).toString());
+ const installExtensionTask = this.installingExtensions.get(getInstallExtensionTaskKey(extension));
if (installExtensionTask) {
this.logService.info('Extensions is already requested to install', extension.identifier.id);
- const waitUntilTaskIsFinishedTask = this.createWaitUntilInstallExtensionTaskIsFinishedTask(installExtensionTask, options);
- const { local } = await waitUntilTaskIsFinishedTask.waitUntilTaskIsFinished();
+ const { local } = await installExtensionTask.waitUntilTaskIsFinished();
return local;
}
options = { ...options, installOnlyNewlyAddedFromExtensionPack: true /* always true for gallery extensions */ };
@@ -156,7 +154,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
const installResults: (ServerInstallExtensionResult & { local: ILocalExtension })[] = [];
const installExtensionTask = this.createInstallExtensionTask(manifest, extension, options);
if (!URI.isUri(extension)) {
- this.installingExtensions.set(ExtensionKey.create(extension).toString(), installExtensionTask);
+ this.installingExtensions.set(getInstallExtensionTaskKey(extension), installExtensionTask);
}
this._onInstallExtension.fire({ identifier: installExtensionTask.identifier, source: extension, profileLocation: options.profileLocation });
this.logService.info('Installing extension:', installExtensionTask.identifier.id);
@@ -171,7 +169,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
const allDepsAndPackExtensionsToInstall = await this.getAllDepsAndPackExtensionsToInstall(installExtensionTask.identifier, manifest, !!options.installOnlyNewlyAddedFromExtensionPack, !!options.installPreReleaseVersion, options.profileLocation);
for (const { gallery, manifest } of allDepsAndPackExtensionsToInstall) {
installExtensionHasDependents = installExtensionHasDependents || !!manifest.extensionDependencies?.some(id => areSameExtensions({ id }, installExtensionTask.identifier));
- const key = ExtensionKey.create(gallery).toString();
+ const key = getInstallExtensionTaskKey(gallery);
if (this.installingExtensions.has(key)) {
this.logService.info('Extension is already requested to install', gallery.identifier.id);
} else {
@@ -260,7 +258,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
// rollback installed extensions
if (installResults.length) {
try {
- const result = await Promise.allSettled(installResults.map(({ local }) => this.createUninstallExtensionTask(local, { versionOnly: true }, options.profileLocation).run()));
+ const result = await Promise.allSettled(installResults.map(({ local }) => this.createUninstallExtensionTask(local, { versionOnly: true, profileLocation: options.profileLocation }).run()));
for (let index = 0; index < result.length; index++) {
const r = result[index];
const { identifier } = installResults[index];
@@ -282,7 +280,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
/* Remove the gallery tasks from the cache */
for (const { task } of allInstallExtensionTasks) {
if (!URI.isUri(task.source)) {
- const key = ExtensionKey.create(task.source).toString();
+ const key = getInstallExtensionTaskKey(task.source);
if (!this.installingExtensions.delete(key)) {
this.logService.warn('Installation task is not found in the cache', key);
}
@@ -434,7 +432,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
}
const createUninstallExtensionTask = (extension: ILocalExtension, uninstallOptions: ServerUninstallOptions): IUninstallExtensionTask => {
- const uninstallExtensionTask = this.createUninstallExtensionTask(extension, uninstallOptions, options.profileLocation);
+ const uninstallExtensionTask = this.createUninstallExtensionTask(extension, uninstallOptions);
this.uninstallingExtensions.set(getUninstallExtensionTaskKey(uninstallExtensionTask.extension.identifier), uninstallExtensionTask);
if (options.profileLocation) {
this.logService.info('Uninstalling extension from the profile:', `${extension.identifier.id}@${extension.manifest.version}`, options.profileLocation.toString());
@@ -604,22 +602,17 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
}
private createInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: ServerInstallOptions & ServerInstallVSIXOptions): IInstallExtensionTask {
- const installTask = this.createDefaultInstallExtensionTask(manifest, extension, options);
- return options.profileLocation && this.userDataProfilesService.defaultProfile.extensionsResource ? new InstallExtensionInProfileTask(installTask, options.profileLocation, this.userDataProfilesService.defaultProfile.extensionsResource, this.extensionsProfileScannerService) : installTask;
- }
-
- private createWaitUntilInstallExtensionTaskIsFinishedTask(installTask: IInstallExtensionTask, options: ServerInstallOptions & ServerInstallVSIXOptions): IInstallExtensionTask {
- if (!options.profileLocation || !this.userDataProfilesService.defaultProfile.extensionsResource) {
- return installTask;
- }
- if (installTask instanceof InstallExtensionInProfileTask && this.uriIdenityService.extUri.isEqual(installTask.profileLocation, options.profileLocation)) {
- return installTask;
+ if (options.profileLocation && isApplicationScopedExtension(manifest)) {
+ options = { ...options, profileLocation: this.userDataProfilesService.defaultProfile.extensionsResource };
}
- return new InstallExtensionInProfileTask(installTask, options.profileLocation, this.userDataProfilesService.defaultProfile.extensionsResource, this.extensionsProfileScannerService);
+ return this.doCreateInstallExtensionTask(manifest, extension, options);
}
- private createUninstallExtensionTask(extension: ILocalExtension, options: ServerUninstallOptions, profile?: URI): IUninstallExtensionTask {
- return profile && this.userDataProfilesService.defaultProfile.extensionsResource ? new UninstallExtensionFromProfileTask(extension, profile, this.userDataProfilesService, this.extensionsProfileScannerService) : this.createDefaultUninstallExtensionTask(extension, options);
+ private createUninstallExtensionTask(extension: ILocalExtension, options: ServerUninstallOptions): IUninstallExtensionTask {
+ if (options.profileLocation && extension.isApplicationScoped) {
+ options = { ...options, profileLocation: this.userDataProfilesService.defaultProfile.extensionsResource };
+ }
+ return this.doCreateUninstallExtensionTask(extension, options);
}
abstract getTargetPlatform(): Promise<TargetPlatform>;
@@ -633,8 +626,8 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
abstract updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise<ILocalExtension>;
abstract updateExtensionScope(local: ILocalExtension, isMachineScoped: boolean): Promise<ILocalExtension>;
- protected abstract createDefaultInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: ServerInstallOptions & ServerInstallVSIXOptions): IInstallExtensionTask;
- protected abstract createDefaultUninstallExtensionTask(extension: ILocalExtension, options: ServerUninstallOptions): IUninstallExtensionTask;
+ protected abstract doCreateInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: ServerInstallOptions & ServerInstallVSIXOptions): IInstallExtensionTask;
+ protected abstract doCreateUninstallExtensionTask(extension: ILocalExtension, options: ServerUninstallOptions): IUninstallExtensionTask;
}
export function joinErrors(errorOrErrors: (Error | string) | (Array<Error | string>)): Error {
@@ -731,63 +724,3 @@ export abstract class AbstractExtensionTask<T> {
protected abstract doRun(token: CancellationToken): Promise<T>;
}
-
-class InstallExtensionInProfileTask implements IInstallExtensionTask {
-
- readonly identifier = this.task.identifier;
- readonly source = this.task.source;
- readonly operation = this.task.operation;
-
- private readonly promise: Promise<{ local: ILocalExtension; metadata: Metadata }>;
-
- constructor(
- private readonly task: IInstallExtensionTask,
- readonly profileLocation: URI,
- private readonly defaultProfileLocation: URI,
- private readonly extensionsProfileScannerService: IExtensionsProfileScannerService,
- ) {
- this.promise = this.waitAndAddExtensionToProfile();
- }
-
- private async waitAndAddExtensionToProfile(): Promise<{ local: ILocalExtension; metadata: Metadata }> {
- const result = await this.task.waitUntilTaskIsFinished();
- const profileLocation = result.local.isApplicationScoped ? this.defaultProfileLocation : this.profileLocation;
- await this.extensionsProfileScannerService.addExtensionsToProfile([[result.local, result.metadata]], profileLocation);
- return result;
- }
-
- async run(): Promise<{ local: ILocalExtension; metadata: Metadata }> {
- await this.task.run();
- return this.promise;
- }
-
- waitUntilTaskIsFinished(): Promise<{ local: ILocalExtension; metadata: Metadata }> {
- return this.promise;
- }
-
- cancel(): void {
- return this.task.cancel();
- }
-}
-
-class UninstallExtensionFromProfileTask extends AbstractExtensionTask<void> implements IUninstallExtensionTask {
-
- constructor(
- readonly extension: ILocalExtension,
- private readonly profileLocation: URI,
- private readonly userDataProfilesService: IUserDataProfilesService,
- private readonly extensionsProfileScannerService: IExtensionsProfileScannerService,
- ) {
- super();
- }
-
- protected async doRun(token: CancellationToken): Promise<void> {
- const promises: Promise<any>[] = [];
- promises.push(this.extensionsProfileScannerService.removeExtensionFromProfile(this.extension.identifier, this.profileLocation));
- if (this.extension.isApplicationScoped && this.userDataProfilesService.defaultProfile.extensionsResource) {
- promises.push(this.extensionsProfileScannerService.removeExtensionFromProfile(this.extension.identifier, this.userDataProfilesService.defaultProfile.extensionsResource));
- }
- await Promise.all(promises);
- }
-
-}
diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts
index 3c5ec520512..9302da87da0 100644
--- a/src/vs/platform/extensionManagement/common/extensionManagement.ts
+++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts
@@ -528,3 +528,10 @@ export interface IExtensionManagementCLIService {
uninstallExtensions(extensions: (string | URI)[], force: boolean, output?: CLIOutput): Promise<void>;
locateExtension(extensions: string[], output?: CLIOutput): Promise<void>;
}
+
+export const IDefaultExtensionsProfileInitService = createDecorator<IDefaultExtensionsProfileInitService>('IDefaultExtensionsProfileInitService');
+export interface IDefaultExtensionsProfileInitService {
+ readonly _serviceBrand: undefined;
+ initialize(): Promise<void>;
+ uninitialize(): Promise<void>;
+}
diff --git a/src/vs/platform/extensionManagement/common/extensionTipsService.ts b/src/vs/platform/extensionManagement/common/extensionTipsService.ts
index 4e59c5a3b47..c9c4aaf97dd 100644
--- a/src/vs/platform/extensionManagement/common/extensionTipsService.ts
+++ b/src/vs/platform/extensionManagement/common/extensionTipsService.ts
@@ -5,7 +5,6 @@
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { CancellationToken } from 'vs/base/common/cancellation';
-import { forEach } from 'vs/base/common/collections';
import { Disposable } from 'vs/base/common/lifecycle';
import { IConfigBasedExtensionTip as IRawConfigBasedExtensionTip } from 'vs/base/common/product';
import { joinPath } from 'vs/base/common/resources';
@@ -31,7 +30,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
) {
super();
if (this.productService.configBasedExtensionTips) {
- forEach(this.productService.configBasedExtensionTips, ({ value }) => this.allConfigBasedTips.set(value.configPath, value));
+ Object.entries(this.productService.configBasedExtensionTips).forEach(([, value]) => this.allConfigBasedTips.set(value.configPath, value));
}
}
@@ -60,7 +59,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
try {
const content = await this.fileService.readFile(joinPath(folder, configPath));
const recommendationByRemote: Map<string, IConfigBasedExtensionTip> = new Map<string, IConfigBasedExtensionTip>();
- forEach(tip.recommendations, ({ key, value }) => {
+ Object.entries(tip.recommendations).forEach(([key, value]) => {
if (isNonEmptyArray(value.remotes)) {
for (const remote of value.remotes) {
recommendationByRemote.set(remote, {
diff --git a/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts b/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts
index 8acce96e6e1..43c8cfd1e19 100644
--- a/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts
+++ b/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts
@@ -10,12 +10,10 @@ import { ResourceMap } from 'vs/base/common/map';
import { URI, UriComponents } from 'vs/base/common/uri';
import { ILocalExtension, Metadata } from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
-import { IExtensionIdentifier, IExtensionManifest } from 'vs/platform/extensions/common/extensions';
+import { IExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
-import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
-import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
interface IStoredProfileExtension {
identifier: IExtensionIdentifier;
@@ -43,45 +41,13 @@ export interface IExtensionsProfileScannerService {
export class ExtensionsProfileScannerService extends Disposable implements IExtensionsProfileScannerService {
readonly _serviceBrand: undefined;
- private readonly migratePromise: Promise<void>;
private readonly resourcesAccessQueueMap = new ResourceMap<Queue<IScannedProfileExtension[]>>();
constructor(
@IFileService private readonly fileService: IFileService,
- @IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
- @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,
@ILogService private readonly logService: ILogService,
) {
super();
- this.migratePromise = this.migrate();
- }
-
- // TODO: @sandy081 remove it in a month
- private async migrate(): Promise<void> {
- await Promise.all(this.userDataProfilesService.profiles.map(async e => {
- if (!e.extensionsResource) {
- return;
- }
- try {
- let needsMigrating: boolean = false;
- const storedWebExtensions: IStoredProfileExtension[] = JSON.parse((await this.fileService.readFile(e.extensionsResource)).value.toString());
- for (const e of storedWebExtensions) {
- if (!e.location) {
- continue;
- }
- if (!e.version) {
- try {
- const content = (await this.fileService.readFile(this.uriIdentityService.extUri.joinPath(URI.revive(e.location), 'package.json'))).value.toString();
- e.version = (<IExtensionManifest>JSON.parse(content)).version;
- needsMigrating = true;
- } catch (error) { /* ignore */ }
- }
- }
- if (needsMigrating) {
- await this.fileService.writeFile(e.extensionsResource, VSBuffer.fromString(JSON.stringify(storedWebExtensions)));
- }
- } catch (error) { /* Ignore */ }
- }));
}
scanProfileExtensions(profileLocation: URI): Promise<IScannedProfileExtension[]> {
@@ -102,7 +68,6 @@ export class ExtensionsProfileScannerService extends Disposable implements IExte
}
private async withProfileExtensions(file: URI, updateFn?: (extensions: IScannedProfileExtension[]) => IScannedProfileExtension[]): Promise<IScannedProfileExtension[]> {
- await this.migratePromise;
return this.getResourceAccessQueue(file).queue(async () => {
let extensions: IScannedProfileExtension[] = [];
diff --git a/src/vs/platform/extensionManagement/common/extensionsScannerService.ts b/src/vs/platform/extensionManagement/common/extensionsScannerService.ts
index 29a0264d9cc..115348c255f 100644
--- a/src/vs/platform/extensionManagement/common/extensionsScannerService.ts
+++ b/src/vs/platform/extensionManagement/common/extensionsScannerService.ts
@@ -27,13 +27,15 @@ import { areSameExtensions, computeTargetPlatform, ExtensionKey, getExtensionId,
import { ExtensionType, ExtensionIdentifier, IExtensionManifest, TargetPlatform, IExtensionIdentifier, IRelaxedExtensionManifest, UNDEFINED_PUBLISHER, IExtensionDescription, BUILTIN_MANIFEST_CACHE_FILE, USER_MANIFEST_CACHE_FILE, MANIFEST_CACHE_FOLDER } from 'vs/platform/extensions/common/extensions';
import { validateExtensionManifest } from 'vs/platform/extensions/common/extensionValidator';
import { FileOperationResult, IFileService, toFileOperationResult } from 'vs/platform/files/common/files';
-import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
+import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { IProductService } from 'vs/platform/product/common/productService';
import { Emitter, Event } from 'vs/base/common/event';
import { revive } from 'vs/base/common/marshalling';
import { IExtensionsProfileScannerService, IScannedProfileExtension } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService';
import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
+import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
+import { ILocalizedString } from 'vs/platform/action/common/action';
export type IScannedExtensionManifest = IRelaxedExtensionManifest & { __metadata?: Metadata };
@@ -139,9 +141,9 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem
readonly onDidChangeCache = this._onDidChangeCache.event;
private readonly obsoleteFile = joinPath(this.userExtensionsLocation, '.obsolete');
- private readonly systemExtensionsCachedScanner = this._register(new CachedExtensionsScanner(joinPath(this.cacheLocation, BUILTIN_MANIFEST_CACHE_FILE), this.obsoleteFile, this.extensionsProfileScannerService, this.fileService, this.logService));
- private readonly userExtensionsCachedScanner = this._register(new CachedExtensionsScanner(joinPath(this.cacheLocation, USER_MANIFEST_CACHE_FILE), this.obsoleteFile, this.extensionsProfileScannerService, this.fileService, this.logService));
- private readonly extensionsScanner = this._register(new ExtensionsScanner(this.obsoleteFile, this.extensionsProfileScannerService, this.fileService, this.logService));
+ private readonly systemExtensionsCachedScanner = this._register(this.instantiationService.createInstance(CachedExtensionsScanner, joinPath(this.cacheLocation, BUILTIN_MANIFEST_CACHE_FILE), this.obsoleteFile));
+ private readonly userExtensionsCachedScanner = this._register(this.instantiationService.createInstance(CachedExtensionsScanner, joinPath(this.cacheLocation, USER_MANIFEST_CACHE_FILE), this.obsoleteFile));
+ private readonly extensionsScanner = this._register(this.instantiationService.createInstance(ExtensionsScanner, this.obsoleteFile));
constructor(
readonly systemExtensionsLocation: URI,
@@ -154,6 +156,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem
@ILogService protected readonly logService: ILogService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IProductService private readonly productService: IProductService,
+ @IInstantiationService private readonly instantiationService: IInstantiationService,
) {
super();
@@ -381,7 +384,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem
private async createExtensionScannerInput(location: URI, profile: boolean, type: ExtensionType, excludeObsolete: boolean, language: string | undefined, validate: boolean = true): Promise<ExtensionScannerInput> {
const translations = await this.getTranslations(language ?? platform.language);
const mtime = await this.getMtime(location);
- const applicationExtensionsLocation = this.userDataProfilesService.defaultProfile.extensionsResource;
+ const applicationExtensionsLocation = profile ? this.userDataProfilesService.defaultProfile.extensionsResource : undefined;
const applicationExtensionsLocationMtime = applicationExtensionsLocation ? await this.getMtime(applicationExtensionsLocation) : undefined;
return new ExtensionScannerInput(
location,
@@ -476,9 +479,10 @@ class ExtensionsScanner extends Disposable {
constructor(
private readonly obsoleteFile: URI,
- protected readonly extensionsProfileScannerService: IExtensionsProfileScannerService,
- protected readonly fileService: IFileService,
- protected readonly logService: ILogService
+ @IExtensionsProfileScannerService protected readonly extensionsProfileScannerService: IExtensionsProfileScannerService,
+ @IUriIdentityService protected readonly uriIdentityService: IUriIdentityService,
+ @IFileService protected readonly fileService: IFileService,
+ @ILogService protected readonly logService: ILogService
) {
super();
}
@@ -512,19 +516,19 @@ class ExtensionsScanner extends Disposable {
const extensionScannerInput = new ExtensionScannerInput(c.resource, input.mtime, input.applicationExtensionslocation, input.applicationExtensionslocationMtime, input.profile, input.type, input.excludeObsolete, input.validate, input.productVersion, input.productDate, input.productCommit, input.devMode, input.language, input.translations);
return this.scanExtension(extensionScannerInput);
}));
- return coalesce(extensions);
+ return coalesce(extensions)
+ // Sort: Make sure extensions are in the same order always. Helps cache invalidation even if the order changes.
+ .sort((a, b) => a.location.path < b.location.path ? -1 : 1);
}
private async scanExtensionsFromProfile(input: ExtensionScannerInput): Promise<IRelaxedScannedExtension[]> {
- const profileExtensions = await this.scanExtensionsFromProfileResource(input.location, () => true, input);
- const applicationExtensions = await this.scanApplicationExtensions(input);
- return [...profileExtensions, ...applicationExtensions];
- }
-
- private async scanApplicationExtensions(input: ExtensionScannerInput): Promise<IRelaxedScannedExtension[]> {
- return input.applicationExtensionslocation
- ? this.scanExtensionsFromProfileResource(input.applicationExtensionslocation, (e) => !!e.metadata?.isApplicationScoped, input)
- : [];
+ let profileExtensions = await this.scanExtensionsFromProfileResource(input.location, () => true, input);
+ if (input.applicationExtensionslocation && !this.uriIdentityService.extUri.isEqual(input.location, input.applicationExtensionslocation)) {
+ profileExtensions = profileExtensions.filter(e => !e.metadata?.isApplicationScoped);
+ const applicationExtensions = await this.scanExtensionsFromProfileResource(input.applicationExtensionslocation, (e) => !!e.metadata?.isApplicationScoped, input);
+ profileExtensions.push(...applicationExtensions);
+ }
+ return profileExtensions;
}
private async scanExtensionsFromProfileResource(profileResource: URI, filter: (extensionInfo: IScannedProfileExtension) => boolean, input: ExtensionScannerInput): Promise<IRelaxedScannedExtension[]> {
@@ -790,13 +794,23 @@ class ExtensionsScanner extends Disposable {
if (translated === undefined && originalMessages) {
translated = originalMessages[messageKey];
}
- let message: string | undefined = typeof translated === 'string' ? translated : (typeof translated?.message === 'string' ? translated.message : undefined);
+ let message: string | undefined = typeof translated === 'string' ? translated : translated.message;
if (message !== undefined) {
if (pseudo) {
// FF3B and FF3D is the Unicode zenkaku representation for [ and ]
message = '\uFF3B' + message.replace(/[aouei]/g, '$&$&') + '\uFF3D';
}
- obj[key] = command && (key === 'title' || key === 'category') && originalMessages ? { value: message, original: originalMessages[messageKey] } : message;
+ // This branch returns ILocalizedString's instead of Strings so that the Command Palette can contain both the localized and the original value.
+ if (command && originalMessages && (key === 'title' || key === 'category')) {
+ const originalMessage = originalMessages[messageKey];
+ const localizedString: ILocalizedString = {
+ value: message,
+ original: typeof originalMessage === 'string' ? originalMessage : originalMessage?.message
+ };
+ obj[key] = localizedString;
+ } else {
+ obj[key] = message;
+ }
} else {
this.logService.warn(this.formatMessage(extensionLocation, localize('missingNLSKey', "Couldn't find message for key {0}.", messageKey)));
}
@@ -843,11 +857,12 @@ class CachedExtensionsScanner extends ExtensionsScanner {
constructor(
private readonly cacheFile: URI,
obsoleteFile: URI,
- extensionsProfileScannerService: IExtensionsProfileScannerService,
- fileService: IFileService,
- logService: ILogService
+ @IExtensionsProfileScannerService extensionsProfileScannerService: IExtensionsProfileScannerService,
+ @IUriIdentityService uriIdentityService: IUriIdentityService,
+ @IFileService fileService: IFileService,
+ @ILogService logService: ILogService
) {
- super(obsoleteFile, extensionsProfileScannerService, fileService, logService);
+ super(obsoleteFile, extensionsProfileScannerService, uriIdentityService, fileService, logService);
}
override async scanExtensions(input: ExtensionScannerInput): Promise<IRelaxedScannedExtension[]> {
@@ -945,13 +960,14 @@ export class NativeExtensionsScannerService extends AbstractExtensionsScannerSer
logService: ILogService,
environmentService: IEnvironmentService,
productService: IProductService,
+ instantiationService: IInstantiationService,
) {
super(
systemExtensionsLocation,
userExtensionsLocation,
joinPath(userHome, '.vscode-oss-dev', 'extensions', 'control.json'),
joinPath(userDataPath, MANIFEST_CACHE_FOLDER),
- userDataProfilesService, extensionsProfileScannerService, fileService, logService, environmentService, productService);
+ userDataProfilesService, extensionsProfileScannerService, fileService, logService, environmentService, productService, instantiationService);
this.translationsPromise = (async () => {
if (platform.translationsConfigFile) {
try {
diff --git a/src/vs/platform/extensionManagement/electron-main/defaultExtensionsProfileInit.ts b/src/vs/platform/extensionManagement/electron-main/defaultExtensionsProfileInit.ts
new file mode 100644
index 00000000000..fd3d1698683
--- /dev/null
+++ b/src/vs/platform/extensionManagement/electron-main/defaultExtensionsProfileInit.ts
@@ -0,0 +1,27 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { Disposable } from 'vs/base/common/lifecycle';
+import { IDefaultExtensionsProfileInitService } from 'vs/platform/extensionManagement/common/extensionManagement';
+import { IUserDataProfilesMainService } from 'vs/platform/userDataProfile/electron-main/userDataProfile';
+
+export class DefaultExtensionsProfileInitHandler extends Disposable {
+ constructor(
+ @IDefaultExtensionsProfileInitService private readonly defaultExtensionsProfileInitService: IDefaultExtensionsProfileInitService,
+ @IUserDataProfilesMainService userDataProfilesService: IUserDataProfilesMainService,
+ ) {
+ super();
+ this._register(userDataProfilesService.onWillCreateProfile(e => {
+ if (userDataProfilesService.profiles.length === 1) {
+ e.join(this.defaultExtensionsProfileInitService.initialize());
+ }
+ }));
+ this._register(userDataProfilesService.onDidChangeProfiles(e => {
+ if (userDataProfilesService.profiles.length === 1) {
+ this.defaultExtensionsProfileInitService.uninitialize();
+ }
+ }));
+ }
+}
diff --git a/src/vs/platform/extensionManagement/electron-sandbox/defaultExtensionsProfileInit.ts b/src/vs/platform/extensionManagement/electron-sandbox/defaultExtensionsProfileInit.ts
new file mode 100644
index 00000000000..d353dc77727
--- /dev/null
+++ b/src/vs/platform/extensionManagement/electron-sandbox/defaultExtensionsProfileInit.ts
@@ -0,0 +1,45 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { Disposable } from 'vs/base/common/lifecycle';
+import { joinPath } from 'vs/base/common/resources';
+import { URI } from 'vs/base/common/uri';
+import { IDefaultExtensionsProfileInitService, IExtensionManagementService, 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 { EXTENSIONS_RESOURCE_NAME, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
+
+export class DefaultExtensionsProfileInitService extends Disposable implements IDefaultExtensionsProfileInitService {
+
+ readonly _serviceBrand: undefined;
+
+ constructor(
+ @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,
+ @IFileService private readonly fileService: IFileService,
+ @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
+ @IExtensionsProfileScannerService private readonly extensionsProfileScannerService: IExtensionsProfileScannerService,
+ ) {
+ super();
+ }
+
+ async initialize(): Promise<void> {
+ /* Create and populate the default extensions profile resource */
+ const extensionsProfileResource = this.getDefaultExtensionsProfileResource();
+ try { await this.fileService.del(extensionsProfileResource); } catch (error) { /* ignore */ }
+ const userExtensions = await this.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);
+ }
+
+ async uninitialize(): Promise<void> {
+ /* Remove the default extensions profile resource */
+ try { await this.fileService.del(this.getDefaultExtensionsProfileResource()); } catch (error) { /* ignore */ }
+ }
+
+ private getDefaultExtensionsProfileResource(): URI {
+ return this.userDataProfilesService.defaultProfile.extensionsResource ?? joinPath(this.userDataProfilesService.defaultProfile.location, EXTENSIONS_RESOURCE_NAME);
+ }
+}
diff --git a/src/vs/platform/extensionManagement/electron-sandbox/extensionTipsService.ts b/src/vs/platform/extensionManagement/electron-sandbox/extensionTipsService.ts
index c6e03e0bf5f..fdfb6721b9a 100644
--- a/src/vs/platform/extensionManagement/electron-sandbox/extensionTipsService.ts
+++ b/src/vs/platform/extensionManagement/electron-sandbox/extensionTipsService.ts
@@ -5,7 +5,7 @@
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { disposableTimeout, timeout } from 'vs/base/common/async';
-import { forEach, IStringDictionary } from 'vs/base/common/collections';
+import { IStringDictionary } from 'vs/base/common/collections';
import { Event } from 'vs/base/common/event';
import { join } from 'vs/base/common/path';
import { isWindows } from 'vs/base/common/platform';
@@ -67,11 +67,11 @@ export class ExtensionTipsService extends BaseExtensionTipsService {
) {
super(fileService, productService, requestService, logService);
if (productService.exeBasedExtensionTips) {
- forEach(productService.exeBasedExtensionTips, ({ key, value: exeBasedExtensionTip }) => {
+ Object.entries(productService.exeBasedExtensionTips).forEach(([key, exeBasedExtensionTip]) => {
const highImportanceRecommendations: { extensionId: string; extensionName: string; isExtensionPack: boolean }[] = [];
const mediumImportanceRecommendations: { extensionId: string; extensionName: string; isExtensionPack: boolean }[] = [];
const otherRecommendations: { extensionId: string; extensionName: string; isExtensionPack: boolean }[] = [];
- forEach(exeBasedExtensionTip.recommendations, ({ key: extensionId, value }) => {
+ Object.entries(exeBasedExtensionTip.recommendations).forEach(([extensionId, value]) => {
if (value.important) {
if (exeBasedExtensionTip.important) {
highImportanceRecommendations.push({ extensionId, extensionName: value.name, isExtensionPack: !!value.isExtensionPack });
diff --git a/src/vs/platform/extensionManagement/electron-sandbox/extensionsScannerService.ts b/src/vs/platform/extensionManagement/electron-sandbox/extensionsScannerService.ts
index cfc0d5dacf3..9503799fef8 100644
--- a/src/vs/platform/extensionManagement/electron-sandbox/extensionsScannerService.ts
+++ b/src/vs/platform/extensionManagement/electron-sandbox/extensionsScannerService.ts
@@ -9,6 +9,7 @@ import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagemen
import { IExtensionsScannerService, NativeExtensionsScannerService, } from 'vs/platform/extensionManagement/common/extensionsScannerService';
import { IFileService } from 'vs/platform/files/common/files';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
+import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { IProductService } from 'vs/platform/product/common/productService';
import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
@@ -22,13 +23,14 @@ export class ExtensionsScannerService extends NativeExtensionsScannerService imp
@ILogService logService: ILogService,
@INativeEnvironmentService environmentService: INativeEnvironmentService,
@IProductService productService: IProductService,
+ @IInstantiationService instantiationService: IInstantiationService,
) {
super(
URI.file(environmentService.builtinExtensionsPath),
URI.file(environmentService.extensionsPath),
environmentService.userHome,
URI.file(environmentService.userDataPath),
- userDataProfilesService, extensionsProfileScannerService, fileService, logService, environmentService, productService);
+ userDataProfilesService, extensionsProfileScannerService, fileService, logService, environmentService, productService, instantiationService);
}
}
diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts
index 56ea181d99e..a5f367b4124 100644
--- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts
+++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts
@@ -65,13 +65,15 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
private readonly manifestCache: ExtensionsManifestCache;
private readonly extensionsDownloader: ExtensionsDownloader;
+ private readonly installGalleryExtensionsTasks = new Map<string, InstallGalleryExtensionTask>();
+
constructor(
@IExtensionGalleryService galleryService: IExtensionGalleryService,
@ITelemetryService telemetryService: ITelemetryService,
@ILogService logService: ILogService,
@INativeEnvironmentService private readonly environmentService: INativeEnvironmentService,
@IExtensionsScannerService private readonly extensionsScannerService: IExtensionsScannerService,
- @IExtensionsProfileScannerService extensionsProfileScannerService: IExtensionsProfileScannerService,
+ @IExtensionsProfileScannerService private readonly extensionsProfileScannerService: IExtensionsProfileScannerService,
@IDownloadService private downloadService: IDownloadService,
@IInstantiationService instantiationService: IInstantiationService,
@IFileService private readonly fileService: IFileService,
@@ -79,7 +81,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
@IUriIdentityService uriIdentityService: IUriIdentityService,
@IUserDataProfilesService userDataProfilesService: IUserDataProfilesService,
) {
- super(userDataProfilesService, uriIdentityService, galleryService, extensionsProfileScannerService, telemetryService, logService, productService);
+ super(galleryService, telemetryService, logService, productService, userDataProfilesService);
const extensionLifecycle = this._register(instantiationService.createInstance(ExtensionsLifecycle));
this.extensionsScanner = this._register(instantiationService.createInstance(ExtensionsScanner, extension => extensionLifecycle.postUninstall(extension)));
this.manifestCache = this._register(new ExtensionsManifestCache(environmentService, this));
@@ -176,11 +178,28 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
return downloadedLocation;
}
- protected createDefaultInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: ServerInstallOptions & ServerInstallVSIXOptions): IInstallExtensionTask {
- return URI.isUri(extension) ? new InstallVSIXTask(manifest, extension, options, this.galleryService, this.extensionsScanner, this.logService) : new InstallGalleryExtensionTask(manifest, extension, options, this.extensionsDownloader, this.extensionsScanner, this.logService);
+ protected doCreateInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: ServerInstallOptions & ServerInstallVSIXOptions): IInstallExtensionTask {
+ let installExtensionTask: IInstallExtensionTask | undefined;
+ if (URI.isUri(extension)) {
+ installExtensionTask = new InstallVSIXTask(manifest, extension, options, this.galleryService, this.extensionsScanner, this.logService);
+ } else {
+ const key = ExtensionKey.create(extension).toString();
+ installExtensionTask = this.installGalleryExtensionsTasks.get(key);
+ if (!installExtensionTask) {
+ this.installGalleryExtensionsTasks.set(key, installExtensionTask = new InstallGalleryExtensionTask(manifest, extension, options, this.extensionsDownloader, this.extensionsScanner, this.logService));
+ installExtensionTask.waitUntilTaskIsFinished().then(() => this.installGalleryExtensionsTasks.delete(key));
+ }
+ }
+ if (options.profileLocation) {
+ return new InstallExtensionInProfileTask(installExtensionTask, options.profileLocation, this.extensionsProfileScannerService);
+ }
+ return installExtensionTask;
}
- protected createDefaultUninstallExtensionTask(extension: ILocalExtension, options: ServerUninstallOptions): IUninstallExtensionTask {
+ protected doCreateUninstallExtensionTask(extension: ILocalExtension, options: ServerUninstallOptions): IUninstallExtensionTask {
+ if (options.profileLocation) {
+ return new UninstallExtensionFromProfileTask(extension, options.profileLocation, this.extensionsProfileScannerService);
+ }
return new UninstallExtensionTask(extension, options, this.extensionsScanner);
}
@@ -706,6 +725,42 @@ class InstallVSIXTask extends InstallExtensionTask {
}
}
+class InstallExtensionInProfileTask implements IInstallExtensionTask {
+
+ readonly identifier = this.task.identifier;
+ readonly source = this.task.source;
+ readonly operation = this.task.operation;
+
+ private readonly promise: Promise<{ local: ILocalExtension; metadata: Metadata }>;
+
+ constructor(
+ private readonly task: IInstallExtensionTask,
+ private readonly profileLocation: URI,
+ private readonly extensionsProfileScannerService: IExtensionsProfileScannerService,
+ ) {
+ this.promise = this.waitAndAddExtensionToProfile();
+ }
+
+ private async waitAndAddExtensionToProfile(): Promise<{ local: ILocalExtension; metadata: Metadata }> {
+ const result = await this.task.waitUntilTaskIsFinished();
+ await this.extensionsProfileScannerService.addExtensionsToProfile([[result.local, result.metadata]], this.profileLocation);
+ return result;
+ }
+
+ async run(): Promise<{ local: ILocalExtension; metadata: Metadata }> {
+ await this.task.run();
+ return this.promise;
+ }
+
+ waitUntilTaskIsFinished(): Promise<{ local: ILocalExtension; metadata: Metadata }> {
+ return this.promise;
+ }
+
+ cancel(): void {
+ return this.task.cancel();
+ }
+}
+
class UninstallExtensionTask extends AbstractExtensionTask<void> implements IUninstallExtensionTask {
constructor(
@@ -745,3 +800,20 @@ class UninstallExtensionTask extends AbstractExtensionTask<void> implements IUni
}
}
+
+class UninstallExtensionFromProfileTask extends AbstractExtensionTask<void> implements IUninstallExtensionTask {
+
+ constructor(
+ readonly extension: ILocalExtension,
+ private readonly profileLocation: URI,
+ private readonly extensionsProfileScannerService: IExtensionsProfileScannerService,
+ ) {
+ super();
+ }
+
+ protected async doRun(token: CancellationToken): Promise<void> {
+ await this.extensionsProfileScannerService.removeExtensionFromProfile(this.extension.identifier, this.profileLocation);
+ }
+
+}
+
diff --git a/src/vs/platform/extensionManagement/node/extensionsScannerService.ts b/src/vs/platform/extensionManagement/node/extensionsScannerService.ts
index 9d90bf687d6..20a1fd0a61d 100644
--- a/src/vs/platform/extensionManagement/node/extensionsScannerService.ts
+++ b/src/vs/platform/extensionManagement/node/extensionsScannerService.ts
@@ -8,6 +8,7 @@ import { INativeEnvironmentService } from 'vs/platform/environment/common/enviro
import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService';
import { IExtensionsScannerService, NativeExtensionsScannerService, } from 'vs/platform/extensionManagement/common/extensionsScannerService';
import { IFileService } from 'vs/platform/files/common/files';
+import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { IProductService } from 'vs/platform/product/common/productService';
import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
@@ -21,13 +22,14 @@ export class ExtensionsScannerService extends NativeExtensionsScannerService imp
@ILogService logService: ILogService,
@INativeEnvironmentService environmentService: INativeEnvironmentService,
@IProductService productService: IProductService,
+ @IInstantiationService instantiationService: IInstantiationService,
) {
super(
URI.file(environmentService.builtinExtensionsPath),
URI.file(environmentService.extensionsPath),
environmentService.userHome,
URI.file(environmentService.userDataPath),
- userDataProfilesService, extensionsProfileScannerService, fileService, logService, environmentService, productService);
+ userDataProfilesService, extensionsProfileScannerService, fileService, logService, environmentService, productService, instantiationService);
}
}
diff --git a/src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.ts b/src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.ts
index 54a7232c285..b0a7ec8ad9a 100644
--- a/src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.ts
+++ b/src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.ts
@@ -14,6 +14,7 @@ import { ExtensionType, IExtensionManifest, MANIFEST_CACHE_FOLDER, TargetPlatfor
import { IFileService } from 'vs/platform/files/common/files';
import { FileService } from 'vs/platform/files/common/fileService';
import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider';
+import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { ILogService, NullLogService } from 'vs/platform/log/common/log';
import { IProductService } from 'vs/platform/product/common/productService';
@@ -32,13 +33,14 @@ class ExtensionsScannerService extends AbstractExtensionsScannerService implemen
@ILogService logService: ILogService,
@INativeEnvironmentService nativeEnvironmentService: INativeEnvironmentService,
@IProductService productService: IProductService,
+ @IInstantiationService instantiationService: IInstantiationService,
) {
super(
URI.file(nativeEnvironmentService.builtinExtensionsPath),
URI.file(nativeEnvironmentService.extensionsPath),
joinPath(nativeEnvironmentService.userHome, '.vscode-oss-dev', 'extensions', 'control.json'),
joinPath(ROOT, MANIFEST_CACHE_FOLDER),
- userDataProfilesService, extensionsProfileScannerService, fileService, logService, nativeEnvironmentService, productService);
+ userDataProfilesService, extensionsProfileScannerService, fileService, logService, nativeEnvironmentService, productService, instantiationService);
}
protected async getTranslations(language: string): Promise<Translations> {
@@ -70,10 +72,8 @@ suite('NativeExtensionsScanerService Test', () => {
extensionsPath: userExtensionsLocation.fsPath,
});
instantiationService.stub(IProductService, { version: '1.66.0' });
- const uriIdentityService = new UriIdentityService(fileService);
- const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, logService);
- instantiationService.stub(IExtensionsProfileScannerService, new ExtensionsProfileScannerService(fileService, uriIdentityService, userDataProfilesService, logService));
- instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, logService));
+ instantiationService.stub(IExtensionsProfileScannerService, new ExtensionsProfileScannerService(fileService, logService));
+ instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, new UriIdentityService(fileService), logService));
await fileService.createFolder(systemExtensionsLocation);
await fileService.createFolder(userExtensionsLocation);
});
diff --git a/src/vs/platform/extensions/electron-main/extensionHostStarter.ts b/src/vs/platform/extensions/electron-main/extensionHostStarter.ts
index ca52229c320..832a28daaa0 100644
--- a/src/vs/platform/extensions/electron-main/extensionHostStarter.ts
+++ b/src/vs/platform/extensions/electron-main/extensionHostStarter.ts
@@ -324,6 +324,7 @@ class UtilityExtensionHostProcess extends Disposable {
const modulePath = FileAccess.asFileUri('bootstrap-fork.js', require).fsPath;
const args: string[] = ['--type=extensionHost', '--skipWorkspaceStorageLock'];
const execArgv: string[] = opts.execArgv || [];
+ execArgv.push(`--vscode-utility-kind=extension-host`);
const env: { [key: string]: any } = { ...opts.env };
// Make sure all values are strings, otherwise the process will not start
diff --git a/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts b/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts
index 36b2f65cdfc..660dd5cdcc1 100644
--- a/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts
+++ b/src/vs/platform/files/browser/indexedDBFileSystemProvider.ts
@@ -5,15 +5,15 @@
import { Throttler } from 'vs/base/common/async';
import { VSBuffer } from 'vs/base/common/buffer';
-import { getErrorMessage } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
-import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
+import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { ExtUri } from 'vs/base/common/resources';
-import { isString } from 'vs/base/common/types';
-import { URI, UriComponents } from 'vs/base/common/uri';
+import { isString, UriDto } from 'vs/base/common/types';
+import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { createFileSystemProviderError, FileChangeType, IFileDeleteOptions, IFileOverwriteOptions, FileSystemProviderCapabilities, FileSystemProviderError, FileSystemProviderErrorCode, FileType, IFileWriteOptions, IFileChange, IFileSystemProviderWithFileReadWriteCapability, IStat, IWatchOptions } from 'vs/platform/files/common/files';
import { DBClosedError, IndexedDB } from 'vs/base/browser/indexedDB';
+import { BroadcastDataChannel } from 'vs/base/browser/broadcast';
export type IndexedDBFileSystemProviderErrorDataClassification = {
owner: 'sandy081';
@@ -165,78 +165,6 @@ class IndexedDBFileSystemNode {
}
}
-type FileChangeDto = {
- readonly type: FileChangeType;
- readonly resource: UriComponents;
-};
-
-class IndexedDBChangesBroadcastChannel extends Disposable {
-
- private broadcastChannel: BroadcastChannel | undefined;
-
- private readonly _onDidFileChanges = this._register(new Emitter<readonly IFileChange[]>());
- readonly onDidFileChanges: Event<readonly IFileChange[]> = this._onDidFileChanges.event;
-
- constructor(private readonly changesKey: string) {
- super();
-
- // Use BroadcastChannel
- if ('BroadcastChannel' in window) {
- try {
- this.broadcastChannel = new BroadcastChannel(changesKey);
- const listener = (event: MessageEvent) => {
- if (isString(event.data)) {
- this.onDidReceiveChanges(event.data);
- }
- };
- this.broadcastChannel.addEventListener('message', listener);
- this._register(toDisposable(() => {
- if (this.broadcastChannel) {
- this.broadcastChannel.removeEventListener('message', listener);
- this.broadcastChannel.close();
- }
- }));
- } catch (error) {
- console.warn('Error while creating broadcast channel. Falling back to localStorage.', getErrorMessage(error));
- this.createStorageBroadcastChannel(changesKey);
- }
- }
-
- // BroadcastChannel is not supported. Use storage.
- else {
- this.createStorageBroadcastChannel(changesKey);
- }
- }
-
- private createStorageBroadcastChannel(changesKey: string): void {
- const listener = (event: StorageEvent) => {
- if (event.key === changesKey && event.newValue) {
- this.onDidReceiveChanges(event.newValue);
- }
- };
- window.addEventListener('storage', listener);
- this._register(toDisposable(() => window.removeEventListener('storage', listener)));
- }
-
- private onDidReceiveChanges(data: string): void {
- try {
- const changesDto: FileChangeDto[] = JSON.parse(data);
- this._onDidFileChanges.fire(changesDto.map(c => ({ type: c.type, resource: URI.revive(c.resource) })));
- } catch (error) {/* ignore*/ }
- }
-
- postChanges(changes: IFileChange[]): void {
- if (this.broadcastChannel) {
- this.broadcastChannel.postMessage(JSON.stringify(changes));
- } else {
- // remove previous changes so that event is triggered even if new changes are same as old changes
- window.localStorage.removeItem(this.changesKey);
- window.localStorage.setItem(this.changesKey, JSON.stringify(changes));
- }
- }
-
-}
-
export class IndexedDBFileSystemProvider extends Disposable implements IFileSystemProviderWithFileReadWriteCapability {
readonly capabilities: FileSystemProviderCapabilities =
@@ -246,14 +174,14 @@ export class IndexedDBFileSystemProvider extends Disposable implements IFileSyst
private readonly extUri = new ExtUri(() => false) /* Case Sensitive */;
- private readonly changesBroadcastChannel: IndexedDBChangesBroadcastChannel | undefined;
+ private readonly changesBroadcastChannel: BroadcastDataChannel<UriDto<IFileChange>[]> | undefined;
private readonly _onDidChangeFile = this._register(new Emitter<readonly IFileChange[]>());
readonly onDidChangeFile: Event<readonly IFileChange[]> = this._onDidChangeFile.event;
private readonly _onReportError = this._register(new Emitter<IndexedDBFileSystemProviderErrorData>());
readonly onReportError = this._onReportError.event;
- private readonly versions = new Map<string, number>();
+ private readonly mtimes = new Map<string, number>();
private cachedFiletree: Promise<IndexedDBFileSystemNode> | undefined;
private writeManyThrottler: Throttler;
@@ -263,8 +191,10 @@ export class IndexedDBFileSystemProvider extends Disposable implements IFileSyst
this.writeManyThrottler = new Throttler();
if (watchCrossWindowChanges) {
- this.changesBroadcastChannel = this._register(new IndexedDBChangesBroadcastChannel(`vscode.indexedDB.${scheme}.changes`));
- this._register(this.changesBroadcastChannel.onDidFileChanges(changes => this._onDidChangeFile.fire(changes)));
+ this.changesBroadcastChannel = this._register(new BroadcastDataChannel<UriDto<IFileChange>[]>(`vscode.indexedDB.${scheme}.changes`));
+ this._register(this.changesBroadcastChannel.onDidReceiveData(changes => {
+ this._onDidChangeFile.fire(changes.map(c => ({ type: c.type, resource: URI.revive(c.resource) })));
+ }));
}
}
@@ -289,7 +219,7 @@ export class IndexedDBFileSystemProvider extends Disposable implements IFileSyst
return {
type: FileType.File,
ctime: 0,
- mtime: this.versions.get(resource.toString()) || 0,
+ mtime: this.mtimes.get(resource.toString()) || 0,
size: entry.size ?? (await this.readFile(resource)).byteLength
};
}
@@ -434,7 +364,7 @@ export class IndexedDBFileSystemProvider extends Disposable implements IFileSyst
}
await this.deleteKeys(toDelete);
(await this.getFiletree()).delete(resource.path);
- toDelete.forEach(key => this.versions.delete(key));
+ toDelete.forEach(key => this.mtimes.delete(key));
this.triggerChanges(toDelete.map(path => ({ resource: resource.with({ path }), type: FileChangeType.DELETED })));
}
@@ -459,7 +389,7 @@ export class IndexedDBFileSystemProvider extends Disposable implements IFileSyst
if (changes.length) {
this._onDidChangeFile.fire(changes);
- this.changesBroadcastChannel?.postChanges(changes);
+ this.changesBroadcastChannel?.postData(changes);
}
}
@@ -487,7 +417,7 @@ export class IndexedDBFileSystemProvider extends Disposable implements IFileSyst
const fileTree = await this.getFiletree();
for (const [resource, content] of files) {
fileTree.add(resource.path, { type: 'file', size: content.byteLength });
- this.versions.set(resource.toString(), (this.versions.get(resource.toString()) || 0) + 1);
+ this.mtimes.set(resource.toString(), Date.now());
}
this.triggerChanges(files.map(([resource]) => ({ resource, type: FileChangeType.UPDATED })));
diff --git a/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts b/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts
index 56509eb740f..66b0bc116eb 100644
--- a/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts
+++ b/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as parcelWatcher from '@parcel/watcher';
-import { existsSync, unlinkSync } from 'fs';
+import { existsSync, statSync, unlinkSync } from 'fs';
import { tmpdir } from 'os';
import { DeferredPromise, RunOnceScheduler, ThrottledWorker } from 'vs/base/common/async';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
@@ -653,7 +653,7 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher {
}
}
- protected normalizeRequests(requests: IRecursiveWatchRequest[]): IRecursiveWatchRequest[] {
+ protected normalizeRequests(requests: IRecursiveWatchRequest[], validatePaths = true): IRecursiveWatchRequest[] {
const requestTrie = TernarySearchTree.forPaths<IRecursiveWatchRequest>(!isLinux);
// Sort requests by path length to have shortest first
@@ -674,16 +674,35 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher {
continue; // path is ignored entirely (via `**` glob exclude)
}
+ // Check for overlapping requests
if (requestTrie.findSubstr(request.path)) {
try {
const realpath = realpathSync(request.path);
if (realpath === request.path) {
this.trace(`ignoring a path for watching who's parent is already watched: ${request.path}`);
- continue; // path is not a symbolic link or similar
+ continue;
}
} catch (error) {
- continue; // invalid path - ignore from watching
+ this.trace(`ignoring a path for watching who's realpath failed to resolve: ${request.path} (error: ${error})`);
+
+ continue;
+ }
+ }
+
+ // Check for invalid paths
+ if (validatePaths) {
+ try {
+ const stat = statSync(request.path);
+ if (!stat.isDirectory()) {
+ this.trace(`ignoring a path for watching that is a file and not a folder: ${request.path}`);
+
+ continue;
+ }
+ } catch (error) {
+ this.trace(`ignoring a path for watching who's stat info failed to resolve: ${request.path} (error: ${error})`);
+
+ continue;
}
}
diff --git a/src/vs/platform/files/test/node/parcelWatcher.integrationTest.ts b/src/vs/platform/files/test/node/parcelWatcher.integrationTest.ts
index 052e2ffb603..3196a4b6271 100644
--- a/src/vs/platform/files/test/node/parcelWatcher.integrationTest.ts
+++ b/src/vs/platform/files/test/node/parcelWatcher.integrationTest.ts
@@ -33,7 +33,7 @@ import { ltrim } from 'vs/base/common/strings';
return { path, excludes, recursive: true };
});
- return this.normalizeRequests(requests).map(request => request.path);
+ return this.normalizeRequests(requests, false /* validate paths skipped for tests */).map(request => request.path);
}
override async watch(requests: IRecursiveWatchRequest[]): Promise<void> {
@@ -155,7 +155,7 @@ import { ltrim } from 'vs/base/common/strings';
}
test('basics', async function () {
- await watcher.watch([{ path: testDir, excludes: [], recursive: true }]); //
+ await watcher.watch([{ path: testDir, excludes: [], recursive: true }]);
// New file
const newFilePath = join(testDir, 'deep', 'newFile.txt');
@@ -430,6 +430,16 @@ import { ltrim } from 'vs/base/common/strings';
await changeFuture;
});
+ test('invalid path does not crash watcher', async function () {
+ await watcher.watch([
+ { path: testDir, excludes: [], recursive: true },
+ { path: join(testDir, 'invalid-folder'), excludes: [], recursive: true },
+ { path: __filename, excludes: [], recursive: true }
+ ]);
+
+ return basicCrudTest(join(testDir, 'deep', 'newFile.txt'));
+ });
+
test('subsequent watch updates watchers (excludes)', async function () {
await watcher.watch([{ path: testDir, excludes: [realpathSync(testDir)], recursive: true }]);
await watcher.watch([{ path: testDir, excludes: [], recursive: true }]);
diff --git a/src/vs/platform/instantiation/common/instantiationService.ts b/src/vs/platform/instantiation/common/instantiationService.ts
index b4253b7765c..1f6bba77e24 100644
--- a/src/vs/platform/instantiation/common/instantiationService.ts
+++ b/src/vs/platform/instantiation/common/instantiationService.ts
@@ -258,7 +258,7 @@ export class InstantiationService implements IInstantiationService {
private _throwIfStrict(msg: string, printWarning: boolean): void {
if (printWarning) {
- console.warn(printWarning);
+ console.warn(msg);
}
if (this._strict) {
throw new Error(msg);
diff --git a/src/vs/platform/issue/common/issue.ts b/src/vs/platform/issue/common/issue.ts
index 550c83be0d9..cbc4d251934 100644
--- a/src/vs/platform/issue/common/issue.ts
+++ b/src/vs/platform/issue/common/issue.ts
@@ -59,6 +59,7 @@ export interface IssueReporterData extends WindowData {
extensionId?: string;
experiments?: string;
restrictedMode: boolean;
+ isUnsupported: boolean;
githubAccessToken: string;
readonly issueTitle?: string;
readonly issueBody?: string;
diff --git a/src/vs/platform/languagePacks/common/languagePacks.ts b/src/vs/platform/languagePacks/common/languagePacks.ts
index 146cbce7401..098cbe3a471 100644
--- a/src/vs/platform/languagePacks/common/languagePacks.ts
+++ b/src/vs/platform/languagePacks/common/languagePacks.ts
@@ -22,6 +22,7 @@ export interface ILanguagePackService {
readonly _serviceBrand: undefined;
getAvailableLanguages(): Promise<Array<ILanguagePackItem>>;
getInstalledLanguages(): Promise<Array<ILanguagePackItem>>;
+ getLocale(extension: IGalleryExtension): string | undefined;
}
export abstract class LanguagePackBaseService extends Disposable implements ILanguagePackService {
@@ -51,7 +52,7 @@ export abstract class LanguagePackBaseService extends Disposable implements ILan
const languagePackExtensions = result.firstPage.filter(e => e.properties.localizedLanguages?.length && e.tags.some(t => t.startsWith('lp-')));
const allFromMarketplace: ILanguagePackItem[] = languagePackExtensions.map(lp => {
const languageName = lp.properties.localizedLanguages?.[0];
- const locale = lp.tags.find(t => t.startsWith('lp-'))!.split('lp-')[1];
+ const locale = this.getLocale(lp)!;
const baseQuickPick = this.createQuickPickItem({ locale, label: languageName });
return {
...baseQuickPick,
@@ -68,6 +69,10 @@ export abstract class LanguagePackBaseService extends Disposable implements ILan
return allFromMarketplace;
}
+ getLocale(extension: IGalleryExtension): string | undefined {
+ return extension.tags.find(t => t.startsWith('lp-'))?.split('lp-')[1];
+ }
+
protected createQuickPickItem(languageItem: { locale: string; label?: string | undefined }): IQuickPickItem {
const label = languageItem.label ?? languageItem.locale;
let description: string | undefined = languageItem.locale !== languageItem.label ? languageItem.locale : undefined;
diff --git a/src/vs/platform/native/common/native.ts b/src/vs/platform/native/common/native.ts
index 5f6937e8655..5698299eb3c 100644
--- a/src/vs/platform/native/common/native.ts
+++ b/src/vs/platform/native/common/native.ts
@@ -3,6 +3,7 @@
* 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 { URI } from 'vs/base/common/uri';
import { MessageBoxOptions, MessageBoxReturnValue, MouseInputEvent, OpenDevToolsOptions, OpenDialogOptions, OpenDialogReturnValue, SaveDialogOptions, SaveDialogReturnValue } from 'vs/base/parts/sandbox/common/electronTypes';
@@ -121,8 +122,8 @@ export interface ICommonNativeHostService {
writeClipboardText(text: string, type?: 'selection' | 'clipboard'): Promise<void>;
readClipboardFindText(): Promise<string>;
writeClipboardFindText(text: string): Promise<void>;
- writeClipboardBuffer(format: string, buffer: Uint8Array, type?: 'selection' | 'clipboard'): Promise<void>;
- readClipboardBuffer(format: string): Promise<Uint8Array>;
+ writeClipboardBuffer(format: string, buffer: VSBuffer, type?: 'selection' | 'clipboard'): Promise<void>;
+ readClipboardBuffer(format: string): Promise<VSBuffer>;
hasClipboard(format: string, type?: 'selection' | 'clipboard'): Promise<boolean>;
// macOS Touchbar
diff --git a/src/vs/platform/native/electron-main/nativeHostMainService.ts b/src/vs/platform/native/electron-main/nativeHostMainService.ts
index 639c6cfa063..3cfca907766 100644
--- a/src/vs/platform/native/electron-main/nativeHostMainService.ts
+++ b/src/vs/platform/native/electron-main/nativeHostMainService.ts
@@ -39,6 +39,7 @@ import { IColorScheme, IOpenedWindow, IOpenEmptyWindowOptions, IOpenWindowOption
import { IWindowsMainService, OpenContext } from 'vs/platform/windows/electron-main/windows';
import { isWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace';
import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService';
+import { VSBuffer } from 'vs/base/common/buffer';
export interface INativeHostMainService extends AddFirstParameterToFunctions<ICommonNativeHostService, Promise<unknown> /* only methods, not events */, number | undefined /* window ID */> { }
@@ -603,12 +604,12 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
return clipboard.writeFindText(text);
}
- async writeClipboardBuffer(windowId: number | undefined, format: string, buffer: Uint8Array, type?: 'selection' | 'clipboard'): Promise<void> {
- return clipboard.writeBuffer(format, Buffer.from(buffer), type);
+ async writeClipboardBuffer(windowId: number | undefined, format: string, buffer: VSBuffer, type?: 'selection' | 'clipboard'): Promise<void> {
+ return clipboard.writeBuffer(format, Buffer.from(buffer.buffer), type);
}
- async readClipboardBuffer(windowId: number | undefined, format: string): Promise<Uint8Array> {
- return clipboard.readBuffer(format);
+ async readClipboardBuffer(windowId: number | undefined, format: string): Promise<VSBuffer> {
+ return VSBuffer.wrap(clipboard.readBuffer(format));
}
async hasClipboard(windowId: number | undefined, format: string, type?: 'selection' | 'clipboard'): Promise<boolean> {
diff --git a/src/vs/platform/storage/electron-sandbox/storageService.ts b/src/vs/platform/storage/electron-sandbox/storageService.ts
index 9a27bd23135..d5bf2548f20 100644
--- a/src/vs/platform/storage/electron-sandbox/storageService.ts
+++ b/src/vs/platform/storage/electron-sandbox/storageService.ts
@@ -16,30 +16,24 @@ import { IAnyWorkspaceIdentifier, IEmptyWorkspaceIdentifier, ISingleFolderWorksp
export class NativeStorageService extends AbstractStorageService {
- private readonly applicationStorage: IStorage;
- private readonly applicationStorageProfile: IUserDataProfile;
+ private readonly applicationStorageProfile = this.initialProfiles.defaultProfile;
+ private readonly applicationStorage = this.createApplicationStorage();
- private profileStorage: IStorage;
- private profileStorageProfile: IUserDataProfile | undefined = undefined;
+ private profileStorageProfile = this.initialProfiles.currentProfile;
private readonly profileStorageDisposables = this._register(new DisposableStore());
+ private profileStorage = this.createProfileStorage(this.profileStorageProfile);
- private workspaceStorage: IStorage | undefined = undefined;
- private workspaceStorageId: string | undefined = undefined;
+ private workspaceStorageId = this.initialWorkspace?.id;
private readonly workspaceStorageDisposables = this._register(new DisposableStore());
+ private workspaceStorage = this.createWorkspaceStorage(this.initialWorkspace);
constructor(
- workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier | undefined,
- { defaultProfile, currentProfile }: { defaultProfile: IUserDataProfile; currentProfile: IUserDataProfile },
+ private readonly initialWorkspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier | undefined,
+ private readonly initialProfiles: { defaultProfile: IUserDataProfile; currentProfile: IUserDataProfile },
private readonly mainProcessService: IMainProcessService,
private readonly environmentService: IEnvironmentService
) {
super();
-
- this.applicationStorageProfile = defaultProfile;
-
- this.applicationStorage = this.createApplicationStorage();
- this.profileStorage = this.createProfileStorage(currentProfile);
- this.workspaceStorage = this.createWorkspaceStorage(workspace);
}
private createApplicationStorage(): IStorage {
@@ -148,7 +142,7 @@ export class NativeStorageService 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;
}
diff --git a/src/vs/platform/telemetry/browser/1dsAppender.ts b/src/vs/platform/telemetry/browser/1dsAppender.ts
index 93bf07cdbb6..5075fdfc7df 100644
--- a/src/vs/platform/telemetry/browser/1dsAppender.ts
+++ b/src/vs/platform/telemetry/browser/1dsAppender.ts
@@ -4,16 +4,18 @@
*--------------------------------------------------------------------------------------------*/
import type { AppInsightsCore } from '@microsoft/1ds-core-js';
+import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { AbstractOneDataSystemAppender } from 'vs/platform/telemetry/common/1dsAppender';
export class OneDataSystemWebAppender extends AbstractOneDataSystemAppender {
constructor(
+ configurationService: IConfigurationService | undefined,
eventPrefix: string,
defaultData: { [key: string]: any } | null,
iKeyOrClientFactory: string | (() => AppInsightsCore), // allow factory function for testing
) {
- super(eventPrefix, defaultData, iKeyOrClientFactory);
+ super(configurationService, eventPrefix, defaultData, iKeyOrClientFactory);
// If we cannot fetch the endpoint it means it is down and we should not send any telemetry.
// This is most likely due to ad blockers
diff --git a/src/vs/platform/telemetry/browser/appInsightsAppender.ts b/src/vs/platform/telemetry/browser/appInsightsAppender.ts
deleted file mode 100644
index cd4b32c13af..00000000000
--- a/src/vs/platform/telemetry/browser/appInsightsAppender.ts
+++ /dev/null
@@ -1,77 +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 type { ApplicationInsights } from '@microsoft/applicationinsights-web';
-import { ITelemetryAppender, validateTelemetryData } from 'vs/platform/telemetry/common/telemetryUtils';
-
-export class WebAppInsightsAppender implements ITelemetryAppender {
- private _aiClient: ApplicationInsights | undefined;
- private _aiClientLoaded = false;
- private _telemetryCache: { eventName: string; data: any }[] = [];
-
- constructor(private _eventPrefix: string, aiKey: string) {
- const endpointUrl = 'https://vscode.vortex.data.microsoft.com/collect/v1';
- import('@microsoft/applicationinsights-web').then(aiLibrary => {
- this._aiClient = new aiLibrary.ApplicationInsights({
- config: {
- instrumentationKey: aiKey,
- endpointUrl,
- disableAjaxTracking: true,
- disableExceptionTracking: true,
- disableFetchTracking: true,
- disableCorrelationHeaders: true,
- disableCookiesUsage: true,
- autoTrackPageVisitTime: false,
- emitLineDelimitedJson: true,
- },
- });
- this._aiClient.loadAppInsights();
- // Client is loaded we can now flush the cached events
- this._aiClientLoaded = true;
- this._telemetryCache.forEach(cacheEntry => this.log(cacheEntry.eventName, cacheEntry.data));
- this._telemetryCache = [];
-
- // If we cannot access the endpoint this most likely means it's being blocked
- // and we should not attempt to send any telemetry.
- fetch(endpointUrl, { method: 'POST' }).catch(() => (this._aiClient = undefined));
- }).catch(err => {
- console.error(err);
- });
- }
-
- /**
- * Logs a telemetry event with eventName and data
- * @param eventName The event name
- * @param data The data associated with the events
- */
- public log(eventName: string, data: any): void {
- if (!this._aiClient && this._aiClientLoaded) {
- return;
- } else if (!this._aiClient && !this._aiClientLoaded) {
- this._telemetryCache.push({ eventName, data });
- return;
- }
-
- data = validateTelemetryData(data);
-
- // Web does not expect properties and measurements so we must
- // spread them out. This is different from desktop which expects them
- data = { ...data.properties, ...data.measurements };
-
- // undefined assertion is ok since above two if statements cover both cases
- this._aiClient!.trackEvent({ name: this._eventPrefix + '/' + eventName }, data);
- }
-
- /**
- * Flushes all the telemetry data still in the buffer
- */
- public flush(): Promise<any> {
- if (this._aiClient) {
- this._aiClient.flush();
- this._aiClient = undefined;
- }
- return Promise.resolve(undefined);
- }
-}
diff --git a/src/vs/platform/telemetry/common/1dsAppender.ts b/src/vs/platform/telemetry/common/1dsAppender.ts
index e2ea5b42fdc..5dd4975c9e1 100644
--- a/src/vs/platform/telemetry/common/1dsAppender.ts
+++ b/src/vs/platform/telemetry/common/1dsAppender.ts
@@ -7,11 +7,12 @@ import type { AppInsightsCore, IExtendedConfiguration } from '@microsoft/1ds-cor
import type { IChannelConfiguration, IXHROverride, PostChannel } from '@microsoft/1ds-post-js';
import { onUnexpectedError } from 'vs/base/common/errors';
import { mixin } from 'vs/base/common/objects';
+import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ITelemetryAppender, validateTelemetryData } from 'vs/platform/telemetry/common/telemetryUtils';
const endpointUrl = 'https://mobile.events.data.microsoft.com/OneCollector/1.0';
-async function getClient(instrumentationKey: string, xhrOverride?: IXHROverride): Promise<AppInsightsCore> {
+async function getClient(instrumentationKey: string, addInternalFlag?: boolean, xhrOverride?: IXHROverride): Promise<AppInsightsCore> {
const oneDs = await import('@microsoft/1ds-core-js');
const postPlugin = await import('@microsoft/1ds-post-js');
const appInsightsCore = new oneDs.AppInsightsCore();
@@ -43,10 +44,12 @@ async function getClient(instrumentationKey: string, xhrOverride?: IXHROverride)
appInsightsCore.initialize(coreConfig, []);
appInsightsCore.addTelemetryInitializer((envelope) => {
- envelope['ext'] = envelope['ext'] ?? {};
- envelope['ext']['utc'] = envelope['ext']['utc'] ?? {};
- // Sets it to be internal only based on Windows UTC flagging
- envelope['ext']['utc']['flags'] = 0x0000811ECD;
+ if (addInternalFlag) {
+ envelope['ext'] = envelope['ext'] ?? {};
+ envelope['ext']['utc'] = envelope['ext']['utc'] ?? {};
+ // Sets it to be internal only based on Windows UTC flagging
+ envelope['ext']['utc']['flags'] = 0x0000811ECD;
+ }
});
return appInsightsCore;
@@ -60,6 +63,7 @@ export abstract class AbstractOneDataSystemAppender implements ITelemetryAppende
protected readonly endPointUrl = endpointUrl;
constructor(
+ private readonly _configurationService: IConfigurationService | undefined,
private _eventPrefix: string,
private _defaultData: { [key: string]: any } | null,
iKeyOrClientFactory: string | (() => AppInsightsCore), // allow factory function for testing
@@ -88,7 +92,8 @@ export abstract class AbstractOneDataSystemAppender implements ITelemetryAppende
}
if (!this._asyncAiCore) {
- this._asyncAiCore = getClient(this._aiCoreOrKey, this._xhrOverride);
+ const isInternal = this._configurationService?.getValue<boolean>('telemetry.internalTesting');
+ this._asyncAiCore = getClient(this._aiCoreOrKey, isInternal, this._xhrOverride);
}
this._asyncAiCore.then(
@@ -111,10 +116,13 @@ export abstract class AbstractOneDataSystemAppender implements ITelemetryAppende
const name = this._eventPrefix + '/' + eventName;
try {
- this._withAIClient((aiClient) => aiClient.track({
- name,
- baseData: { name, properties: data?.properties, measurements: data?.measurements }
- }));
+ this._withAIClient((aiClient) => {
+ aiClient.pluginVersionString = data?.properties.version ?? 'Unknown';
+ aiClient.track({
+ name,
+ baseData: { name, properties: data?.properties, measurements: data?.measurements }
+ });
+ });
} catch { }
}
diff --git a/src/vs/platform/telemetry/node/1dsAppender.ts b/src/vs/platform/telemetry/node/1dsAppender.ts
index 5721843fac1..c7b6442cf7a 100644
--- a/src/vs/platform/telemetry/node/1dsAppender.ts
+++ b/src/vs/platform/telemetry/node/1dsAppender.ts
@@ -6,12 +6,14 @@
import type { AppInsightsCore } from '@microsoft/1ds-core-js';
import type { IPayloadData, IXHROverride } from '@microsoft/1ds-post-js';
import * as https from 'https';
+import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { AbstractOneDataSystemAppender } from 'vs/platform/telemetry/common/1dsAppender';
export class OneDataSystemAppender extends AbstractOneDataSystemAppender {
constructor(
+ configurationService: IConfigurationService | undefined,
eventPrefix: string,
defaultData: { [key: string]: any } | null,
iKeyOrClientFactory: string | (() => AppInsightsCore), // allow factory function for testing
@@ -46,6 +48,6 @@ export class OneDataSystemAppender extends AbstractOneDataSystemAppender {
}
};
- super(eventPrefix, defaultData, iKeyOrClientFactory, customHttpXHROverride);
+ super(configurationService, eventPrefix, defaultData, iKeyOrClientFactory, customHttpXHROverride);
}
}
diff --git a/src/vs/platform/telemetry/node/appInsightsAppender.ts b/src/vs/platform/telemetry/node/appInsightsAppender.ts
deleted file mode 100644
index d5fc5987438..00000000000
--- a/src/vs/platform/telemetry/node/appInsightsAppender.ts
+++ /dev/null
@@ -1,121 +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 type { TelemetryClient } from 'applicationinsights';
-import { onUnexpectedError } from 'vs/base/common/errors';
-import { mixin } from 'vs/base/common/objects';
-import { ITelemetryAppender, validateTelemetryData } from 'vs/platform/telemetry/common/telemetryUtils';
-
-async function getClient(aiKey: string): Promise<TelemetryClient> {
- const appInsights = await import('applicationinsights');
- let client: TelemetryClient;
- if (appInsights.defaultClient) {
- client = new appInsights.TelemetryClient(aiKey);
- client.channel.setUseDiskRetryCaching(true);
- } else {
- appInsights.setup(aiKey)
- .setAutoCollectRequests(false)
- .setAutoCollectPerformance(false)
- .setAutoCollectExceptions(false)
- .setAutoCollectDependencies(false)
- .setAutoDependencyCorrelation(false)
- .setAutoCollectConsole(false)
- .setInternalLogging(false, false)
- .setUseDiskRetryCaching(true)
- .start();
- client = appInsights.defaultClient;
- }
-
- if (aiKey.indexOf('AIF-') === 0) {
- client.config.endpointUrl = 'https://mobile.events.data.microsoft.com/collect/v1';
- }
- return client;
-}
-
-
-export class AppInsightsAppender implements ITelemetryAppender {
-
- private _aiClient: string | TelemetryClient | undefined;
- private _asyncAIClient: Promise<TelemetryClient> | null;
-
- constructor(
- private _eventPrefix: string,
- private _defaultData: { [key: string]: any } | null,
- aiKeyOrClientFactory: string | (() => TelemetryClient), // allow factory function for testing
- ) {
- if (!this._defaultData) {
- this._defaultData = Object.create(null);
- }
-
- if (typeof aiKeyOrClientFactory === 'function') {
- this._aiClient = aiKeyOrClientFactory();
- } else {
- this._aiClient = aiKeyOrClientFactory;
- }
- this._asyncAIClient = null;
- }
-
- private _withAIClient(callback: (aiClient: TelemetryClient) => void): void {
- if (!this._aiClient) {
- return;
- }
-
- if (typeof this._aiClient !== 'string') {
- callback(this._aiClient);
- return;
- }
-
- if (!this._asyncAIClient) {
- this._asyncAIClient = getClient(this._aiClient);
- }
-
- this._asyncAIClient.then(
- (aiClient) => {
- callback(aiClient);
- },
- (err) => {
- onUnexpectedError(err);
- console.error(err);
- }
- );
- }
-
- log(eventName: string, data?: any): void {
- if (!this._aiClient) {
- return;
- }
- data = mixin(data, this._defaultData);
- data = validateTelemetryData(data);
-
- // Attemps to suppress https://github.com/microsoft/vscode/issues/140624
- try {
- this._withAIClient((aiClient) => aiClient.trackEvent({
- name: this._eventPrefix + '/' + eventName,
- properties: data.properties,
- measurements: data.measurements
- }));
- } catch { }
- }
-
- flush(): Promise<any> {
- if (this._aiClient) {
- return new Promise(resolve => {
- this._withAIClient((aiClient) => {
- // Attempts to suppress https://github.com/microsoft/vscode/issues/140624
- try {
- aiClient.flush({
- callback: () => {
- // all data flushed
- this._aiClient = undefined;
- resolve(undefined);
- }
- });
- } catch { }
- });
- });
- }
- return Promise.resolve(undefined);
- }
-}
diff --git a/src/vs/platform/telemetry/test/electron-browser/appInsightsAppender.test.ts b/src/vs/platform/telemetry/test/browser/1dsAppender.test.ts
index 09f1f611da9..0c0378845ab 100644
--- a/src/vs/platform/telemetry/test/electron-browser/appInsightsAppender.test.ts
+++ b/src/vs/platform/telemetry/test/browser/1dsAppender.test.ts
@@ -2,23 +2,22 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { Contracts, TelemetryClient } from 'applicationinsights';
+import { AppInsightsCore } from '@microsoft/1ds-core-js';
import * as assert from 'assert';
-import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender';
+import { OneDataSystemWebAppender } from 'vs/platform/telemetry/browser/1dsAppender';
-class AppInsightsMock extends TelemetryClient {
+class AppInsightsCoreMock extends AppInsightsCore {
public override config: any;
- public override channel: any;
- public events: Contracts.EventTelemetry[] = [];
+ public events: any[] = [];
public IsTrackingPageView: boolean = false;
public exceptions: any[] = [];
constructor() {
- super('testKey');
+ super();
}
- public override trackEvent(event: any) {
- this.events.push(event);
+ public override track(event: any) {
+ this.events.push(event.baseData);
}
public override flush(options: any): void {
@@ -27,14 +26,14 @@ class AppInsightsMock extends TelemetryClient {
}
suite('AIAdapter', () => {
- let appInsightsMock: AppInsightsMock;
- let adapter: AppInsightsAppender;
+ let appInsightsMock: AppInsightsCoreMock;
+ let adapter: OneDataSystemWebAppender;
const prefix = 'prefix';
setup(() => {
- appInsightsMock = new AppInsightsMock();
- adapter = new AppInsightsAppender(prefix, undefined!, () => appInsightsMock);
+ appInsightsMock = new AppInsightsCoreMock();
+ adapter = new OneDataSystemWebAppender(undefined, prefix, undefined!, () => appInsightsMock);
});
teardown(() => {
@@ -49,7 +48,7 @@ suite('AIAdapter', () => {
});
test('addional data', () => {
- adapter = new AppInsightsAppender(prefix, { first: '1st', second: 2, third: true }, () => appInsightsMock);
+ adapter = new OneDataSystemWebAppender(undefined, prefix, { first: '1st', second: 2, third: true }, () => appInsightsMock);
adapter.log('testEvent');
assert.strictEqual(appInsightsMock.events.length, 1);
diff --git a/src/vs/platform/terminal/common/capabilities/capabilities.ts b/src/vs/platform/terminal/common/capabilities/capabilities.ts
index d398dccd0ac..1cfbab58a30 100644
--- a/src/vs/platform/terminal/common/capabilities/capabilities.ts
+++ b/src/vs/platform/terminal/common/capabilities/capabilities.ts
@@ -198,7 +198,7 @@ export interface ITerminalCommand {
executedMarker?: IXtermMarker;
commandStartLineContent?: string;
getOutput(): string | undefined;
- hasOutput: boolean;
+ hasOutput(): boolean;
genericMarkProperties?: IGenericMarkProperties;
}
diff --git a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts
index 95aa53d305c..7194815abb1 100644
--- a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts
+++ b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts
@@ -457,7 +457,7 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
cwd: this._cwd,
exitCode: this._exitCode,
commandStartLineContent: this._currentCommand.commandStartLineContent,
- hasOutput: !!(executedMarker && endMarker && executedMarker?.line < endMarker!.line),
+ hasOutput: () => !executedMarker?.isDisposed && !endMarker?.isDisposed && !!(executedMarker && endMarker && executedMarker?.line < endMarker!.line),
getOutput: () => getOutputForCommand(executedMarker, endMarker, buffer),
genericMarkProperties: options?.genericMarkProperties
};
@@ -579,7 +579,7 @@ export class CommandDetectionCapability implements ICommandDetectionCapability {
cwd: e.cwd,
commandStartLineContent: e.commandStartLineContent,
exitCode: e.exitCode,
- hasOutput: !!(executedMarker && endMarker && executedMarker.line < endMarker.line),
+ hasOutput: () => !executedMarker?.isDisposed && !endMarker?.isDisposed && !!(executedMarker && endMarker && executedMarker.line < endMarker.line),
getOutput: () => getOutputForCommand(executedMarker, endMarker, buffer),
genericMarkProperties: e.genericMarkProperties
};
diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts
index 008d1e468ad..bc20c79294c 100644
--- a/src/vs/platform/terminal/common/terminal.ts
+++ b/src/vs/platform/terminal/common/terminal.ts
@@ -40,6 +40,8 @@ export const enum TerminalSettingId {
DefaultProfileMacOs = 'terminal.integrated.defaultProfile.osx',
DefaultProfileWindows = 'terminal.integrated.defaultProfile.windows',
UseWslProfiles = 'terminal.integrated.useWslProfiles',
+ TabsDefaultColor = 'terminal.integrated.tabs.defaultColor',
+ TabsDefaultIcon = 'terminal.integrated.tabs.defaultIcon',
TabsEnabled = 'terminal.integrated.tabs.enabled',
TabsEnableAnimation = 'terminal.integrated.tabs.enableAnimation',
TabsHideCondition = 'terminal.integrated.tabs.hideCondition',
@@ -313,6 +315,7 @@ export interface IPtyService extends IPtyHostController {
getProfiles?(workspaceId: string, profiles: unknown, defaultProfile: unknown, includeDetectedProfiles?: boolean): Promise<ITerminalProfile[]>;
getEnvironment(): Promise<IProcessEnvironment>;
getWslPath(original: string): Promise<string>;
+ getRevivedPtyNewId(id: number): Promise<number | undefined>;
setTerminalLayoutInfo(args: ISetTerminalLayoutInfoArgs): Promise<void>;
getTerminalLayoutInfo(args: IGetTerminalLayoutInfoArgs): Promise<ITerminalsLayoutInfo | undefined>;
reduceConnectionGraceTime(): Promise<void>;
@@ -440,11 +443,11 @@ export interface IShellLaunchConfig {
/**
* A string including ANSI escape sequences that will be written to the terminal emulator
- * _before_ the terminal process has launched, a trailing \n is added at the end of the string.
- * This allows for example the terminal instance to display a styled message as the first line
- * of the terminal. Use \x1b over \033 or \e for the escape control character.
+ * _before_ the terminal process has launched, when a string is specified, a trailing \n is
+ * added at the end. This allows for example the terminal instance to display a styled message
+ * as the first line of the terminal. Use \x1b over \033 or \e for the escape control character.
*/
- initialText?: string;
+ initialText?: string | { text: string; trailingNewLine: boolean };
/**
* Custom PTY/pseudoterminal process to use.
@@ -459,7 +462,7 @@ export interface IShellLaunchConfig {
/**
* This is a terminal that attaches to an already running terminal.
*/
- attachPersistentProcess?: { id: number; pid: number; title: string; titleSource: TitleEventSource; cwd: string; icon?: TerminalIcon; color?: string; hasChildProcesses?: boolean; fixedDimensions?: IFixedTerminalDimensions; environmentVariableCollections?: ISerializableEnvironmentVariableCollections };
+ attachPersistentProcess?: { id: number; findRevivedId?: boolean; pid: number; title: string; titleSource: TitleEventSource; cwd: string; icon?: TerminalIcon; color?: string; hasChildProcesses?: boolean; fixedDimensions?: IFixedTerminalDimensions; environmentVariableCollections?: ISerializableEnvironmentVariableCollections };
/**
* Whether the terminal process environment should be exactly as provided in
@@ -782,6 +785,27 @@ export type ITerminalProfileObject = ITerminalExecutable | ITerminalProfileSourc
export type ITerminalProfileType = ITerminalProfile | IExtensionTerminalProfile;
export interface IShellIntegration {
- capabilities: ITerminalCapabilityStore;
+ readonly capabilities: ITerminalCapabilityStore;
+ readonly status: ShellIntegrationStatus;
+
+ readonly onDidChangeStatus: Event<ShellIntegrationStatus>;
+
deserialize(serialized: ISerializedCommandDetectionCapability): void;
}
+
+export const enum ShellIntegrationStatus {
+ /** No shell integration sequences have been encountered. */
+ Off,
+ /** Final term shell integration sequences have been encountered. */
+ FinalTerm,
+ /** VS Code shell integration sequences have been encountered. Supercedes FinalTerm. */
+ VSCode
+}
+
+export enum TerminalExitReason {
+ Unknown = 0,
+ Shutdown = 1,
+ Process = 2,
+ User = 3,
+ Extension = 4,
+}
diff --git a/src/vs/platform/terminal/common/terminalEnvironment.ts b/src/vs/platform/terminal/common/terminalEnvironment.ts
index 66b5ad31a3f..1d24a24f60d 100644
--- a/src/vs/platform/terminal/common/terminalEnvironment.ts
+++ b/src/vs/platform/terminal/common/terminalEnvironment.ts
@@ -12,3 +12,26 @@ export function escapeNonWindowsPath(path: string): string {
newPath = newPath.replace(bannedChars, '');
return `'${newPath}'`;
}
+
+/**
+ * Collapses the user's home directory into `~` if it exists within the path, this gives a shorter
+ * path that is more suitable within the context of a terminal.
+ */
+export function collapseTildePath(path: string | undefined, userHome: string | undefined, separator: string): string {
+ if (!path) {
+ return '';
+ }
+ if (!userHome) {
+ return path;
+ }
+ // Trim the trailing separator from the end if it exists
+ if (userHome.match(/[\/\\]$/)) {
+ userHome = userHome.slice(0, userHome.length - 1);
+ }
+ const normalizedPath = path.replace(/\\/g, '/').toLowerCase();
+ const normalizedUserHome = userHome.replace(/\\/g, '/').toLowerCase();
+ if (!normalizedPath.includes(normalizedUserHome)) {
+ return path;
+ }
+ return `~${separator}${path.slice(userHome.length + 1)}`;
+}
diff --git a/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts b/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts
index 9dc40ebec2c..45bad3159a1 100644
--- a/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts
+++ b/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts
@@ -12,6 +12,27 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { IExtensionTerminalProfile, ITerminalProfile, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
import { createProfileSchemaEnums } from 'vs/platform/terminal/common/terminalProfiles';
+export const terminalColorSchema: IJSONSchema = {
+ type: ['string', 'null'],
+ enum: [
+ 'terminal.ansiBlack',
+ 'terminal.ansiRed',
+ 'terminal.ansiGreen',
+ 'terminal.ansiYellow',
+ 'terminal.ansiBlue',
+ 'terminal.ansiMagenta',
+ 'terminal.ansiCyan',
+ 'terminal.ansiWhite'
+ ],
+ default: null
+};
+
+export const terminalIconSchema: IJSONSchema = {
+ type: 'string',
+ enum: Array.from(Codicon.getAll(), icon => icon.id),
+ markdownEnumDescriptions: Array.from(Codicon.getAll(), icon => `$(${icon.id})`),
+};
+
const terminalProfileBaseProperties: IJSONSchemaMap = {
args: {
description: localize('terminalProfile.args', 'An optional set of arguments to run the shell executable with.'),
@@ -25,25 +46,12 @@ const terminalProfileBaseProperties: IJSONSchemaMap = {
type: 'boolean'
},
icon: {
- description: localize('terminalProfile.icon', 'A codicon ID to associate with this terminal.'),
- type: 'string',
- enum: Array.from(Codicon.getAll(), icon => icon.id),
- markdownEnumDescriptions: Array.from(Codicon.getAll(), icon => `$(${icon.id})`),
+ description: localize('terminalProfile.icon', 'A codicon ID to associate with the terminal icon.'),
+ ...terminalIconSchema
},
color: {
- description: localize('terminalProfile.color', 'A theme color ID to associate with this terminal.'),
- type: ['string', 'null'],
- enum: [
- 'terminal.ansiBlack',
- 'terminal.ansiRed',
- 'terminal.ansiGreen',
- 'terminal.ansiYellow',
- 'terminal.ansiBlue',
- 'terminal.ansiMagenta',
- 'terminal.ansiCyan',
- 'terminal.ansiWhite'
- ],
- default: null
+ description: localize('terminalProfile.color', 'A theme color ID to associate with the terminal icon.'),
+ ...terminalColorSchema
},
env: {
markdownDescription: localize('terminalProfile.env', "An object with environment variables that will be added to the terminal profile process. Set to `null` to delete environment variables from the base environment."),
diff --git a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts
index 7a07b5b1b83..46c1390f4df 100644
--- a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts
+++ b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts
@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { IShellIntegration } from 'vs/platform/terminal/common/terminal';
+import { IShellIntegration, ShellIntegrationStatus } from 'vs/platform/terminal/common/terminal';
import { Disposable, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { TerminalCapabilityStore } from 'vs/platform/terminal/common/capabilities/terminalCapabilityStore';
import { CommandDetectionCapability } from 'vs/platform/terminal/common/capabilities/commandDetectionCapability';
@@ -16,6 +16,7 @@ import { ILogService } from 'vs/platform/log/common/log';
import type { ITerminalAddon, Terminal } from 'xterm-headless';
import { ISerializedCommandDetectionCapability } from 'vs/platform/terminal/common/terminalProcess';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
+import { Emitter } from 'vs/base/common/event';
/**
* Shell integration is a feature that enhances the terminal's understanding of what's happening
@@ -137,6 +138,12 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati
private _hasUpdatedTelemetry: boolean = false;
private _activationTimeout: any;
private _commonProtocolDisposables: IDisposable[] = [];
+ private _status: ShellIntegrationStatus = ShellIntegrationStatus.Off;
+
+ get status(): ShellIntegrationStatus { return this._status; }
+
+ private readonly _onDidChangeStatus = new Emitter<ShellIntegrationStatus>();
+ readonly onDidChangeStatus = this._onDidChangeStatus.event;
constructor(
private readonly _disableTelemetry: boolean | undefined,
@@ -167,6 +174,15 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati
}
private _handleFinalTermSequence(data: string): boolean {
+ const didHandle = this._doHandleFinalTermSequence(data);
+ if (this._status === ShellIntegrationStatus.Off) {
+ this._status = ShellIntegrationStatus.FinalTerm;
+ this._onDidChangeStatus.fire(this._status);
+ }
+ return didHandle;
+ }
+
+ private _doHandleFinalTermSequence(data: string): boolean {
if (!this._terminal) {
return false;
}
@@ -204,6 +220,10 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati
this._hasUpdatedTelemetry = true;
this._clearActivationTimeout();
}
+ if (this._status !== ShellIntegrationStatus.VSCode) {
+ this._status = ShellIntegrationStatus.VSCode;
+ this._onDidChangeStatus.fire(this._status);
+ }
return didHandle;
}
diff --git a/src/vs/platform/terminal/node/ptyHostService.ts b/src/vs/platform/terminal/node/ptyHostService.ts
index 5af2d312b6b..3564d49efb4 100644
--- a/src/vs/platform/terminal/node/ptyHostService.ts
+++ b/src/vs/platform/terminal/node/ptyHostService.ts
@@ -296,6 +296,10 @@ export class PtyHostService extends Disposable implements IPtyService {
return this._proxy.getWslPath(original);
}
+ getRevivedPtyNewId(id: number): Promise<number | undefined> {
+ return this._proxy.getRevivedPtyNewId(id);
+ }
+
setTerminalLayoutInfo(args: ISetTerminalLayoutInfoArgs): Promise<void> {
return this._proxy.setTerminalLayoutInfo(args);
}
diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts
index 73a62b44cf7..c45d75bbb12 100644
--- a/src/vs/platform/terminal/node/ptyService.ts
+++ b/src/vs/platform/terminal/node/ptyService.ts
@@ -190,7 +190,7 @@ export class PtyService extends Disposable implements IPtyService {
executableEnv,
options
};
- const persistentProcess = new PersistentTerminalProcess(id, process, workspaceId, workspaceName, shouldPersist, cols, rows, processLaunchOptions, unicodeVersion, this._reconnectConstants, this._logService, isReviving ? shellLaunchConfig.initialText : undefined, rawReviveBuffer, shellLaunchConfig.icon, shellLaunchConfig.color, shellLaunchConfig.name, shellLaunchConfig.fixedDimensions);
+ const persistentProcess = new PersistentTerminalProcess(id, process, workspaceId, workspaceName, shouldPersist, cols, rows, processLaunchOptions, unicodeVersion, this._reconnectConstants, this._logService, isReviving && typeof shellLaunchConfig.initialText === 'string' ? shellLaunchConfig.initialText : undefined, rawReviveBuffer, shellLaunchConfig.icon, shellLaunchConfig.color, shellLaunchConfig.name, shellLaunchConfig.fixedDimensions);
process.onDidChangeProperty(property => this._onDidChangeProperty.fire({ id, property }));
process.onProcessExit(event => {
persistentProcess.dispose();
@@ -337,6 +337,15 @@ export class PtyService extends Disposable implements IPtyService {
});
}
+ async getRevivedPtyNewId(id: number): Promise<number | undefined> {
+ try {
+ return this._revivedPtyIdMap.get(id)?.newId;
+ } catch (e) {
+ this._logService.trace(`Couldn't find terminal ID ${id}`, e.message);
+ }
+ return undefined;
+ }
+
async setTerminalLayoutInfo(args: ISetTerminalLayoutInfoArgs): Promise<void> {
this._workspaceLayoutInfos.set(args.workspaceId, args);
}
diff --git a/src/vs/platform/terminal/test/common/terminalEnvironment.test.ts b/src/vs/platform/terminal/test/common/terminalEnvironment.test.ts
new file mode 100644
index 00000000000..2c58f9ec1fd
--- /dev/null
+++ b/src/vs/platform/terminal/test/common/terminalEnvironment.test.ts
@@ -0,0 +1,40 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { strictEqual } from 'assert';
+import { collapseTildePath } from 'vs/platform/terminal/common/terminalEnvironment';
+
+suite('terminalEnvironment', () => {
+ suite('collapseTildePath', () => {
+ test('should return empty string for a falsy path', () => {
+ strictEqual(collapseTildePath('', '/foo', '/'), '');
+ strictEqual(collapseTildePath(undefined, '/foo', '/'), '');
+ });
+ test('should return path for a falsy user home', () => {
+ strictEqual(collapseTildePath('/foo', '', '/'), '/foo');
+ strictEqual(collapseTildePath('/foo', undefined, '/'), '/foo');
+ });
+ test('should not collapse when user home isn\'t present', () => {
+ strictEqual(collapseTildePath('/foo', '/bar', '/'), '/foo');
+ strictEqual(collapseTildePath('C:\\foo', 'C:\\bar', '\\'), 'C:\\foo');
+ });
+ test('should collapse with Windows separators', () => {
+ strictEqual(collapseTildePath('C:\\foo\\bar', 'C:\\foo', '\\'), '~\\bar');
+ strictEqual(collapseTildePath('C:\\foo\\bar', 'C:\\foo\\', '\\'), '~\\bar');
+ strictEqual(collapseTildePath('C:\\foo\\bar\\baz', 'C:\\foo\\', '\\'), '~\\bar\\baz');
+ strictEqual(collapseTildePath('C:\\foo\\bar\\baz', 'C:\\foo', '\\'), '~\\bar\\baz');
+ });
+ test('should collapse mixed case with Windows separators', () => {
+ strictEqual(collapseTildePath('c:\\foo\\bar', 'C:\\foo', '\\'), '~\\bar');
+ strictEqual(collapseTildePath('C:\\foo\\bar\\baz', 'c:\\foo', '\\'), '~\\bar\\baz');
+ });
+ test('should collapse with Posix separators', () => {
+ strictEqual(collapseTildePath('/foo/bar', '/foo', '/'), '~/bar');
+ strictEqual(collapseTildePath('/foo/bar', '/foo/', '/'), '~/bar');
+ strictEqual(collapseTildePath('/foo/bar/baz', '/foo', '/'), '~/bar/baz');
+ strictEqual(collapseTildePath('/foo/bar/baz', '/foo/', '/'), '~/bar/baz');
+ });
+ });
+});
diff --git a/src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts b/src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts
index f6a9b0d6993..58fd53e21ec 100644
--- a/src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts
+++ b/src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts
@@ -19,6 +19,7 @@ import { AbstractNativeEnvironmentService } from 'vs/platform/environment/common
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import product from 'vs/platform/product/common/product';
import { IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
+import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService';
const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' });
@@ -52,7 +53,7 @@ suite('FileUserDataProvider', () => {
await testObject.createFolder(backupWorkspaceHomeOnDisk);
environmentService = new TestEnvironmentService(userDataHomeOnDisk);
- userDataProfilesService = new UserDataProfilesService(environmentService, testObject, logService);
+ userDataProfilesService = new UserDataProfilesService(environmentService, testObject, new UriIdentityService(testObject), logService);
fileUserDataProvider = new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, logService);
disposables.add(fileUserDataProvider);
diff --git a/src/vs/platform/userDataProfile/browser/userDataProfile.ts b/src/vs/platform/userDataProfile/browser/userDataProfile.ts
new file mode 100644
index 00000000000..b00f89a92b7
--- /dev/null
+++ b/src/vs/platform/userDataProfile/browser/userDataProfile.ts
@@ -0,0 +1,83 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { BroadcastDataChannel } from 'vs/base/browser/broadcast';
+import { revive } from 'vs/base/common/marshalling';
+import { UriDto } from 'vs/base/common/types';
+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 { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
+import { DidChangeProfilesEvent, IUserDataProfile, IUserDataProfilesService, PROFILES_ENABLEMENT_CONFIG, reviveProfile, StoredProfileAssociations, StoredUserDataProfile, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
+
+type BroadcastedProfileChanges = UriDto<Omit<DidChangeProfilesEvent, 'all'>>;
+
+export class BrowserUserDataProfilesService extends UserDataProfilesService implements IUserDataProfilesService {
+
+ protected override readonly defaultProfileShouldIncludeExtensionsResourceAlways: boolean = true;
+ private readonly changesBroadcastChannel: BroadcastDataChannel<BroadcastedProfileChanges>;
+
+ constructor(
+ @IEnvironmentService environmentService: IEnvironmentService,
+ @IFileService fileService: IFileService,
+ @IUriIdentityService uriIdentityService: IUriIdentityService,
+ @ILogService logService: ILogService,
+ ) {
+ super(environmentService, fileService, uriIdentityService, logService);
+ super.setEnablement(window.localStorage.getItem(PROFILES_ENABLEMENT_CONFIG) === 'true');
+ this.changesBroadcastChannel = this._register(new BroadcastDataChannel<BroadcastedProfileChanges>(`${UserDataProfilesService.PROFILES_KEY}.changes`));
+ this._register(this.changesBroadcastChannel.onDidReceiveData(changes => {
+ try {
+ this._profilesObject = undefined;
+ this._onDidChangeProfiles.fire({ added: changes.added.map(p => reviveProfile(p, this.profilesHome.scheme)), removed: changes.removed.map(p => reviveProfile(p, this.profilesHome.scheme)), all: this.profiles });
+ } catch (error) {/* ignore */ }
+ }));
+ }
+
+ override setEnablement(enabled: boolean): void {
+ super.setEnablement(enabled);
+ window.localStorage.setItem(PROFILES_ENABLEMENT_CONFIG, enabled ? 'true' : 'false');
+ }
+
+ protected override getStoredProfiles(): StoredUserDataProfile[] {
+ try {
+ const value = window.localStorage.getItem(UserDataProfilesService.PROFILES_KEY);
+ if (value) {
+ return revive(JSON.parse(value));
+ }
+ } catch (error) {
+ /* ignore */
+ this.logService.error(error);
+ }
+ return [];
+ }
+
+ protected override triggerProfilesChanges(added: IUserDataProfile[], removed: IUserDataProfile[]) {
+ super.triggerProfilesChanges(added, removed);
+ this.changesBroadcastChannel.postData({ added, removed });
+ }
+
+ protected override saveStoredProfiles(storedProfiles: StoredUserDataProfile[]): void {
+ window.localStorage.setItem(UserDataProfilesService.PROFILES_KEY, JSON.stringify(storedProfiles));
+ }
+
+ protected override getStoredProfileAssociations(): StoredProfileAssociations {
+ try {
+ const value = window.localStorage.getItem(UserDataProfilesService.PROFILE_ASSOCIATIONS_KEY);
+ if (value) {
+ return revive(JSON.parse(value));
+ }
+ } catch (error) {
+ /* ignore */
+ this.logService.error(error);
+ }
+ return {};
+ }
+
+ protected override saveStoredProfileAssociations(storedProfileAssociations: StoredProfileAssociations): void {
+ window.localStorage.setItem(UserDataProfilesService.PROFILE_ASSOCIATIONS_KEY, JSON.stringify(storedProfileAssociations));
+ }
+
+}
diff --git a/src/vs/platform/userDataProfile/common/userDataProfile.ts b/src/vs/platform/userDataProfile/common/userDataProfile.ts
index 0705d48af4f..3bc2eb4ea3d 100644
--- a/src/vs/platform/userDataProfile/common/userDataProfile.ts
+++ b/src/vs/platform/userDataProfile/common/userDataProfile.ts
@@ -14,7 +14,11 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
import { IFileService } from 'vs/platform/files/common/files';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
-import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace';
+import { ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace';
+import { ResourceMap } from 'vs/base/common/map';
+import { IStringDictionary } from 'vs/base/common/collections';
+import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
+import { Promises } from 'vs/base/common/async';
/**
* Flags to indicate whether to use the default profile or not.
@@ -42,8 +46,6 @@ export interface IUserDataProfile {
readonly useDefaultFlags?: UseDefaultProfileFlags;
}
-export type CustomUserDataProfile = IUserDataProfile & { readonly extensionsResource: URI; readonly isDefault: false };
-
export function isUserDataProfile(thing: unknown): thing is IUserDataProfile {
const candidate = thing as IUserDataProfile | undefined;
@@ -68,6 +70,16 @@ export type WorkspaceIdentifier = ISingleFolderWorkspaceIdentifier | IWorkspaceI
export type DidChangeProfilesEvent = { readonly added: IUserDataProfile[]; readonly removed: IUserDataProfile[]; readonly all: IUserDataProfile[] };
+export type WillCreateProfileEvent = {
+ profile: IUserDataProfile;
+ join(promise: Promise<void>): void;
+};
+
+export type WillRemoveProfileEvent = {
+ profile: IUserDataProfile;
+ join(promise: Promise<void>): void;
+};
+
export const IUserDataProfilesService = createDecorator<IUserDataProfilesService>('IUserDataProfilesService');
export interface IUserDataProfilesService {
readonly _serviceBrand: undefined;
@@ -78,9 +90,8 @@ export interface IUserDataProfilesService {
readonly onDidChangeProfiles: Event<DidChangeProfilesEvent>;
readonly profiles: IUserDataProfile[];
- newProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags): CustomUserDataProfile;
- createProfile(profile: IUserDataProfile, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile>;
- setProfileForWorkspace(profile: IUserDataProfile, workspaceIdentifier: WorkspaceIdentifier): Promise<IUserDataProfile>;
+ createProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile>;
+ setProfileForWorkspace(profile: IUserDataProfile, workspaceIdentifier: WorkspaceIdentifier): Promise<void>;
getProfile(workspaceIdentifier: WorkspaceIdentifier): IUserDataProfile;
removeProfile(profile: IUserDataProfile): Promise<void>;
}
@@ -102,9 +113,9 @@ export function reviveProfile(profile: UriDto<IUserDataProfile>, scheme: string)
export const EXTENSIONS_RESOURCE_NAME = 'extensions.json';
-export function toUserDataProfile(name: string, location: URI, useDefaultFlags?: UseDefaultProfileFlags): CustomUserDataProfile {
+export function toUserDataProfile(name: string, location: URI, useDefaultFlags?: UseDefaultProfileFlags): IUserDataProfile {
return {
- id: hash(location.toString()).toString(16),
+ id: hash(location.path).toString(16),
name: name,
location: location,
isDefault: false,
@@ -118,38 +129,253 @@ export function toUserDataProfile(name: string, location: URI, useDefaultFlags?:
};
}
+export type UserDataProfilesObject = {
+ profiles: IUserDataProfile[];
+ workspaces: ResourceMap<IUserDataProfile>;
+ emptyWindow?: IUserDataProfile;
+};
+
+export type StoredUserDataProfile = {
+ name: string;
+ location: URI;
+ useDefaultFlags?: UseDefaultProfileFlags;
+};
+
+export type StoredProfileAssociations = {
+ workspaces?: IStringDictionary<string>;
+ emptyWindow?: string;
+};
+
export class UserDataProfilesService extends Disposable implements IUserDataProfilesService {
+
+ protected static readonly PROFILES_KEY = 'userDataProfiles';
+ protected static readonly PROFILE_ASSOCIATIONS_KEY = 'profileAssociations';
+
readonly _serviceBrand: undefined;
+ private enabled: boolean = false;
+ protected readonly defaultProfileShouldIncludeExtensionsResourceAlways: boolean = false;
readonly profilesHome: URI;
- private readonly _defaultProfile = this.createDefaultUserDataProfile(false);
- get defaultProfile(): IUserDataProfile { return this.profiles[0] ?? this._defaultProfile; }
- get profiles(): IUserDataProfile[] { return []; }
+ get defaultProfile(): IUserDataProfile { return this.profiles[0]; }
+ get profiles(): IUserDataProfile[] { return this.profilesObject.profiles; }
protected readonly _onDidChangeProfiles = this._register(new Emitter<DidChangeProfilesEvent>());
readonly onDidChangeProfiles = this._onDidChangeProfiles.event;
+ protected readonly _onWillCreateProfile = this._register(new Emitter<WillCreateProfileEvent>());
+ readonly onWillCreateProfile = this._onWillCreateProfile.event;
+
+ protected readonly _onWillRemoveProfile = this._register(new Emitter<WillRemoveProfileEvent>());
+ readonly onWillRemoveProfile = this._onWillRemoveProfile.event;
+
constructor(
@IEnvironmentService protected readonly environmentService: IEnvironmentService,
@IFileService protected readonly fileService: IFileService,
+ @IUriIdentityService protected readonly uriIdentityService: IUriIdentityService,
@ILogService protected readonly logService: ILogService
) {
super();
this.profilesHome = joinPath(this.environmentService.userRoamingDataHome, 'profiles');
}
- newProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags): CustomUserDataProfile {
- return toUserDataProfile(name, joinPath(this.profilesHome, hash(name).toString(16)), useDefaultFlags);
+ setEnablement(enabled: boolean): void {
+ if (this.enabled !== enabled) {
+ this._profilesObject = undefined;
+ this.enabled = enabled;
+ }
+ }
+
+ protected _profilesObject: UserDataProfilesObject | undefined;
+ protected get profilesObject(): UserDataProfilesObject {
+ if (!this._profilesObject) {
+ const profiles = this.enabled ? this.getStoredProfiles().map<IUserDataProfile>(storedProfile => toUserDataProfile(storedProfile.name, storedProfile.location, storedProfile.useDefaultFlags)) : [];
+ let emptyWindow: IUserDataProfile | undefined;
+ const workspaces = new ResourceMap<IUserDataProfile>();
+ if (profiles.length) {
+ const profileAssicaitions = this.getStoredProfileAssociations();
+ if (profileAssicaitions.workspaces) {
+ for (const [workspacePath, profilePath] of Object.entries(profileAssicaitions.workspaces)) {
+ const workspace = URI.parse(workspacePath);
+ const profileLocation = URI.parse(profilePath);
+ const profile = profiles.find(p => this.uriIdentityService.extUri.isEqual(p.location, profileLocation));
+ if (profile) {
+ workspaces.set(workspace, profile);
+ }
+ }
+ }
+ if (profileAssicaitions.emptyWindow) {
+ const emptyWindowProfileLocation = URI.parse(profileAssicaitions.emptyWindow);
+ emptyWindow = profiles.find(p => this.uriIdentityService.extUri.isEqual(p.location, emptyWindowProfileLocation));
+ }
+ }
+ const profile = toUserDataProfile(localize('defaultProfile', "Default"), this.environmentService.userRoamingDataHome);
+ profiles.unshift({ ...profile, isDefault: true, extensionsResource: this.defaultProfileShouldIncludeExtensionsResourceAlways || profiles.length > 0 ? profile.extensionsResource : undefined });
+ this._profilesObject = { profiles, workspaces, emptyWindow };
+ }
+ return this._profilesObject;
+ }
+
+ getProfile(workspaceIdentifier: WorkspaceIdentifier): IUserDataProfile {
+ const workspace = this.getWorkspace(workspaceIdentifier);
+ const profile = URI.isUri(workspace) ? this.profilesObject.workspaces.get(workspace) : this.profilesObject.emptyWindow;
+ return profile ?? this.defaultProfile;
}
- protected createDefaultUserDataProfile(extensions: boolean): IUserDataProfile {
- const profile = toUserDataProfile(localize('defaultProfile', "Default"), this.environmentService.userRoamingDataHome);
- return { ...profile, isDefault: true, extensionsResource: extensions ? profile.extensionsResource : undefined };
+ protected getWorkspace(workspaceIdentifier: WorkspaceIdentifier): URI | EmptyWindowWorkspaceIdentifier {
+ if (isSingleFolderWorkspaceIdentifier(workspaceIdentifier)) {
+ return workspaceIdentifier.uri;
+ }
+ if (isWorkspaceIdentifier(workspaceIdentifier)) {
+ return workspaceIdentifier.configPath;
+ }
+ return 'empty-window';
}
- createProfile(profile: IUserDataProfile, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile> { throw new Error('Not implemented'); }
- setProfileForWorkspace(profile: IUserDataProfile, workspaceIdentifier: WorkspaceIdentifier): Promise<IUserDataProfile> { throw new Error('Not implemented'); }
- getProfile(workspaceIdentifier: WorkspaceIdentifier): IUserDataProfile { throw new Error('Not implemented'); }
- removeProfile(profile: IUserDataProfile): Promise<void> { throw new Error('Not implemented'); }
+ async createProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile> {
+ if (!this.enabled) {
+ throw new Error(`Settings Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`);
+ }
+ if (this.getStoredProfiles().some(p => p.name === name)) {
+ throw new Error(`Profile with name ${name} already exists`);
+ }
+
+ const profile = toUserDataProfile(name, joinPath(this.profilesHome, hash(name).toString(16)), useDefaultFlags);
+ await this.fileService.createFolder(profile.location);
+
+ const joiners: Promise<void>[] = [];
+ this._onWillCreateProfile.fire({
+ profile,
+ join(promise) {
+ joiners.push(promise);
+ }
+ });
+ await Promises.settled(joiners);
+
+ this.updateProfiles([profile], []);
+
+ if (workspaceIdentifier) {
+ await this.setProfileForWorkspace(profile, workspaceIdentifier);
+ }
+
+ return profile;
+ }
+
+ async setProfileForWorkspace(profileToSet: IUserDataProfile, workspaceIdentifier: WorkspaceIdentifier): Promise<void> {
+ if (!this.enabled) {
+ throw new Error(`Settings Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`);
+ }
+
+ const profile = this.profiles.find(p => p.id === profileToSet.id);
+ if (!profile) {
+ throw new Error(`Profile '${profileToSet.name}' does not exist`);
+ }
+
+ this.updateWorkspaceAssociation(workspaceIdentifier, profile);
+ }
+
+ async unsetWorkspace(workspaceIdentifier: WorkspaceIdentifier): Promise<void> {
+ if (!this.enabled) {
+ throw new Error(`Settings Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`);
+ }
+ this.updateWorkspaceAssociation(workspaceIdentifier);
+ }
+
+ async removeProfile(profileToRemove: IUserDataProfile): Promise<void> {
+ if (!this.enabled) {
+ throw new Error(`Settings Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`);
+ }
+ if (profileToRemove.isDefault) {
+ throw new Error('Cannot remove default profile');
+ }
+ const profile = this.profiles.find(p => p.id === profileToRemove.id);
+ if (!profile) {
+ throw new Error(`Profile '${profileToRemove.name}' does not exist`);
+ }
+
+ const joiners: Promise<void>[] = [];
+ this._onWillRemoveProfile.fire({
+ profile,
+ join(promise) {
+ joiners.push(promise);
+ }
+ });
+ await Promises.settled(joiners);
+
+ if (profile.id === this.profilesObject.emptyWindow?.id) {
+ this.profilesObject.emptyWindow = undefined;
+ }
+ for (const workspace of [...this.profilesObject.workspaces.keys()]) {
+ if (profile.id === this.profilesObject.workspaces.get(workspace)?.id) {
+ this.profilesObject.workspaces.delete(workspace);
+ }
+ }
+ this.updateStoredProfileAssociations();
+
+ this.updateProfiles([], [profile]);
+
+ try {
+ if (this.profiles.length === 1) {
+ await this.fileService.del(this.profilesHome, { recursive: true });
+ } else {
+ await this.fileService.del(profile.location, { recursive: true });
+ }
+ } catch (error) {
+ this.logService.error(error);
+ }
+ }
+
+ private updateProfiles(added: IUserDataProfile[], removed: IUserDataProfile[]) {
+ const storedProfiles: StoredUserDataProfile[] = [];
+ for (const profile of [...this.profilesObject.profiles, ...added]) {
+ if (profile.isDefault) {
+ continue;
+ }
+ if (removed.some(p => profile.id === p.id)) {
+ continue;
+ }
+ storedProfiles.push({ location: profile.location, name: profile.name, useDefaultFlags: profile.useDefaultFlags });
+ }
+ this.saveStoredProfiles(storedProfiles);
+ this._profilesObject = undefined;
+ this.triggerProfilesChanges(added, removed);
+ }
+
+ protected triggerProfilesChanges(added: IUserDataProfile[], removed: IUserDataProfile[]) {
+ this._onDidChangeProfiles.fire({ added, removed, all: this.profiles });
+ }
+
+ private updateWorkspaceAssociation(workspaceIdentifier: WorkspaceIdentifier, newProfile?: IUserDataProfile) {
+ const workspace = this.getWorkspace(workspaceIdentifier);
+
+ // Folder or Multiroot workspace
+ if (URI.isUri(workspace)) {
+ this.profilesObject.workspaces.delete(workspace);
+ if (newProfile && !newProfile.isDefault) {
+ this.profilesObject.workspaces.set(workspace, newProfile);
+ }
+ }
+ // Empty Window
+ else {
+ this.profilesObject.emptyWindow = !newProfile?.isDefault ? newProfile : undefined;
+ }
+
+ this.updateStoredProfileAssociations();
+ }
+
+ private updateStoredProfileAssociations() {
+ const workspaces: IStringDictionary<string> = {};
+ for (const [workspace, profile] of this.profilesObject.workspaces.entries()) {
+ workspaces[workspace.toString()] = profile.location.toString();
+ }
+ const emptyWindow = this.profilesObject.emptyWindow?.location.toString();
+ this.saveStoredProfileAssociations({ workspaces, emptyWindow });
+ this._profilesObject = undefined;
+ }
+
+ protected getStoredProfiles(): StoredUserDataProfile[] { return []; }
+ protected saveStoredProfiles(storedProfiles: StoredUserDataProfile[]): void { throw new Error('not implemented'); }
+
+ protected getStoredProfileAssociations(): StoredProfileAssociations { return {}; }
+ protected saveStoredProfileAssociations(storedProfileAssociations: StoredProfileAssociations): void { throw new Error('not implemented'); }
}
diff --git a/src/vs/platform/userDataProfile/electron-main/userDataProfile.ts b/src/vs/platform/userDataProfile/electron-main/userDataProfile.ts
index 7073ead9cda..0ac191f058a 100644
--- a/src/vs/platform/userDataProfile/electron-main/userDataProfile.ts
+++ b/src/vs/platform/userDataProfile/electron-main/userDataProfile.ts
@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { Emitter, Event } from 'vs/base/common/event';
+import { Event } from 'vs/base/common/event';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IFileService } from 'vs/platform/files/common/files';
@@ -11,21 +11,10 @@ import { refineServiceDecorator } from 'vs/platform/instantiation/common/instant
import { ILogService } from 'vs/platform/log/common/log';
import { IStateMainService } from 'vs/platform/state/electron-main/state';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
-import { IUserDataProfile, IUserDataProfilesService, reviveProfile, PROFILES_ENABLEMENT_CONFIG, WorkspaceIdentifier } from 'vs/platform/userDataProfile/common/userDataProfile';
-import { Promises } from 'vs/base/common/async';
-import { StoredProfileAssociations, StoredUserDataProfile, UserDataProfilesService } from 'vs/platform/userDataProfile/node/userDataProfile';
+import { IUserDataProfilesService, WorkspaceIdentifier, StoredUserDataProfile, StoredProfileAssociations, WillCreateProfileEvent, WillRemoveProfileEvent } from 'vs/platform/userDataProfile/common/userDataProfile';
+import { UserDataProfilesService } from 'vs/platform/userDataProfile/node/userDataProfile';
import { IStringDictionary } from 'vs/base/common/collections';
-export type WillCreateProfileEvent = {
- profile: IUserDataProfile;
- join(promise: Promise<void>): void;
-};
-
-export type WillRemoveProfileEvent = {
- profile: IUserDataProfile;
- join(promise: Promise<void>): void;
-};
-
export const IUserDataProfilesMainService = refineServiceDecorator<IUserDataProfilesService, IUserDataProfilesMainService>(IUserDataProfilesService);
export interface IUserDataProfilesMainService extends IUserDataProfilesService {
unsetWorkspace(workspaceIdentifier: WorkspaceIdentifier): Promise<void>;
@@ -35,12 +24,6 @@ export interface IUserDataProfilesMainService extends IUserDataProfilesService {
export class UserDataProfilesMainService extends UserDataProfilesService implements IUserDataProfilesMainService {
- private readonly _onWillCreateProfile = this._register(new Emitter<WillCreateProfileEvent>());
- readonly onWillCreateProfile = this._onWillCreateProfile.event;
-
- private readonly _onWillRemoveProfile = this._register(new Emitter<WillRemoveProfileEvent>());
- readonly onWillRemoveProfile = this._onWillRemoveProfile.event;
-
constructor(
@IStateMainService private readonly stateMainService: IStateMainService,
@IUriIdentityService uriIdentityService: IUriIdentityService,
@@ -51,142 +34,12 @@ export class UserDataProfilesMainService extends UserDataProfilesService impleme
super(stateMainService, uriIdentityService, environmentService, fileService, logService);
}
- override async createProfile(profile: IUserDataProfile, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile> {
- if (!this.enabled) {
- throw new Error(`Settings Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`);
- }
- profile = reviveProfile(profile, this.profilesHome.scheme);
- if (this.getStoredProfiles().some(p => p.name === profile.name)) {
- throw new Error(`Profile with name ${profile.name} already exists`);
- }
-
- if (!(await this.fileService.exists(this.profilesHome))) {
- await this.fileService.createFolder(this.profilesHome);
- }
-
- const joiners: Promise<void>[] = [];
- this._onWillCreateProfile.fire({
- profile,
- join(promise) {
- joiners.push(promise);
- }
- });
- await Promises.settled(joiners);
-
- this.updateProfiles([profile], []);
-
- if (workspaceIdentifier) {
- await this.setProfileForWorkspace(profile, workspaceIdentifier);
- }
-
- return this.profiles.find(p => this.uriIdentityService.extUri.isEqual(p.location, profile.location))!;
- }
-
- override async setProfileForWorkspace(profile: IUserDataProfile, workspaceIdentifier: WorkspaceIdentifier): Promise<IUserDataProfile> {
- if (!this.enabled) {
- throw new Error(`Settings Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`);
- }
-
- profile = reviveProfile(profile, this.profilesHome.scheme);
- this.updateWorkspaceAssociation(workspaceIdentifier, profile);
-
- return this.profiles.find(p => this.uriIdentityService.extUri.isEqual(p.location, profile.location))!;
- }
-
- async unsetWorkspace(workspaceIdentifier: WorkspaceIdentifier): Promise<void> {
- if (!this.enabled) {
- throw new Error(`Settings Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`);
- }
-
- this.updateWorkspaceAssociation(workspaceIdentifier);
- }
-
- override async removeProfile(profile: IUserDataProfile): Promise<void> {
- if (!this.enabled) {
- throw new Error(`Settings Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`);
- }
- if (profile.isDefault) {
- throw new Error('Cannot remove default profile');
- }
- profile = reviveProfile(profile, this.profilesHome.scheme);
- if (!this.getStoredProfiles().some(p => this.uriIdentityService.extUri.isEqual(p.location, profile.location))) {
- throw new Error(`Profile with name ${profile.name} does not exist`);
- }
-
- const joiners: Promise<void>[] = [];
- this._onWillRemoveProfile.fire({
- profile,
- join(promise) {
- joiners.push(promise);
- }
- });
- await Promises.settled(joiners);
-
- if (profile.id === this.profilesObject.emptyWindow?.id) {
- this.profilesObject.emptyWindow = undefined;
- }
- for (const workspace of [...this.profilesObject.workspaces.keys()]) {
- if (profile.id === this.profilesObject.workspaces.get(workspace)?.id) {
- this.profilesObject.workspaces.delete(workspace);
- }
- }
- this.saveStoredProfileAssociations();
-
- this.updateProfiles([], [profile]);
-
- try {
- if (this.profiles.length === 2) {
- await this.fileService.del(this.profilesHome, { recursive: true });
- } else {
- await this.fileService.del(profile.location, { recursive: true });
- }
- } catch (error) {
- this.logService.error(error);
- }
- }
-
- private updateProfiles(added: IUserDataProfile[], removed: IUserDataProfile[]) {
- const storedProfiles: StoredUserDataProfile[] = [];
- for (const profile of [...this.profilesObject.profiles, ...added]) {
- if (profile.isDefault) {
- continue;
- }
- if (removed.some(p => profile.id === p.id)) {
- continue;
- }
- storedProfiles.push({ location: profile.location, name: profile.name, useDefaultFlags: profile.useDefaultFlags });
- }
+ protected override saveStoredProfiles(storedProfiles: StoredUserDataProfile[]): void {
this.stateMainService.setItem(UserDataProfilesMainService.PROFILES_KEY, storedProfiles);
- this._profilesObject = undefined;
- this._onDidChangeProfiles.fire({ added, removed, all: this.profiles });
}
- private updateWorkspaceAssociation(workspaceIdentifier: WorkspaceIdentifier, newProfile?: IUserDataProfile) {
- const workspace = this.getWorkspace(workspaceIdentifier);
-
- // Folder or Multiroot workspace
- if (URI.isUri(workspace)) {
- this.profilesObject.workspaces.delete(workspace);
- if (newProfile && !newProfile.isDefault) {
- this.profilesObject.workspaces.set(workspace, newProfile);
- }
- }
- // Empty Window
- else {
- this.profilesObject.emptyWindow = !newProfile?.isDefault ? newProfile : undefined;
- }
-
- this.saveStoredProfileAssociations();
- }
-
- private saveStoredProfileAssociations() {
- const workspaces: IStringDictionary<string> = {};
- for (const [workspace, profile] of this.profilesObject.workspaces.entries()) {
- workspaces[workspace.toString()] = profile.location.toString();
- }
- const emptyWindow = this.profilesObject.emptyWindow?.location.toString();
- this.stateMainService.setItem(UserDataProfilesMainService.PROFILE_ASSOCIATIONS_KEY, { workspaces, emptyWindow });
- this._profilesObject = undefined;
+ protected override saveStoredProfileAssociations(storedProfileAssociations: StoredProfileAssociations): void {
+ this.stateMainService.setItem(UserDataProfilesMainService.PROFILE_ASSOCIATIONS_KEY, storedProfileAssociations);
}
protected override getStoredProfileAssociations(): StoredProfileAssociations {
diff --git a/src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts b/src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts
index 210bd2288b9..085e035b676 100644
--- a/src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts
+++ b/src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts
@@ -3,31 +3,40 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
+import { Emitter } from 'vs/base/common/event';
+import { Disposable } from 'vs/base/common/lifecycle';
+import { joinPath } from 'vs/base/common/resources';
import { UriDto } from 'vs/base/common/types';
+import { URI } from 'vs/base/common/uri';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
-import { IFileService } from 'vs/platform/files/common/files';
import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services';
-import { ILogService } from 'vs/platform/log/common/log';
-import { DidChangeProfilesEvent, IUserDataProfile, IUserDataProfilesService, reviveProfile, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
+import { DidChangeProfilesEvent, IUserDataProfile, IUserDataProfilesService, reviveProfile, UseDefaultProfileFlags, WorkspaceIdentifier } from 'vs/platform/userDataProfile/common/userDataProfile';
import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace';
-export class UserDataProfilesNativeService extends UserDataProfilesService implements IUserDataProfilesService {
+export class UserDataProfilesNativeService extends Disposable implements IUserDataProfilesService {
+
+ readonly _serviceBrand: undefined;
private readonly channel: IChannel;
+ readonly profilesHome: URI;
+
+ get defaultProfile(): IUserDataProfile { return this.profiles[0]; }
private _profiles: IUserDataProfile[] = [];
- override get profiles(): IUserDataProfile[] { return this._profiles; }
+ get profiles(): IUserDataProfile[] { return this._profiles; }
+
+ private readonly _onDidChangeProfiles = this._register(new Emitter<DidChangeProfilesEvent>());
+ readonly onDidChangeProfiles = this._onDidChangeProfiles.event;
constructor(
profiles: UriDto<IUserDataProfile>[],
@IMainProcessService mainProcessService: IMainProcessService,
@IEnvironmentService environmentService: IEnvironmentService,
- @IFileService fileService: IFileService,
- @ILogService logService: ILogService,
) {
- super(environmentService, fileService, logService);
+ super();
this.channel = mainProcessService.getChannel('userDataProfiles');
+ this.profilesHome = joinPath(environmentService.userRoamingDataHome, 'profiles');
this._profiles = profiles.map(profile => reviveProfile(profile, this.profilesHome.scheme));
this._register(this.channel.listen<DidChangeProfilesEvent>('onDidChangeProfiles')(e => {
const added = e.added.map(profile => reviveProfile(profile, this.profilesHome.scheme));
@@ -37,18 +46,19 @@ export class UserDataProfilesNativeService extends UserDataProfilesService imple
}));
}
- override async createProfile(profile: IUserDataProfile, workspaceIdentifier?: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise<IUserDataProfile> {
- const result = await this.channel.call<UriDto<IUserDataProfile>>('createProfile', [profile, workspaceIdentifier]);
+ async createProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, workspaceIdentifier?: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise<IUserDataProfile> {
+ const result = await this.channel.call<UriDto<IUserDataProfile>>('createProfile', [name, useDefaultFlags, workspaceIdentifier]);
return reviveProfile(result, this.profilesHome.scheme);
}
- override async setProfileForWorkspace(profile: IUserDataProfile, workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise<IUserDataProfile> {
- const result = await this.channel.call<UriDto<IUserDataProfile>>('setProfileForWorkspace', [profile, workspaceIdentifier]);
- return reviveProfile(result, this.profilesHome.scheme);
+ async setProfileForWorkspace(profile: IUserDataProfile, workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise<void> {
+ await this.channel.call<UriDto<IUserDataProfile>>('setProfileForWorkspace', [profile, workspaceIdentifier]);
}
- override removeProfile(profile: IUserDataProfile): Promise<void> {
+ removeProfile(profile: IUserDataProfile): Promise<void> {
return this.channel.call('removeProfile', [profile]);
}
+
+ getProfile(workspaceIdentifier: WorkspaceIdentifier): IUserDataProfile { throw new Error('Not implemented'); }
}
diff --git a/src/vs/platform/userDataProfile/node/userDataProfile.ts b/src/vs/platform/userDataProfile/node/userDataProfile.ts
index a7b7c247891..1405ca08e76 100644
--- a/src/vs/platform/userDataProfile/node/userDataProfile.ts
+++ b/src/vs/platform/userDataProfile/node/userDataProfile.ts
@@ -3,113 +3,32 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { IStringDictionary } from 'vs/base/common/collections';
-import { ResourceMap } from 'vs/base/common/map';
import { revive } from 'vs/base/common/marshalling';
import { UriDto } from 'vs/base/common/types';
-import { URI } from 'vs/base/common/uri';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IFileService } from 'vs/platform/files/common/files';
import { ILogService } from 'vs/platform/log/common/log';
import { IStateService } from 'vs/platform/state/node/state';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
-import { UseDefaultProfileFlags, IUserDataProfile, IUserDataProfilesService, UserDataProfilesService as BaseUserDataProfilesService, toUserDataProfile, WorkspaceIdentifier, EmptyWindowWorkspaceIdentifier } from 'vs/platform/userDataProfile/common/userDataProfile';
-import { isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace';
-
-export type UserDataProfilesObject = {
- profiles: IUserDataProfile[];
- workspaces: ResourceMap<IUserDataProfile>;
- emptyWindow?: IUserDataProfile;
-};
-
-export type StoredUserDataProfile = {
- name: string;
- location: URI;
- useDefaultFlags?: UseDefaultProfileFlags;
-};
-
-export type StoredProfileAssociations = {
- workspaces?: IStringDictionary<string>;
- emptyWindow?: string;
-};
+import { IUserDataProfilesService, UserDataProfilesService as BaseUserDataProfilesService, StoredUserDataProfile, StoredProfileAssociations } from 'vs/platform/userDataProfile/common/userDataProfile';
export class UserDataProfilesService extends BaseUserDataProfilesService implements IUserDataProfilesService {
- protected static readonly PROFILES_KEY = 'userDataProfiles';
- protected static readonly PROFILE_ASSOCIATIONS_KEY = 'profileAssociations';
-
- protected enabled: boolean = false;
-
constructor(
@IStateService private readonly stateService: IStateService,
- @IUriIdentityService protected readonly uriIdentityService: IUriIdentityService,
+ @IUriIdentityService uriIdentityService: IUriIdentityService,
@IEnvironmentService environmentService: IEnvironmentService,
@IFileService fileService: IFileService,
@ILogService logService: ILogService,
) {
- super(environmentService, fileService, logService);
- }
-
- setEnablement(enabled: boolean): void {
- this._profilesObject = undefined;
- this.enabled = enabled;
- }
-
- protected _profilesObject: UserDataProfilesObject | undefined;
- protected get profilesObject(): UserDataProfilesObject {
- if (!this.enabled) {
- return { profiles: [], workspaces: new ResourceMap() };
- }
- if (!this._profilesObject) {
- const profiles = this.getStoredProfiles().map<IUserDataProfile>(storedProfile => toUserDataProfile(storedProfile.name, storedProfile.location, storedProfile.useDefaultFlags));
- let emptyWindow: IUserDataProfile | undefined;
- const workspaces = new ResourceMap<IUserDataProfile>();
- if (profiles.length) {
- profiles.unshift(this.createDefaultUserDataProfile(true));
- const profileAssicaitions = this.getStoredProfileAssociations();
- if (profileAssicaitions.workspaces) {
- for (const [workspacePath, profilePath] of Object.entries(profileAssicaitions.workspaces)) {
- const workspace = URI.parse(workspacePath);
- const profileLocation = URI.parse(profilePath);
- const profile = profiles.find(p => this.uriIdentityService.extUri.isEqual(p.location, profileLocation));
- if (profile) {
- workspaces.set(workspace, profile);
- }
- }
- }
- if (profileAssicaitions.emptyWindow) {
- const emptyWindowProfileLocation = URI.parse(profileAssicaitions.emptyWindow);
- emptyWindow = profiles.find(p => this.uriIdentityService.extUri.isEqual(p.location, emptyWindowProfileLocation));
- }
- }
- this._profilesObject = { profiles, workspaces, emptyWindow };
- }
- return this._profilesObject;
- }
-
- override get profiles(): IUserDataProfile[] { return this.profilesObject.profiles; }
-
- override getProfile(workspaceIdentifier: WorkspaceIdentifier): IUserDataProfile {
- const workspace = this.getWorkspace(workspaceIdentifier);
- const profile = URI.isUri(workspace) ? this.profilesObject.workspaces.get(workspace) : this.profilesObject.emptyWindow;
- return profile ?? this.defaultProfile;
- }
-
- protected getWorkspace(workspaceIdentifier: WorkspaceIdentifier): URI | EmptyWindowWorkspaceIdentifier {
- if (isSingleFolderWorkspaceIdentifier(workspaceIdentifier)) {
- return workspaceIdentifier.uri;
- }
- if (isWorkspaceIdentifier(workspaceIdentifier)) {
- return workspaceIdentifier.configPath;
- }
- return 'empty-window';
+ super(environmentService, fileService, uriIdentityService, logService);
}
- protected getStoredProfiles(): StoredUserDataProfile[] {
+ protected override getStoredProfiles(): StoredUserDataProfile[] {
return revive(this.stateService.getItem<UriDto<StoredUserDataProfile>[]>(UserDataProfilesService.PROFILES_KEY, []));
}
- protected getStoredProfileAssociations(): StoredProfileAssociations {
+ protected override getStoredProfileAssociations(): StoredProfileAssociations {
return revive(this.stateService.getItem<UriDto<StoredProfileAssociations>>(UserDataProfilesService.PROFILE_ASSOCIATIONS_KEY, {}));
}
diff --git a/src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts b/src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts
index b284dd6fd70..662a70fe033 100644
--- a/src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts
+++ b/src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts
@@ -14,6 +14,7 @@ import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFil
import { AbstractNativeEnvironmentService } from 'vs/platform/environment/common/environmentService';
import product from 'vs/platform/product/common/product';
import { UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
+import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService';
const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' });
@@ -37,7 +38,7 @@ suite('UserDataProfileService (Common)', () => {
disposables.add(fileService.registerProvider(ROOT.scheme, fileSystemProvider));
environmentService = new TestEnvironmentService(joinPath(ROOT, 'User'));
- testObject = new UserDataProfilesService(environmentService, fileService, logService);
+ testObject = new UserDataProfilesService(environmentService, fileService, new UriIdentityService(fileService), logService);
});
teardown(() => disposables.clear());
@@ -54,8 +55,10 @@ suite('UserDataProfileService (Common)', () => {
assert.strictEqual(testObject.defaultProfile.extensionsResource, undefined);
});
- test('profiles are empty', () => {
- assert.deepStrictEqual(testObject.profiles, []);
+ test('profiles always include default profile', () => {
+ assert.deepStrictEqual(testObject.profiles.length, 1);
+ assert.deepStrictEqual(testObject.profiles[0].isDefault, true);
+ assert.deepStrictEqual(testObject.profiles[0].extensionsResource, undefined);
});
diff --git a/src/vs/platform/userDataProfile/test/electron-main/userDataProfileMainService.test.ts b/src/vs/platform/userDataProfile/test/electron-main/userDataProfileMainService.test.ts
index b5592746149..23e7656a1ad 100644
--- a/src/vs/platform/userDataProfile/test/electron-main/userDataProfileMainService.test.ts
+++ b/src/vs/platform/userDataProfile/test/electron-main/userDataProfileMainService.test.ts
@@ -54,18 +54,20 @@ suite('UserDataProfileMainService', () => {
assert.strictEqual(testObject.defaultProfile.extensionsResource, undefined);
});
- test('profiles are empty', () => {
- assert.deepStrictEqual(testObject.profiles, []);
+ test('profiles always include default profile', () => {
+ assert.deepStrictEqual(testObject.profiles.length, 1);
+ assert.deepStrictEqual(testObject.profiles[0].isDefault, true);
+ assert.deepStrictEqual(testObject.profiles[0].extensionsResource, undefined);
});
test('default profile when there are profiles', async () => {
- await testObject.createProfile(testObject.newProfile('test'));
+ await testObject.createProfile('test');
assert.strictEqual(testObject.defaultProfile.isDefault, true);
assert.strictEqual(testObject.defaultProfile.extensionsResource?.toString(), joinPath(environmentService.userRoamingDataHome, 'extensions.json').toString());
});
test('default profile when profiles are removed', async () => {
- const profile = await testObject.createProfile(testObject.newProfile('test'));
+ const profile = await testObject.createProfile('test');
await testObject.removeProfile(profile);
assert.strictEqual(testObject.defaultProfile.isDefault, true);
assert.strictEqual(testObject.defaultProfile.extensionsResource, undefined);
diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts
index ec0a973bf60..6973141914f 100644
--- a/src/vs/platform/userDataSync/common/userDataSync.ts
+++ b/src/vs/platform/userDataSync/common/userDataSync.ts
@@ -184,7 +184,7 @@ export interface IUserDataSyncStoreClient {
delete(resource: ServerResource, ref: string | null): Promise<void>;
getAllRefs(resource: ServerResource): Promise<IResourceRefHandle[]>;
- resolveContent(resource: ServerResource, ref: string): Promise<string | null>;
+ resolveContent(resource: ServerResource, ref: string, headers?: IHeaders): Promise<string | null>;
}
export const IUserDataSyncStoreService = createDecorator<IUserDataSyncStoreService>('IUserDataSyncStoreService');
diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts
index 427babbd4f6..2b7bd79e2b8 100644
--- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts
+++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts
@@ -244,13 +244,13 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync
return result.map(({ url, created }) => ({ ref: relativePath(uri, uri.with({ path: url }))!, created: created * 1000 /* Server returns in seconds */ }));
}
- async resolveContent(resource: ServerResource, ref: string): Promise<string | null> {
+ async resolveContent(resource: ServerResource, ref: string, headers: IHeaders = {}): Promise<string | null> {
if (!this.userDataSyncStoreUrl) {
throw new Error('No settings sync store url configured.');
}
const url = joinPath(this.userDataSyncStoreUrl, 'resource', resource, ref).toString();
- const headers: IHeaders = {};
+ headers = { ...headers };
headers['Cache-Control'] = 'no-cache';
const context = await this.request(url, { type: 'GET', headers }, [], CancellationToken.None);
diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts
index 645181d8a3c..2252b873b35 100644
--- a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts
+++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts
@@ -83,14 +83,16 @@ export class UserDataSyncClient extends Disposable {
fileService.registerProvider(Schemas.inMemory, new InMemoryFileSystemProvider());
this.instantiationService.stub(IFileService, fileService);
- const userDataProfilesService = this.instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, logService));
+ const uriIdentityService = this.instantiationService.createInstance(UriIdentityService);
+ this.instantiationService.stub(IUriIdentityService, uriIdentityService);
+
+ const userDataProfilesService = this.instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService));
this.instantiationService.stub(IStorageService, this._register(new InMemoryStorageService()));
const configurationService = this._register(new ConfigurationService(userDataProfilesService.defaultProfile.settingsResource, fileService, new NullPolicyService(), logService));
await configurationService.initialize();
this.instantiationService.stub(IConfigurationService, configurationService);
- this.instantiationService.stub(IUriIdentityService, this.instantiationService.createInstance(UriIdentityService));
this.instantiationService.stub(IRequestService, this.testServer);
diff --git a/src/vs/platform/window/common/window.ts b/src/vs/platform/window/common/window.ts
index 1b68d3a166f..39b7fbba2c0 100644
--- a/src/vs/platform/window/common/window.ts
+++ b/src/vs/platform/window/common/window.ts
@@ -135,6 +135,7 @@ export interface IWindowSettings {
readonly enableMenuBarMnemonics: boolean;
readonly closeWhenEmpty: boolean;
readonly clickThroughInactive: boolean;
+ readonly experimental?: { useSandbox: boolean };
}
interface IWindowBorderColors {
diff --git a/src/vs/platform/window/electron-main/window.ts b/src/vs/platform/window/electron-main/window.ts
index fdd9954a76a..e6f0513a76d 100644
--- a/src/vs/platform/window/electron-main/window.ts
+++ b/src/vs/platform/window/electron-main/window.ts
@@ -43,8 +43,6 @@ export interface ICodeWindow extends IDisposable {
ready(): Promise<ICodeWindow>;
setReady(): void;
- readonly hasHiddenTitleBarStyle: boolean;
-
addTabbedWindow(window: ICodeWindow): void;
load(config: INativeWindowConfiguration, options?: { isReload?: boolean }): void;
diff --git a/src/vs/platform/windows/electron-main/window.ts b/src/vs/platform/windows/electron-main/window.ts
index bcc3711f7e8..216d743026f 100644
--- a/src/vs/platform/windows/electron-main/window.ts
+++ b/src/vs/platform/windows/electron-main/window.ts
@@ -18,7 +18,7 @@ import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { ISerializableCommandAction } from 'vs/platform/action/common/action';
import { IBackupMainService } from 'vs/platform/backup/electron-main/backup';
-import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
+import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
@@ -122,9 +122,6 @@ export class CodeWindow extends Disposable implements ICodeWindow {
private _config: INativeWindowConfiguration | undefined;
get config(): INativeWindowConfiguration | undefined { return this._config; }
- private hiddenTitleBarStyle: boolean | undefined;
- get hasHiddenTitleBarStyle(): boolean { return !!this.hiddenTitleBarStyle; }
-
get isExtensionDevelopmentHost(): boolean { return !!(this._config?.extensionDevelopmentPath); }
get isExtensionTestHost(): boolean { return !!(this._config?.extensionTestsPath); }
@@ -140,6 +137,8 @@ export class CodeWindow extends Disposable implements ICodeWindow {
private representedFilename: string | undefined;
private documentEdited: boolean | undefined;
+ private customTrafficLightPosition: boolean | undefined;
+
private readonly whenReadyCallbacks: { (window: ICodeWindow): void }[] = [];
private readonly touchBarGroups: TouchBarSegmentedControl[] = [];
@@ -206,7 +205,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
// Enable experimental css highlight api https://chromestatus.com/feature/5436441440026624
// Refs https://github.com/microsoft/vscode/issues/140098
enableBlinkFeatures: 'HighlightAPI',
- ...this.environmentMainService.sandbox ?
+ ...windowSettings?.experimental?.useSandbox ?
// Sandbox
{
@@ -251,19 +250,21 @@ export class CodeWindow extends Disposable implements ICodeWindow {
const useCustomTitleStyle = getTitleBarStyle(this.configurationService) === 'custom';
if (useCustomTitleStyle) {
options.titleBarStyle = 'hidden';
- this.hiddenTitleBarStyle = true;
if (!isMacintosh) {
options.frame = false;
}
if (useWindowControlsOverlay(this.configurationService, this.environmentMainService)) {
- // This logic will not perfectly guess the right colors to use on initialization,
- // but prefer to keep things simple as it is temporary and not noticeable
+
+ // This logic will not perfectly guess the right colors
+ // to use on initialization, but prefer to keep things
+ // simple as it is temporary and not noticeable
+
const titleBarColor = this.themeMainService.getWindowSplash()?.colorInfo.titleBarBackground ?? this.themeMainService.getBackgroundColor();
const symbolColor = Color.fromHex(titleBarColor).isDarker() ? '#FFFFFF' : '#000000';
options.titleBarOverlay = {
- height: 29, // The smallest size of the title bar on windows accounting for the border on windows 11
+ height: 29, // the smallest size of the title bar on windows accounting for the border on windows 11
color: titleBarColor,
symbolColor
};
@@ -277,29 +278,8 @@ export class CodeWindow extends Disposable implements ICodeWindow {
this._id = this._win.id;
- // re-position traffic light if command center is visible
- if (useCustomTitleStyle && isMacintosh) {
- const ccConfigKey = 'window.commandCenter';
- const trafficLightUpdater = () => {
- // temporarily disabled because of https://github.com/microsoft/vscode/pull/150272#issuecomment-1152218493
- // const on = this.configurationService.getValue<boolean>(ccConfigKey);
- // if (on) {
- // this._win.setTrafficLightPosition({ x: 7, y: 9 });
- // } else {
- // this._win.setTrafficLightPosition({ x: 7, y: 6 });
- // }
- };
- trafficLightUpdater();
- this.configurationService.onDidChangeConfiguration(e => {
- if (e.affectsConfiguration(ccConfigKey)) {
- trafficLightUpdater();
- }
- }, undefined, this._store);
- }
-
- // Open devtools if instructed from command line args
- if (this.environmentMainService.args['open-devtools'] === true) {
- this._win.webContents.openDevTools();
+ if (isMacintosh && useCustomTitleStyle) {
+ this.updateTrafficLightPosition(); // adjust traffic light position depending on command center
}
if (isMacintosh && useCustomTitleStyle) {
@@ -348,6 +328,11 @@ export class CodeWindow extends Disposable implements ICodeWindow {
}
//#endregion
+ // Open devtools if instructed from command line args
+ if (this.environmentMainService.args['open-devtools'] === true) {
+ this._win.webContents.openDevTools();
+ }
+
// respect configured menu bar visibility
this.onConfigurationUpdated();
@@ -359,7 +344,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
}
setRepresentedFilename(filename: string): void {
- if (isMacintosh) {
+ if (isMacintosh && !this.customTrafficLightPosition) { // TODO@electron https://github.com/electron/electron/issues/34822
this._win.setRepresentedFilename(filename);
} else {
this.representedFilename = filename;
@@ -367,7 +352,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
}
getRepresentedFilename(): string | undefined {
- if (isMacintosh) {
+ if (isMacintosh && !this.customTrafficLightPosition) { // TODO@electron https://github.com/electron/electron/issues/34822
return this._win.getRepresentedFilename();
}
@@ -525,7 +510,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
});
// Handle configuration changes
- this._register(this.configurationService.onDidChangeConfiguration(() => this.onConfigurationUpdated()));
+ this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e)));
// Handle Workspace events
this._register(this.workspacesManagementMainService.onDidDeleteUntitledWorkspace(e => this.onDidDeleteUntitledWorkspace(e)));
@@ -735,7 +720,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
}
}
- private onConfigurationUpdated(): void {
+ private onConfigurationUpdated(e?: IConfigurationChangeEvent): void {
// Menubar
const newMenuBarVisibility = this.getMenuBarVisibility();
@@ -744,6 +729,9 @@ export class CodeWindow extends Disposable implements ICodeWindow {
this.setMenuBarVisibility(newMenuBarVisibility);
}
+ // Traffic Lights
+ this.updateTrafficLightPosition(e);
+
// Proxy
let newHttpProxy = (this.configurationService.getValue<string>('http.proxy') || '').trim()
|| (process.env['https_proxy'] || process.env['HTTPS_PROXY'] || process.env['http_proxy'] || process.env['HTTP_PROXY'] || '').trim() // Not standardized.
@@ -812,10 +800,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
this.readyState = ReadyState.NAVIGATING;
// Load URL
- this._win.loadURL(FileAccess.asBrowserUri(this.environmentMainService.sandbox ?
- 'vs/code/electron-sandbox/workbench/workbench.html' :
- 'vs/code/electron-browser/workbench/workbench.html', require
- ).toString(true));
+ this._win.loadURL(FileAccess.asBrowserUri('vs/code/electron-sandbox/workbench/workbench.html', require).toString(true));
// Remember that we did load
const wasLoaded = this.wasLoaded;
@@ -1303,6 +1288,31 @@ export class CodeWindow extends Disposable implements ICodeWindow {
}
}
+ private updateTrafficLightPosition(e?: IConfigurationChangeEvent): void {
+ if (!isMacintosh) {
+ return; // only applies to macOS
+ }
+
+ const commandCenterSettingKey = 'window.commandCenter';
+ if (e && !e.affectsConfiguration(commandCenterSettingKey)) {
+ return;
+ }
+
+ const useCustomTitleStyle = getTitleBarStyle(this.configurationService) === 'custom';
+ if (!useCustomTitleStyle) {
+ return; // only applies with custom title bar
+ }
+
+ const useCustomTrafficLightPosition = this.configurationService.getValue<boolean>(commandCenterSettingKey);
+ if (useCustomTrafficLightPosition) {
+ this._win.setTrafficLightPosition({ x: 7, y: 9 });
+ } else {
+ this._win.setTrafficLightPosition({ x: 7, y: 6 });
+ }
+
+ this.customTrafficLightPosition = useCustomTrafficLightPosition;
+ }
+
handleTitleDoubleClick(): void {
// Respect system settings on mac with regards to title click on windows title
diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts
index 0d2cdc09834..af32788dce5 100644
--- a/src/vs/platform/windows/electron-main/windowsMainService.ts
+++ b/src/vs/platform/windows/electron-main/windowsMainService.ts
@@ -785,7 +785,12 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
private doExtractPathsFromCLI(cli: NativeParsedArgs): IPath[] {
const pathsToOpen: IPathToOpen[] = [];
- const pathResolveOptions: IPathResolveOptions = { ignoreFileNotFound: true, gotoLineMode: cli.goto, remoteAuthority: cli.remote || undefined, forceOpenWorkspaceAsFile: false };
+ const pathResolveOptions: IPathResolveOptions = {
+ ignoreFileNotFound: true,
+ gotoLineMode: cli.goto,
+ remoteAuthority: cli.remote || undefined,
+ forceOpenWorkspaceAsFile: cli.diff && cli._.length === 2 // special case diff mode to force open workspace as file (https://github.com/microsoft/vscode/issues/149731)
+ };
// folder uris
const folderUris = cli['folder-uri'];
diff --git a/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts b/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts
index b17048b3ad2..827801bf891 100644
--- a/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts
+++ b/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts
@@ -49,7 +49,6 @@ suite('WindowsFinder', () => {
lastFocusTime = options.lastFocusTime;
isFullScreen = false;
isReady = true;
- hasHiddenTitleBarStyle = false;
ready(): Promise<ICodeWindow> { throw new Error('Method not implemented.'); }
setReady(): void { throw new Error('Method not implemented.'); }
diff --git a/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts
index 3801775bee0..2f29d96f1ab 100644
--- a/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts
+++ b/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts
@@ -377,7 +377,10 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa
});
try {
- app.setJumpList(jumpList);
+ const res = app.setJumpList(jumpList);
+ if (res && res !== 'ok') {
+ this.logService.warn(`updateWindowsJumpList#setJumpList unexpected result: ${res}`);
+ }
} catch (error) {
this.logService.warn('updateWindowsJumpList#setJumpList', error); // since setJumpList is relatively new API, make sure to guard for errors
}
diff --git a/src/vs/server/node/extensionsScannerService.ts b/src/vs/server/node/extensionsScannerService.ts
index 5f2be934b4b..b780ab7d6e8 100644
--- a/src/vs/server/node/extensionsScannerService.ts
+++ b/src/vs/server/node/extensionsScannerService.ts
@@ -10,6 +10,7 @@ import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagemen
import { AbstractExtensionsScannerService, IExtensionsScannerService, Translations } from 'vs/platform/extensionManagement/common/extensionsScannerService';
import { MANIFEST_CACHE_FOLDER } from 'vs/platform/extensions/common/extensions';
import { IFileService } from 'vs/platform/files/common/files';
+import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { IProductService } from 'vs/platform/product/common/productService';
import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
@@ -24,13 +25,14 @@ export class ExtensionsScannerService extends AbstractExtensionsScannerService i
@ILogService logService: ILogService,
@INativeEnvironmentService private readonly nativeEnvironmentService: INativeEnvironmentService,
@IProductService productService: IProductService,
+ @IInstantiationService instantiationService: IInstantiationService,
) {
super(
URI.file(nativeEnvironmentService.builtinExtensionsPath),
URI.file(nativeEnvironmentService.extensionsPath),
joinPath(nativeEnvironmentService.userHome, '.vscode-oss-dev', 'extensions', 'control.json'),
joinPath(URI.file(nativeEnvironmentService.userDataPath), MANIFEST_CACHE_FOLDER),
- userDataProfilesService, extensionsProfileScannerService, fileService, logService, nativeEnvironmentService, productService);
+ userDataProfilesService, extensionsProfileScannerService, fileService, logService, nativeEnvironmentService, productService, instantiationService);
}
protected async getTranslations(language: string): Promise<Translations> {
diff --git a/src/vs/server/node/remoteExtensionHostAgentCli.ts b/src/vs/server/node/remoteExtensionHostAgentCli.ts
index 653748724e0..52647624f79 100644
--- a/src/vs/server/node/remoteExtensionHostAgentCli.ts
+++ b/src/vs/server/node/remoteExtensionHostAgentCli.ts
@@ -93,8 +93,11 @@ class CliMain extends Disposable {
services.set(IFileService, fileService);
fileService.registerProvider(Schemas.file, this._register(new DiskFileSystemProvider(logService)));
+ const uriIdentityService = new UriIdentityService(fileService);
+ services.set(IUriIdentityService, uriIdentityService);
+
// User Data Profiles
- const userDataProfilesService = this._register(new UserDataProfilesService(environmentService, fileService, logService));
+ const userDataProfilesService = this._register(new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService));
services.set(IUserDataProfilesService, userDataProfilesService);
// Configuration
@@ -102,7 +105,6 @@ class CliMain extends Disposable {
await configurationService.initialize();
services.set(IConfigurationService, configurationService);
- services.set(IUriIdentityService, new UriIdentityService(fileService));
services.set(IRequestService, new SyncDescriptor(RequestService));
services.set(IDownloadService, new SyncDescriptor(DownloadService));
services.set(ITelemetryService, NullTelemetryService);
diff --git a/src/vs/server/node/remoteTerminalChannel.ts b/src/vs/server/node/remoteTerminalChannel.ts
index 367dcfb7b76..3c11876cfe9 100644
--- a/src/vs/server/node/remoteTerminalChannel.ts
+++ b/src/vs/server/node/remoteTerminalChannel.ts
@@ -138,6 +138,7 @@ export class RemoteTerminalChannel extends Disposable implements IServerChannel<
case '$setTerminalLayoutInfo': return this._ptyService.setTerminalLayoutInfo(<ISetTerminalLayoutInfoArgs>args);
case '$serializeTerminalState': return this._ptyService.serializeTerminalState.apply(this._ptyService, args);
case '$reviveTerminalProcesses': return this._ptyService.reviveTerminalProcesses.apply(this._ptyService, args);
+ case '$getRevivedPtyNewId': return this._ptyService.getRevivedPtyNewId.apply(this._ptyService, args);
case '$setUnicodeVersion': return this._ptyService.setUnicodeVersion.apply(this._ptyService, args);
case '$reduceConnectionGraceTime': return this._reduceConnectionGraceTime();
case '$updateIcon': return this._ptyService.updateIcon.apply(this._ptyService, args);
diff --git a/src/vs/server/node/serverServices.ts b/src/vs/server/node/serverServices.ts
index 797f29fd9e5..ab5f5cb7d57 100644
--- a/src/vs/server/node/serverServices.ts
+++ b/src/vs/server/node/serverServices.ts
@@ -50,7 +50,6 @@ import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProp
import { ITelemetryService, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry';
import { ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService';
import { getPiiPathsFromEnvironment, ITelemetryAppender, NullAppender, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils';
-import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender';
import ErrorTelemetry from 'vs/platform/telemetry/node/errorTelemetry';
import { IPtyService, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
import { PtyHostService } from 'vs/platform/terminal/node/ptyHostService';
@@ -73,6 +72,7 @@ import { ExtensionsScannerService } from 'vs/server/node/extensionsScannerServic
import { ExtensionsProfileScannerService, IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService';
import { IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
import { NullPolicyService } from 'vs/platform/policy/common/policy';
+import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender';
const eventPrefix = 'monacoworkbench';
@@ -110,8 +110,12 @@ export async function setupServerServices(connectionToken: ServerConnectionToken
services.set(IFileService, fileService);
fileService.registerProvider(Schemas.file, disposables.add(new DiskFileSystemProvider(logService)));
+ // URI Identity
+ const uriIdentityService = new UriIdentityService(fileService);
+ services.set(IUriIdentityService, uriIdentityService);
+
// User Data Profiles
- const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, logService);
+ const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService);
services.set(IUserDataProfilesService, userDataProfilesService);
// Configuration
@@ -122,22 +126,19 @@ export async function setupServerServices(connectionToken: ServerConnectionToken
const extensionHostStatusService = new ExtensionHostStatusService();
services.set(IExtensionHostStatusService, extensionHostStatusService);
- // URI Identity
- services.set(IUriIdentityService, new UriIdentityService(fileService));
-
// Request
services.set(IRequestService, new SyncDescriptor(RequestService));
- let appInsightsAppender: ITelemetryAppender = NullAppender;
+ let oneDsAppender: ITelemetryAppender = NullAppender;
const machineId = await getMachineId();
if (supportsTelemetry(productService, environmentService)) {
- if (productService.aiConfig && productService.aiConfig.asimovKey) {
- appInsightsAppender = new AppInsightsAppender(eventPrefix, null, productService.aiConfig.asimovKey);
- disposables.add(toDisposable(() => appInsightsAppender!.flush())); // Ensure the AI appender is disposed so that it flushes remaining data
+ if (productService.aiConfig && productService.aiConfig.ariaKey) {
+ oneDsAppender = new OneDataSystemAppender(configurationService, eventPrefix, null, productService.aiConfig.ariaKey);
+ disposables.add(toDisposable(() => oneDsAppender?.flush())); // Ensure the AI appender is disposed so that it flushes remaining data
}
const config: ITelemetryServiceConfig = {
- appenders: [appInsightsAppender],
+ appenders: [oneDsAppender],
commonProperties: resolveCommonProperties(fileService, release(), hostname(), process.arch, productService.commit, productService.version + '-remote', machineId, productService.msftInternalDomains, environmentService.installSourcePath, 'remoteAgent'),
piiPaths: getPiiPathsFromEnvironment(environmentService)
};
@@ -193,7 +194,7 @@ export async function setupServerServices(connectionToken: ServerConnectionToken
const remoteExtensionEnvironmentChannel = new RemoteAgentEnvironmentChannel(connectionToken, environmentService, userDataProfilesService, extensionManagementCLIService, logService, extensionHostStatusService, extensionsScannerService);
socketServer.registerChannel('remoteextensionsenvironment', remoteExtensionEnvironmentChannel);
- const telemetryChannel = new ServerTelemetryChannel(accessor.get(IServerTelemetryService), appInsightsAppender);
+ const telemetryChannel = new ServerTelemetryChannel(accessor.get(IServerTelemetryService), oneDsAppender);
socketServer.registerChannel('telemetry', telemetryChannel);
socketServer.registerChannel(REMOTE_TERMINAL_CHANNEL_NAME, new RemoteTerminalChannel(environmentService, logService, ptyService, productService, extensionManagementService));
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';