From 5ed2c5d9fb69e96230e378efde514173a5178aa2 Mon Sep 17 00:00:00 2001 From: Roman Nikitenko Date: Mon, 16 Oct 2023 12:08:04 +0300 Subject: [PATCH] chore: Rebase against the upstream Signed-off-by: Roman Nikitenko --- code/.gitignore | 1 + code/.nvmrc | 2 +- code/.vscode-test.js | 82 + code/.vscode/extensions.json | 7 +- code/.vscode/settings.json | 6 + code/CONTRIBUTING.md | 23 +- .../cli/cli-compile-and-publish.yml | 5 + .../azure-pipelines/cli/cli-darwin-sign.yml | 1 + .../azure-pipelines/cli/cli-win32-sign.yml | 7 +- .../cli/install-rust-posix.yml | 19 +- .../cli/install-rust-win32.yml | 19 +- .../distro/download-distro.yml | 8 +- .../linux/product-build-linux.yml | 6 +- .../azure-pipelines/product-build-pr.yml | 2 + code/build/azure-pipelines/product-build.yml | 6 + .../build/azure-pipelines/product-compile.yml | 13 +- .../build/azure-pipelines/product-publish.yml | 4 +- .../build/azure-pipelines/product-release.yml | 2 + .../publish-types/publish-types.yml | 2 +- code/build/azure-pipelines/sdl-scan.yml | 15 + .../win32/product-build-win32.yml | 4 +- code/build/gulpfile.scan.js | 18 +- code/build/lib/compilation.js | 4 +- code/build/lib/compilation.ts | 2 +- code/build/lib/electron.js | 17 +- code/build/lib/electron.ts | 16 +- code/build/lib/i18n.js | 13 +- code/build/lib/i18n.resources.json | 4 + code/build/lib/i18n.ts | 11 +- code/build/lib/typings/is.d.ts | 11 - code/build/package.json | 15 +- code/build/yarn.lock | 288 +-- code/cli/Cargo.lock | 103 +- code/cli/Cargo.toml | 6 +- code/cli/src/auth.rs | 6 +- code/cli/src/bin/code/legacy_args.rs | 10 +- code/cli/src/commands/serve_web.rs | 14 +- code/cli/src/commands/tunnels.rs | 3 +- code/cli/src/desktop/version_manager.rs | 9 +- code/cli/src/rpc.rs | 75 +- code/cli/src/self_update.rs | 5 +- code/cli/src/tunnels/code_server.rs | 16 +- code/cli/src/tunnels/control_server.rs | 5 +- code/cli/src/tunnels/dev_tunnels.rs | 1 + code/cli/src/tunnels/nosleep_windows.rs | 4 +- code/cli/src/tunnels/service_windows.rs | 10 +- code/cli/src/tunnels/wsl_detect.rs | 6 +- code/cli/src/util/command.rs | 55 +- code/extensions/csharp/cgmanifest.json | 4 +- .../csharp/syntaxes/csharp.tmLanguage.json | 1800 +++++------------ .../css-language-features/package.json | 4 +- .../css-language-features/server/package.json | 6 +- .../css-language-features/server/yarn.lock | 83 +- .../css-language-features/yarn.lock | 46 +- code/extensions/git/package.nls.json | 64 +- code/extensions/git/src/operation.ts | 4 - .../html-language-features/package.json | 4 +- .../server/package.json | 8 +- .../html-language-features/server/yarn.lock | 66 +- .../html-language-features/yarn.lock | 46 +- .../json-language-features/package.json | 2 +- .../server/package.json | 6 +- .../json-language-features/server/yarn.lock | 73 +- .../json-language-features/yarn.lock | 38 +- .../package.nls.json | 2 +- code/extensions/r/cgmanifest.json | 4 +- code/extensions/r/syntaxes/r.tmLanguage.json | 16 +- code/extensions/shared.webpack.config.js | 2 + .../package.nls.json | 10 +- code/extensions/vscode-api-tests/package.json | 13 +- .../src/singlefolder-tests/chat.test.ts | 80 + .../singlefolder-tests/interactive.test.ts | 62 + .../workspace.watcher.test.ts | 54 +- .../test/colorize-fixtures/test.cs | 4 + .../test/colorize-results/test_cs.json | 846 ++++++-- .../test/colorize-results/test_cshtml.json | 110 +- code/package.json | 32 +- code/product.json | 4 +- code/remote/package.json | 8 +- code/remote/yarn.lock | 108 +- code/scripts/test-integration.bat | 10 +- code/scripts/test-integration.sh | 6 +- code/src/main.js | 20 +- code/src/tsconfig.vscode-dts.json | 3 +- .../ui/dropdown/dropdownActionViewItem.ts | 4 + .../base/browser/ui/icons/iconSelectBox.css | 6 +- .../vs/base/browser/ui/icons/iconSelectBox.ts | 10 +- code/src/vs/base/common/async.ts | 57 + code/src/vs/base/common/htmlContent.ts | 2 +- code/src/vs/base/node/id.ts | 15 + .../parts/sandbox/electron-sandbox/globals.ts | 29 + .../sandbox/electron-sandbox/preload-slim.js | 33 +- code/src/vs/base/test/browser/dom.test.ts | 4 +- code/src/vs/base/test/node/id.test.ts | 9 +- code/src/vs/code/electron-main/app.ts | 41 +- code/src/vs/code/node/cliProcessMain.ts | 5 +- .../node/sharedProcess/sharedProcessMain.ts | 2 +- .../vs/editor/common/config/editorOptions.ts | 10 +- .../computeMovedLines.ts | 19 +- .../heuristicSequenceOptimizations.ts | 3 + .../linesSliceCharSequence.ts | 4 + code/src/vs/editor/common/languages.ts | 1 + .../editor/common/services/getIconClasses.ts | 7 +- .../contrib/codeAction/browser/codeAction.ts | 5 + .../editor/test/node/diffing/fixtures.test.ts | 6 +- .../advanced.expected.diff.json | 26 + .../fixtures/false-positive-move/1.tst | 29 + .../fixtures/false-positive-move/2.tst | 34 + .../advanced.expected.diff.json | 140 ++ .../legacy.expected.diff.json | 17 + .../advanced.expected.diff.json | 8 +- .../move-1/advanced.expected.diff.json | 18 + .../noise-1/advanced.expected.diff.json | 6 +- .../noise-2/advanced.expected.diff.json | 4 +- .../node/diffing/fixtures/noisy-move1/1.tst | 544 +++++ .../node/diffing/fixtures/noisy-move1/2.tst | 565 ++++++ .../noisy-move1/advanced.expected.diff.json | 316 +++ .../noisy-move1/legacy.expected.diff.json | 178 ++ .../advanced.expected.diff.json | 4 +- .../diffing/fixtures/shifting-twice/1.txt | 18 + .../diffing/fixtures/shifting-twice/2.txt | 21 + .../advanced.expected.diff.json | 40 + .../shifting-twice/legacy.expected.diff.json | 35 + .../ts-class/advanced.expected.diff.json | 4 +- .../advanced.expected.diff.json | 4 +- code/src/vs/monaco.d.ts | 1 + .../browser/accessibilityService.ts | 2 +- .../browser/accessibleNotificationService.ts | 59 + .../accessibility/common/accessibility.ts | 18 + .../browser/menuEntryActionViewItem.ts | 24 +- .../vs/platform/actions/browser/toolbar.ts | 1 + .../src/vs/platform/actions/common/actions.ts | 3 + .../audioCues/browser/audioCueService.ts | 14 + .../audioCues/browser/media/clear.mp3 | Bin 0 -> 34816 bytes .../platform/audioCues/browser/media/save.mp3 | Bin 0 -> 30428 bytes .../electron-main/auxiliaryWindow.ts | 81 + .../electron-main/auxiliaryWindows.ts | 23 + .../auxiliaryWindowsMainService.ts | 55 + .../common/extensionsScannerService.ts | 8 +- .../node/extensionManagementService.ts | 12 +- .../files/common/diskFileSystemProvider.ts | 14 +- .../common/diskFileSystemProviderClient.ts | 9 +- .../vs/platform/files/common/fileService.ts | 55 +- code/src/vs/platform/files/common/files.ts | 177 +- code/src/vs/platform/files/common/watcher.ts | 39 +- .../files/node/diskFileSystemProvider.ts | 8 +- .../node/diskFileSystemProviderServer.ts | 3 +- .../files/node/watcher/nodejs/nodejsClient.ts | 5 +- .../node/watcher/nodejs/nodejsWatcher.ts | 22 +- .../node/watcher/nodejs/nodejsWatcherLib.ts | 28 +- .../node/watcher/parcel/parcelWatcher.ts | 127 +- .../files/node/watcher/watcherClient.ts | 5 +- .../files/test/browser/fileService.test.ts | 55 +- .../platform/files/test/common/files.test.ts | 47 +- .../files/test/common/watcher.test.ts | 26 +- .../node/nodejsWatcher.integrationTest.ts | 43 +- .../node/parcelWatcher.integrationTest.ts | 90 +- code/src/vs/platform/native/common/native.ts | 4 +- .../electron-main/nativeHostMainService.ts | 128 +- .../quickinput/browser/commandsQuickAccess.ts | 4 +- .../browser/quickInputController.ts | 10 +- .../quickinput/browser/quickInputService.ts | 3 +- .../test/browser/quickinput.test.ts | 3 +- code/src/vs/platform/request/node/proxy.ts | 6 +- .../electron-main/sharedProcess.ts | 2 + .../sharedProcess/node/sharedProcess.ts | 2 + .../telemetry/common/commonProperties.ts | 3 + .../vs/platform/telemetry/common/telemetry.ts | 1 + .../telemetry/electron-main/telemetryUtils.ts | 12 +- .../platform/telemetry/node/telemetryUtils.ts | 15 +- .../commandDetectionCapability.ts | 103 +- .../userData/common/fileUserDataProvider.ts | 3 +- code/src/vs/platform/window/common/window.ts | 1 + .../window/electron-sandbox/window.ts | 7 +- .../windows/electron-main/windowImpl.ts | 51 +- .../platform/windows/electron-main/windows.ts | 39 + .../electron-main/windowsMainService.ts | 35 +- code/src/vs/server/node/serverServices.ts | 9 +- .../api/browser/extensionHost.contribution.ts | 2 + .../api/browser/mainThreadAuthentication.ts | 6 +- .../workbench/api/browser/mainThreadChat.ts | 29 +- .../api/browser/mainThreadChatAgents.ts | 50 +- .../api/browser/mainThreadChatAgents2.ts | 102 + .../api/browser/mainThreadComments.ts | 9 +- .../api/browser/mainThreadFileSystem.ts | 108 +- .../mainThreadFileSystemEventService.ts | 161 +- .../api/browser/mainThreadInlineChat.ts | 7 +- .../workbench/api/browser/mainThreadSpeech.ts | 83 + .../api/browser/mainThreadTerminalService.ts | 2 - .../api/browser/viewsExtensionPoint.ts | 2 +- .../workbench/api/common/extHost.api.impl.ts | 37 +- .../workbench/api/common/extHost.protocol.ts | 65 +- .../api/common/extHostApiCommands.ts | 2 +- .../vs/workbench/api/common/extHostChat.ts | 43 +- .../workbench/api/common/extHostChatAgents.ts | 2 +- .../api/common/extHostChatAgents2.ts | 338 ++++ .../api/common/extHostChatProvider.ts | 95 +- .../workbench/api/common/extHostComments.ts | 2 +- .../common/extHostFileSystemEventService.ts | 50 +- .../workbench/api/common/extHostInlineChat.ts | 82 +- .../workbench/api/common/extHostLanguages.ts | 12 +- .../vs/workbench/api/common/extHostSpeech.ts | 66 + .../api/common/extHostTypeConverters.ts | 51 + .../vs/workbench/api/common/extHostTypes.ts | 20 +- .../extHostFileSystemEventService.test.ts | 4 +- .../test/browser/mainThreadTreeViews.test.ts | 5 +- .../browser/actions/layoutActions.ts | 39 +- .../browser/actions/workspaceActions.ts | 2 +- code/src/vs/workbench/browser/composite.ts | 2 +- code/src/vs/workbench/browser/contextkeys.ts | 12 +- code/src/vs/workbench/browser/layout.ts | 46 +- .../parts/activitybar/activitybarActions.ts | 632 ------ .../parts/activitybar/activitybarPart.ts | 593 ++++-- .../parts/auxiliarybar/auxiliaryBarPart.ts | 13 +- .../auxiliarybar/media/auxiliaryBarPart.css | 14 +- .../workbench/browser/parts/compositeBar.ts | 126 +- .../browser/parts/compositeBarActions.ts | 208 +- .../browser/parts/dialogs/dialogHandler.ts | 4 +- .../parts/editor/editor.contribution.ts | 13 +- .../browser/parts/editor/editorActions.ts | 10 +- .../browser/parts/editor/editorAutoSave.ts | 6 +- .../browser/parts/editor/editorGroupView.ts | 9 +- .../parts/editor/editorGroupWatermark.ts | 41 +- .../browser/parts/editor/editorPanes.ts | 22 +- .../browser/parts/editor/editorParts.ts | 41 +- .../browser/parts/editor/editorTabsControl.ts | 2 +- .../parts/editor/multiEditorTabsControl.ts | 11 +- .../browser/parts/globalCompositeBar.ts | 660 ++++-- .../browser/parts/media/paneCompositePart.css | 139 +- .../notifications/notificationsCommands.ts | 2 +- .../browser/parts/paneCompositeBar.ts | 141 +- .../browser/parts/paneCompositePart.ts | 141 +- .../browser/parts/paneCompositePartService.ts | 7 +- .../browser/parts/panel/media/panelpart.css | 6 +- .../browser/parts/panel/panelActions.ts | 2 +- .../browser/parts/panel/panelPart.ts | 2 +- .../parts/sidebar/media/sidebarpart.css | 4 + .../browser/parts/sidebar/sidebarPart.ts | 114 +- .../parts/titlebar/media/titlebarpart.css | 69 +- .../browser/parts/titlebar/titlebarPart.ts | 22 +- .../browser/parts/titlebar/windowTitle.ts | 37 +- .../workbench/browser/parts/views/viewPane.ts | 2 +- .../browser/parts/views/viewPaneContainer.ts | 23 +- .../browser/parts/views/viewsService.ts | 15 +- code/src/vs/workbench/browser/web.main.ts | 7 +- code/src/vs/workbench/browser/window.ts | 2 +- .../browser/workbench.contribution.ts | 28 +- code/src/vs/workbench/common/activity.ts | 14 +- code/src/vs/workbench/common/contextkeys.ts | 6 + code/src/vs/workbench/common/editor.ts | 8 +- code/src/vs/workbench/common/views.ts | 11 +- .../browser/accessibility.contribution.ts | 3 + .../browser/accessibilityConfiguration.ts | 39 +- .../browser/accessibilityContributions.ts | 4 - .../accessibility/browser/accessibleView.ts | 64 +- .../browser/audioCues.contribution.ts | 27 +- .../browser/preview/bulkEdit.contribution.ts | 30 +- .../chat/browser/actions/chatClearActions.ts | 4 +- .../browser/actions/chatCodeblockActions.ts | 38 +- .../chat/browser/actions/chatTitleActions.ts | 8 +- .../vs/workbench/contrib/chat/browser/chat.ts | 2 + .../browser/chatContributionServiceImpl.ts | 8 +- .../contrib/chat/browser/chatInputPart.ts | 2 +- .../contrib/chat/browser/chatListRenderer.ts | 153 +- .../chatMarkdownDecorationsRenderer.ts | 47 +- .../contrib/chat/browser/chatVariables.ts | 8 +- .../contrib/chat/browser/chatWidget.ts | 30 +- .../browser/contrib/chatInputEditorContrib.ts | 70 +- .../contrib/chat/browser/media/chat.css | 55 +- .../contrib/chat/common/chatAgents.ts | 184 +- .../contrib/chat/common/chatModel.ts | 136 +- .../contrib/chat/common/chatParserTypes.ts | 29 +- .../contrib/chat/common/chatRequestParser.ts | 30 +- .../contrib/chat/common/chatService.ts | 29 +- .../contrib/chat/common/chatServiceImpl.ts | 193 +- .../contrib/chat/common/chatViewModel.ts | 65 +- .../actions/media/voiceChatActions.css | 74 +- .../actions/voiceChatActions.ts | 171 +- .../electron-sandbox/chat.contribution.ts | 37 +- .../ChatRequestParser_agent_not_first.0.snap | 14 +- ...uestParser_agent_with_question_mark.0.snap | 32 +- .../ChatRequestParser_agents.0.snap | 10 +- ...hatRequestParser_agents__subCommand.0.snap | 68 + ..._agents_and_variables_and_multiline.0.snap | 49 +- ..._and_variables_and_multiline__part2.0.snap | 109 + .../__snapshots__/Chat_can_deserialize.0.snap | 12 +- .../__snapshots__/Chat_can_serialize.1.snap | 12 +- .../chat/test/common/chatModel.test.ts | 52 +- .../test/common/chatRequestParser.test.ts | 37 +- .../browser/codeActionsContribution.ts | 4 +- .../browser/accessibility/accessibility.css | 6 + .../quickaccess/gotoSymbolQuickAccess.ts | 4 +- .../codeEditor/browser/saveParticipants.ts | 28 +- .../contrib/comments/browser/commentReply.ts | 12 +- .../comments/browser/commentService.ts | 42 +- .../comments/browser/commentThreadWidget.ts | 5 + .../browser/commentThreadZoneWidget.ts | 26 +- .../comments/browser/commentsController.ts | 64 +- .../comments/browser/commentsTreeViewer.ts | 4 +- .../contrib/comments/browser/commentsView.ts | 2 +- .../comments/common/commentsConfiguration.ts | 1 - .../customEditor/browser/customEditorInput.ts | 1 + .../debug/browser/debug.contribution.ts | 36 +- .../debug/browser/debugEditorActions.ts | 27 +- .../workbench/contrib/debug/browser/repl.ts | 6 +- .../contrib/debug/browser/welcomeView.ts | 5 +- .../browser/editSessions.contribution.ts | 4 +- .../editSessions/browser/editSessionsViews.ts | 9 +- .../editSessions/common/editSessions.ts | 5 +- .../extensions/browser/extensionEditor.ts | 160 +- .../extensions/browser/extensionsViewlet.ts | 59 +- .../extensions/browser/media/extension.css | 2 +- .../extensions/common/extensionsInput.ts | 2 +- .../contrib/files/browser/explorerViewlet.ts | 8 +- .../contrib/files/browser/views/emptyView.ts | 5 +- .../files/browser/views/openEditorsView.ts | 3 +- .../contrib/files/browser/workspaceWatcher.ts | 5 +- .../files/common/dirtyFilesIndicator.ts | 1 - .../files/test/browser/editorAutoSave.test.ts | 4 +- .../contrib/inlineChat/browser/inlineChat.css | 13 +- .../browser/inlineChatController.ts | 43 +- .../browser/inlineChatLivePreviewWidget.ts | 2 +- .../browser/inlineChatStrategies.ts | 2 +- .../inlineChat/browser/inlineChatWidget.ts | 33 +- .../contrib/inlineChat/common/inlineChat.ts | 1 + .../browser/interactiveEditorInput.ts | 1 + .../browser/languageStatus.contribution.ts | 24 +- .../markers/browser/markers.contribution.ts | 2 +- .../contrib/markers/browser/markersView.ts | 2 +- .../contrib/markers/browser/messages.ts | 4 +- .../contrib/outline/notebookOutline.ts | 2 +- .../browser/controller/coreActions.ts | 3 +- .../browser/controller/executeActions.ts | 7 +- .../notebook/browser/notebook.contribution.ts | 4 +- .../notebook/browser/notebookEditorWidget.ts | 3 - .../notebook/browser/notebookOptions.ts | 2 +- .../browser/view/cellParts/cellOutput.ts | 11 +- .../notebook/browser/view/notebookCellList.ts | 18 +- .../notebook/common/notebookEditorInput.ts | 2 +- .../test/browser/notebookCellList.test.ts | 112 +- .../test/browser/testNotebookEditor.ts | 8 +- .../outline/browser/outline.contribution.ts | 4 +- .../output/browser/output.contribution.ts | 24 +- .../browser/media/settingsEditor2.css | 3 - .../preferences/browser/settingsEditor2.ts | 72 +- .../preferences/browser/settingsTreeModels.ts | 5 +- .../common/preferencesContribution.ts | 2 +- .../quickaccess/browser/viewQuickAccess.ts | 2 +- .../remote/browser/explorerViewItems.ts | 2 +- .../contrib/remote/browser/remote.ts | 2 +- .../contrib/remote/browser/tunnelView.ts | 4 +- .../workbench/contrib/scm/browser/activity.ts | 2 +- .../contrib/scm/browser/media/scm.css | 17 + .../vs/workbench/contrib/scm/browser/menus.ts | 10 + .../contrib/scm/browser/scm.contribution.ts | 8 +- .../contrib/scm/browser/scmViewPane.ts | 17 +- .../vs/workbench/contrib/scm/common/scm.ts | 1 + .../search/browser/search.contribution.ts | 8 +- .../speech/common/speech.contribution.ts | 9 + .../contrib/speech/common/speechService.ts | 94 + .../electron-sandbox/workspaceTagsService.ts | 242 ++- .../tasks/browser/terminalTaskSystem.ts | 2 +- .../fish/vendor_conf.d/shellIntegration.fish | 52 +- .../terminal/browser/media/terminal.css | 28 - .../terminal/browser/terminal.contribution.ts | 4 +- .../terminal/browser/terminalActions.ts | 6 +- .../terminal/browser/terminalEditor.ts | 10 +- .../terminal/browser/terminalInstance.ts | 2 +- .../terminal/browser/xterm/xtermTerminal.ts | 6 +- .../terminal.accessibility.contribution.ts | 21 +- .../browser/terminalAccessibilityHelp.ts | 1 + .../terminalAccessibleBufferProvider.ts | 20 +- .../browser/textAreaSyncAddon.ts | 3 +- .../test/browser/bufferContentTracker.test.ts | 6 +- .../browser/media/terminalQuickFix.css | 4 +- .../quickFix/browser/quickFixAddon.ts | 9 + .../contrib/testing/browser/media/testing.css | 8 + .../testing/browser/testing.contribution.ts | 8 +- .../timeline/browser/timeline.contribution.ts | 3 +- .../contrib/timeline/browser/timelinePane.ts | 5 +- .../contrib/update/browser/update.ts | 6 +- .../userDataSync/browser/userDataSync.ts | 36 +- .../userDataSync/browser/userDataSyncViews.ts | 24 +- .../userDataSync.contribution.ts | 6 +- .../browser/webviewEditorInput.ts | 2 +- .../electron-sandbox/actions/windowActions.ts | 2 +- .../electron-sandbox/desktop.contribution.ts | 8 + .../electron-sandbox/desktop.main.ts | 4 +- .../vs/workbench/electron-sandbox/window.ts | 26 +- .../actions/common/menusExtensionPoint.ts | 6 + .../activity/browser/activityService.ts | 83 +- .../services/activity/common/activity.ts | 18 +- .../browser/auxiliaryWindowService.ts | 105 +- .../auxiliaryWindowService.ts | 52 +- .../services}/driver/browser/driver.ts | 13 +- .../services}/driver/common/driver.ts | 1 + .../driver/electron-sandbox/driver.ts | 8 +- .../services/editor/browser/editorService.ts | 11 +- .../electron-sandbox/environmentService.ts | 4 + .../common/webExtensionManagementService.ts | 12 +- .../common/extensionsApiProposals.ts | 7 + .../workbench/services/files/common/files.ts | 22 - .../diskFileSystemProvider.ts | 6 +- .../files/electron-sandbox/watcherClient.ts | 5 +- .../common/languageStatusService.ts | 2 +- .../services/layout/browser/layoutService.ts | 10 + .../panecomposite/browser/panecomposite.ts | 7 - .../browser/media/progressService.css | 5 +- .../progress/browser/progressService.ts | 2 +- .../common/workbenchCommonProperties.ts | 3 +- .../electron-sandbox/telemetryService.ts | 2 +- .../test/node/commonProperties.test.ts | 6 +- .../textfile/browser/textFileService.ts | 3 - .../themes/browser/fileIconThemeData.ts | 17 + .../themes/common/fileIconThemeSchema.ts | 16 + .../themes/common/themeConfiguration.ts | 2 +- .../userDataProfileImportExportService.ts | 2 +- .../browser/userDataSyncWorkbenchService.ts | 6 +- .../userDataSync/common/userDataSync.ts | 6 +- .../views/browser/viewDescriptorService.ts | 30 +- .../views/common/viewContainerModel.ts | 8 +- .../test/browser/viewContainerModel.test.ts | 125 +- .../browser/viewDescriptorService.test.ts | 100 +- .../workingCopyBackupService.test.ts | 1 + .../test/browser/workbenchTestServices.ts | 26 +- .../test/common/workbenchTestServices.ts | 7 + .../electron-sandbox/workbenchTestServices.ts | 4 +- .../src/vs/workbench/workbench.common.main.ts | 4 + code/src/vscode-dts/vscode.d.ts | 2 +- .../vscode.proposed.chatAgents2.d.ts | 265 +++ .../vscode.proposed.chatAgents2Additions.d.ts | 23 + .../vscode.proposed.chatRequestAccess.d.ts | 72 +- ...osed.contribSourceControlInputBoxMenu.d.ts | 7 + ...code.proposed.createFileSystemWatcher.d.ts | 38 + .../vscode.proposed.defaultChatAgent.d.ts | 14 + .../vscode.proposed.interactive.d.ts | 24 +- ...scode.proposed.interactiveUserActions.d.ts | 8 +- .../vscode.proposed.languageStatusText.d.ts | 12 + .../vscode-dts/vscode.proposed.resolvers.d.ts | 2 +- .../vscode-dts/vscode.proposed.speech.d.ts | 35 + code/test/automation/src/code.ts | 4 + code/test/automation/src/extensions.ts | 2 +- code/test/automation/src/playwrightDriver.ts | 4 + code/test/automation/src/quickaccess.ts | 49 +- code/test/automation/src/task.ts | 2 +- code/test/automation/src/terminal.ts | 6 +- code/test/automation/src/workbench.ts | 2 +- .../tools/copy-driver-definition.js | 2 +- code/test/automation/yarn.lock | 6 +- code/test/integration/browser/package.json | 1 - code/test/integration/browser/src/index.ts | 70 +- code/test/integration/browser/yarn.lock | 5 - code/test/leaks/yarn.lock | 6 +- code/test/smoke/package.json | 1 - .../src/areas/preferences/preferences.test.ts | 22 + code/test/smoke/yarn.lock | 149 +- code/test/unit/browser/index.js | 107 +- code/test/unit/electron/index.js | 103 +- code/test/unit/node/index.js | 67 +- code/yarn.lock | 1459 +++++-------- 460 files changed, 12897 insertions(+), 7163 deletions(-) create mode 100644 code/.vscode-test.js delete mode 100644 code/build/lib/typings/is.d.ts create mode 100644 code/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts create mode 100644 code/extensions/vscode-api-tests/src/singlefolder-tests/interactive.test.ts create mode 100644 code/src/vs/editor/test/node/diffing/fixtures/false-positive-move/1.tst create mode 100644 code/src/vs/editor/test/node/diffing/fixtures/false-positive-move/2.tst create mode 100644 code/src/vs/editor/test/node/diffing/fixtures/false-positive-move/advanced.expected.diff.json create mode 100644 code/src/vs/editor/test/node/diffing/fixtures/false-positive-move/legacy.expected.diff.json create mode 100644 code/src/vs/editor/test/node/diffing/fixtures/noisy-move1/1.tst create mode 100644 code/src/vs/editor/test/node/diffing/fixtures/noisy-move1/2.tst create mode 100644 code/src/vs/editor/test/node/diffing/fixtures/noisy-move1/advanced.expected.diff.json create mode 100644 code/src/vs/editor/test/node/diffing/fixtures/noisy-move1/legacy.expected.diff.json create mode 100644 code/src/vs/editor/test/node/diffing/fixtures/shifting-twice/1.txt create mode 100644 code/src/vs/editor/test/node/diffing/fixtures/shifting-twice/2.txt create mode 100644 code/src/vs/editor/test/node/diffing/fixtures/shifting-twice/advanced.expected.diff.json create mode 100644 code/src/vs/editor/test/node/diffing/fixtures/shifting-twice/legacy.expected.diff.json create mode 100644 code/src/vs/platform/accessibility/browser/accessibleNotificationService.ts create mode 100644 code/src/vs/platform/audioCues/browser/media/clear.mp3 create mode 100644 code/src/vs/platform/audioCues/browser/media/save.mp3 create mode 100644 code/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts create mode 100644 code/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows.ts create mode 100644 code/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts create mode 100644 code/src/vs/workbench/api/browser/mainThreadChatAgents2.ts create mode 100644 code/src/vs/workbench/api/browser/mainThreadSpeech.ts create mode 100644 code/src/vs/workbench/api/common/extHostChatAgents2.ts create mode 100644 code/src/vs/workbench/api/common/extHostSpeech.ts delete mode 100644 code/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts create mode 100644 code/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents__subCommand.0.snap create mode 100644 code/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline__part2.0.snap create mode 100644 code/src/vs/workbench/contrib/speech/common/speech.contribution.ts create mode 100644 code/src/vs/workbench/contrib/speech/common/speechService.ts rename code/src/vs/{platform => workbench/services}/driver/browser/driver.ts (92%) rename code/src/vs/{platform => workbench/services}/driver/common/driver.ts (97%) rename code/src/vs/{platform => workbench/services}/driver/electron-sandbox/driver.ts (76%) delete mode 100644 code/src/vs/workbench/services/files/common/files.ts create mode 100644 code/src/vscode-dts/vscode.proposed.chatAgents2.d.ts create mode 100644 code/src/vscode-dts/vscode.proposed.chatAgents2Additions.d.ts create mode 100644 code/src/vscode-dts/vscode.proposed.contribSourceControlInputBoxMenu.d.ts create mode 100644 code/src/vscode-dts/vscode.proposed.createFileSystemWatcher.d.ts create mode 100644 code/src/vscode-dts/vscode.proposed.defaultChatAgent.d.ts create mode 100644 code/src/vscode-dts/vscode.proposed.languageStatusText.d.ts create mode 100644 code/src/vscode-dts/vscode.proposed.speech.d.ts diff --git a/code/.gitignore b/code/.gitignore index 0601e762dff..c0459c86043 100644 --- a/code/.gitignore +++ b/code/.gitignore @@ -19,3 +19,4 @@ vscode.db /cli/openssl product.overrides.json *.snap.actual +.vscode-test diff --git a/code/.nvmrc b/code/.nvmrc index bcaa3377df1..aacb5181047 100644 --- a/code/.nvmrc +++ b/code/.nvmrc @@ -1 +1 @@ -18.17 \ No newline at end of file +18.17 diff --git a/code/.vscode-test.js b/code/.vscode-test.js new file mode 100644 index 00000000000..e09b8443b7f --- /dev/null +++ b/code/.vscode-test.js @@ -0,0 +1,82 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check + +const path = require('path'); +const { defineConfig } = require('@vscode/test-cli'); +const os = require('os'); + +/** + * A list of extension folders who have opted into tests, or configuration objects. + * Edit me to add more! + * + * @type {Array & { label: string })>} + */ +const extensions = [ + { + label: 'markdown-language-features', + workspaceFolder: `extensions/markdown-language-features/test-workspace`, + mocha: { timeout: 60_000 } + }, + { + label: 'ipynb', + workspaceFolder: path.join(os.tmpdir(), `ipynb-${Math.floor(Math.random() * 100000)}`), + mocha: { timeout: 60_000 } + }, + { + label: 'notebook-renderers', + workspaceFolder: path.join(os.tmpdir(), `nbout-${Math.floor(Math.random() * 100000)}`), + mocha: { timeout: 60_000 } + }, +]; + + +const defaultLaunchArgs = process.env.API_TESTS_EXTRA_ARGS?.split(' ') || [ + '--disable-telemetry', '--skip-welcome', '--skip-release-notes', `--crash-reporter-directory=${__dirname}/.build/crashes`, `--logsPath=${__dirname}/.build/logs/integration-tests`, '--no-cached-data', '--disable-updates', '--use-inmemory-secretstorage', '--disable-extensions', '--disable-workspace-trust' +]; + +module.exports = defineConfig(extensions.map(extension => { + /** @type {import('@vscode/test-cli').TestConfiguration} */ + const config = typeof extension === 'object' + ? { files: `extensions/${extension.label}/out/**/*.test.js`, ...extension } + : { files: `extensions/${extension}/out/**/*.test.js`, label: extension }; + + config.mocha ??= {}; + if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) { + let suite = ''; + if (process.env.VSCODE_BROWSER) { + suite = `${process.env.VSCODE_BROWSER} Browser Integration ${config.label} tests`; + } else if (process.env.REMOTE_VSCODE) { + suite = `Remote Integration ${config.label} tests`; + } else { + suite = `Integration ${config.label} tests`; + } + + config.mocha.reporter = 'mocha-multi-reporters'; + config.mocha.reporterOptions = { + reporterEnabled: 'spec, mocha-junit-reporter', + mochaJunitReporterReporterOptions: { + testsuitesTitle: `${suite} ${process.platform}`, + mochaFile: path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${process.arch}-${suite.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`) + } + }; + } + + if (!config.platform || config.platform === 'desktop') { + config.launchArgs = defaultLaunchArgs; + config.useInstallation = { + fromPath: process.env.INTEGRATION_TEST_ELECTRON_PATH || `${__dirname}/scripts/code.${process.platform === 'win32' ? 'bat' : 'sh'}`, + }; + config.env = { + ...config.env, + VSCODE_SKIP_PRELAUNCH: '1', + }; + } else { + // web configs not supported, yet + } + + return config; +})); diff --git a/code/.vscode/extensions.json b/code/.vscode/extensions.json index 0d3101d30a8..2dd8a08255d 100644 --- a/code/.vscode/extensions.json +++ b/code/.vscode/extensions.json @@ -3,9 +3,10 @@ // for the documentation about the extensions.json format "recommendations": [ "dbaeumer.vscode-eslint", - "EditorConfig.EditorConfig", - "GitHub.vscode-pull-request-github", + "editorconfig.editorconfig", + "github.vscode-pull-request-github", "ms-vscode.vscode-github-issue-notebooks", - "ms-vscode.vscode-selfhost-test-provider" + "ms-vscode.vscode-selfhost-test-provider", + "ms-vscode.extension-test-runner" ] } diff --git a/code/.vscode/settings.json b/code/.vscode/settings.json index 0069fee3d12..0c73cc745a6 100644 --- a/code/.vscode/settings.json +++ b/code/.vscode/settings.json @@ -6,6 +6,7 @@ ".build": true, ".profile-oss": true, "**/.DS_Store": true, + ".vscode-test": true, "cli/target": true, "build/**/*.js": { "when": "$(basename).ts" @@ -143,6 +144,11 @@ "${workspaceFolder}/build/**/*.js" ] }, + "extension-test-runner.debugOptions": { + "outFiles": [ + "${workspaceFolder}/extensions/*/out/**/*.js", + ] + }, "githubPullRequests.assignCreated": "${user}", "githubPullRequests.defaultMergeMethod": "squash", "githubPullRequests.ignoredPullRequestBranches": [ diff --git a/code/CONTRIBUTING.md b/code/CONTRIBUTING.md index 2a019ed7205..42b369c7ecf 100644 --- a/code/CONTRIBUTING.md +++ b/code/CONTRIBUTING.md @@ -2,11 +2,11 @@ Welcome, and thank you for your interest in contributing to VS Code! -There are many ways in which you can contribute, beyond writing code. The goal of this document is to provide a high-level overview of how you can get involved. +There are several ways in which you can contribute, beyond writing code. The goal of this document is to provide a high-level overview of how you can get involved. ## Asking Questions -Have a question? Rather than opening an issue, please ask away on [Stack Overflow](https://stackoverflow.com/questions/tagged/vscode) using the tag `vscode`. +Have a question? Instead of opening an issue, please ask on [Stack Overflow](https://stackoverflow.com/questions/tagged/vscode) using the tag `vscode`. The active community will be eager to assist you. Your well-worded question will serve as a resource to others searching for help. @@ -18,7 +18,7 @@ See the [Feedback Channels](https://github.com/microsoft/vscode/wiki/Feedback-Ch ## Reporting Issues -Have you identified a reproducible problem in VS Code? Have a feature request? We want to hear about it! Here's how you can report your issue as effectively as possible. +Have you identified a reproducible problem in VS Code? Do you have a feature request? We want to hear about it! Here's how you can report your issue as effectively as possible. ### Identify Where to Report @@ -52,21 +52,13 @@ The built-in tool for reporting an issue, which you can access by using `Report Please include the following with each issue: * Version of VS Code - * Your operating system - * List of extensions that you have installed - * Reproducible steps (1... 2... 3...) that cause the issue - * What you expected to see, versus what you actually saw - * Images, animations, or a link to a video showing the issue occurring - * A code snippet that demonstrates the issue or a link to a code repository the developers can easily pull down to recreate the issue locally - * **Note:** Because the developers need to copy and paste the code snippet, including a code snippet as a media file (i.e. .gif) is not sufficient. - * Errors from the Dev Tools Console (open from the menu: Help > Toggle Developer Tools) ### Creating Pull Requests @@ -78,22 +70,20 @@ Please include the following with each issue: Please remember to do the following: * [ ] Search the issue repository to ensure your report is a new issue - * [ ] Recreate the issue after disabling all extensions - * [ ] Simplify your code around the issue to better isolate the problem Don't feel bad if the developers can't reproduce the issue right away. They will simply ask for more information! ### Follow Your Issue -Once submitted, your report will go into the [issue tracking](https://github.com/microsoft/vscode/wiki/Issue-Tracking) workflow. Be sure to understand what will happen next, so you know what to expect, and how to continue to assist throughout the process. +Once submitted, your report will go into the [issue tracking](https://github.com/microsoft/vscode/wiki/Issue-Tracking) workflow. Be sure to understand what will happen next, so you know what to expect and how to continue to assist throughout the process. ## Automated Issue Management We use GitHub Actions to help us manage issues. These Actions and their descriptions can be [viewed here](https://github.com/microsoft/vscode-github-triage-actions). Some examples of what these Actions do are: -* Automatically closes any issue marked `info-needed` if there has been no response in the past 7 days. +* Automatically close any issue marked `info-needed` if there has been no response in the past 7 days. * Automatically lock issues 45 days after they are closed. * Automatically implement the VS Code [feature request pipeline](https://github.com/microsoft/vscode/wiki/Issues-Triaging#managing-feature-requests). @@ -101,8 +91,7 @@ If you believe the bot got something wrong, please open a new issue and let us k ## Contributing Fixes -If you are interested in writing code to fix issues, -please see [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute) in the wiki. +If you are interested in writing code to fix issues, please see [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute) in the wiki. ## Thank You diff --git a/code/build/azure-pipelines/cli/cli-compile-and-publish.yml b/code/build/azure-pipelines/cli/cli-compile-and-publish.yml index 37d68e2c51e..af9960d7f5b 100644 --- a/code/build/azure-pipelines/cli/cli-compile-and-publish.yml +++ b/code/build/azure-pipelines/cli/cli-compile-and-publish.yml @@ -57,8 +57,10 @@ steps: Write-Host "##vso[task.setvariable variable=VSCODE_CLI_APPLICATION_NAME]$env:VSCODE_CLI_APPLICATION_NAME" Move-Item -Path $(Build.SourcesDirectory)/cli/target/${{ parameters.VSCODE_CLI_TARGET }}/release/code.exe -Destination "$(Build.ArtifactStagingDirectory)/${env:VSCODE_CLI_APPLICATION_NAME}.exe" + displayName: Stage CLI - task: ArchiveFiles@2 + displayName: Archive CLI inputs: rootFolderOrFile: $(Build.ArtifactStagingDirectory)/$(VSCODE_CLI_APPLICATION_NAME).exe includeRootFolder: false @@ -76,9 +78,11 @@ steps: echo "##vso[task.setvariable variable=VSCODE_CLI_APPLICATION_NAME]$VSCODE_CLI_APPLICATION_NAME" mv $(Build.SourcesDirectory)/cli/target/${{ parameters.VSCODE_CLI_TARGET }}/release/code $(Build.ArtifactStagingDirectory)/$VSCODE_CLI_APPLICATION_NAME + displayName: Stage CLI - ${{ if contains(parameters.VSCODE_CLI_TARGET, '-darwin') }}: - task: ArchiveFiles@2 + displayName: Archive CLI inputs: rootFolderOrFile: $(Build.ArtifactStagingDirectory)/$(VSCODE_CLI_APPLICATION_NAME) includeRootFolder: false @@ -91,6 +95,7 @@ steps: - ${{ else }}: - task: ArchiveFiles@2 + displayName: Archive CLI inputs: rootFolderOrFile: $(Build.ArtifactStagingDirectory)/$(VSCODE_CLI_APPLICATION_NAME) includeRootFolder: false diff --git a/code/build/azure-pipelines/cli/cli-darwin-sign.yml b/code/build/azure-pipelines/cli/cli-darwin-sign.yml index 7d4cbdaecbf..b4cfdc8f10f 100644 --- a/code/build/azure-pipelines/cli/cli-darwin-sign.yml +++ b/code/build/azure-pipelines/cli/cli-darwin-sign.yml @@ -41,4 +41,5 @@ steps: displayName: Set asset id variable - publish: $(Build.ArtifactStagingDirectory)/pkg/${{ target }}/$(ASSET_ID).zip + displayName: Publish signed artifact with ID $(ASSET_ID) artifact: $(ASSET_ID) diff --git a/code/build/azure-pipelines/cli/cli-win32-sign.yml b/code/build/azure-pipelines/cli/cli-win32-sign.yml index fe46171aaac..2880eafb85d 100644 --- a/code/build/azure-pipelines/cli/cli-win32-sign.yml +++ b/code/build/azure-pipelines/cli/cli-win32-sign.yml @@ -20,12 +20,13 @@ steps: - ${{ each target in parameters.VSCODE_CLI_ARTIFACTS }}: - task: DownloadPipelineArtifact@2 - displayName: Download artifacts + displayName: Download artifact inputs: artifact: ${{ target }} path: $(Build.ArtifactStagingDirectory)/pkg/${{ target }} - task: ExtractFiles@1 + displayName: Extract artifact inputs: archiveFilePatterns: $(Build.ArtifactStagingDirectory)/pkg/${{ target }}/*.zip destinationFolder: $(Build.ArtifactStagingDirectory)/sign/${{ target }} @@ -42,7 +43,7 @@ steps: displayName: Find ESRP CLI - powershell: node build\azure-pipelines\common\sign $env:EsrpCliDllPath windows $(ESRP-PKI) $(esrp-aad-username) $(esrp-aad-password) $(Build.ArtifactStagingDirectory)/sign "*.exe" - displayName: "Code sign" + displayName: Codesign executable - ${{ each target in parameters.VSCODE_CLI_ARTIFACTS }}: - powershell: | @@ -51,6 +52,7 @@ steps: displayName: Set asset id variable - task: ArchiveFiles@2 + displayName: Archive signed files inputs: rootFolderOrFile: $(Build.ArtifactStagingDirectory)/sign/${{ target }} includeRootFolder: false @@ -58,4 +60,5 @@ steps: archiveFile: $(Build.ArtifactStagingDirectory)/$(ASSET_ID).zip - publish: $(Build.ArtifactStagingDirectory)/$(ASSET_ID).zip + displayName: Publish signed artifact with ID $(ASSET_ID) artifact: $(ASSET_ID) diff --git a/code/build/azure-pipelines/cli/install-rust-posix.yml b/code/build/azure-pipelines/cli/install-rust-posix.yml index d6022a44f7a..00e3ecbdc51 100644 --- a/code/build/azure-pipelines/cli/install-rust-posix.yml +++ b/code/build/azure-pipelines/cli/install-rust-posix.yml @@ -1,7 +1,7 @@ parameters: - name: channel type: string - default: 1.71.0 + default: 1.73.0 - name: targets default: [] type: object @@ -9,13 +9,25 @@ parameters: # Todo: use 1ES pipeline once extension is installed in ADO steps: + - task: RustInstaller@1 + inputs: + rustVersion: ms-${{ parameters.channel }} + cratesIoFeedOverride: $(CARGO_REGISTRY) + additionalTargets: ${{ join(' ', parameters.targets) }} + toolchainFeed: https://pkgs.dev.azure.com/monacotools/Monaco/_packaging/vscode/nuget/v3/index.json + default: true + addToPath: true + displayName: Install MSFT Rust + condition: and(succeeded(), ne(variables['CARGO_REGISTRY'], 'none')) + - script: | set -e curl https://sh.rustup.rs -sSf | sh -s -- -y --profile minimal --default-toolchain $RUSTUP_TOOLCHAIN echo "##vso[task.setvariable variable=PATH;]$PATH:$HOME/.cargo/bin" env: RUSTUP_TOOLCHAIN: ${{ parameters.channel }} - displayName: "Install Rust" + displayName: Install OSS Rust + condition: and(succeeded(), eq(variables['CARGO_REGISTRY'], 'none')) - script: | set -e @@ -24,14 +36,15 @@ steps: env: RUSTUP_TOOLCHAIN: ${{ parameters.channel }} displayName: "Set Rust version" + condition: and(succeeded(), eq(variables['CARGO_REGISTRY'], 'none')) - ${{ each target in parameters.targets }}: - script: rustup target add ${{ target }} displayName: "Adding Rust target '${{ target }}'" + condition: and(succeeded(), eq(variables['CARGO_REGISTRY'], 'none')) - script: | set -e rustc --version cargo --version - rustup --version displayName: "Check Rust versions" diff --git a/code/build/azure-pipelines/cli/install-rust-win32.yml b/code/build/azure-pipelines/cli/install-rust-win32.yml index 373c41dd95f..3c88d9adc86 100644 --- a/code/build/azure-pipelines/cli/install-rust-win32.yml +++ b/code/build/azure-pipelines/cli/install-rust-win32.yml @@ -1,7 +1,7 @@ parameters: - name: channel type: string - default: 1.71.0 + default: 1.73.0 - name: targets default: [] type: object @@ -9,6 +9,17 @@ parameters: # Todo: use 1ES pipeline once extension is installed in ADO steps: + - task: RustInstaller@1 + inputs: + rustVersion: ms-${{ parameters.channel }} + cratesIoFeedOverride: $(CARGO_REGISTRY) + additionalTargets: ${{ join(' ', parameters.targets) }} + toolchainFeed: https://pkgs.dev.azure.com/monacotools/Monaco/_packaging/vscode/nuget/v3/index.json + default: true + addToPath: true + displayName: Install MSFT Rust + condition: and(succeeded(), ne(variables['CARGO_REGISTRY'], 'none')) + - powershell: | . build/azure-pipelines/win32/exec.ps1 Invoke-WebRequest -Uri "https://win.rustup.rs" -Outfile $(Build.ArtifactStagingDirectory)/rustup-init.exe @@ -16,7 +27,8 @@ steps: echo "##vso[task.prependpath]$env:USERPROFILE\.cargo\bin" env: RUSTUP_TOOLCHAIN: ${{ parameters.channel }} - displayName: "Install Rust" + displayName: Install OSS Rust + condition: and(succeeded(), eq(variables['CARGO_REGISTRY'], 'none')) - powershell: | . build/azure-pipelines/win32/exec.ps1 @@ -25,14 +37,15 @@ steps: env: RUSTUP_TOOLCHAIN: ${{ parameters.channel }} displayName: "Set Rust version" + condition: and(succeeded(), eq(variables['CARGO_REGISTRY'], 'none')) - ${{ each target in parameters.targets }}: - script: rustup target add ${{ target }} displayName: "Adding Rust target '${{ target }}'" + condition: and(succeeded(), eq(variables['CARGO_REGISTRY'], 'none')) - powershell: | . build/azure-pipelines/win32/exec.ps1 exec { rustc --version } exec { cargo --version } - exec { rustup --version } displayName: "Check Rust versions" diff --git a/code/build/azure-pipelines/distro/download-distro.yml b/code/build/azure-pipelines/distro/download-distro.yml index 2e727b28b4d..a703992aab2 100644 --- a/code/build/azure-pipelines/distro/download-distro.yml +++ b/code/build/azure-pipelines/distro/download-distro.yml @@ -10,7 +10,7 @@ steps: - pwsh: | "machine github.com`nlogin vscode`npassword $(github-distro-mixin-password)" | Out-File "$Home/_netrc" -Encoding ASCII condition: and(succeeded(), contains(variables['Agent.OS'], 'windows')) - displayName: Setup distro auth + displayName: Setup distro auth (Windows) - pwsh: | $ErrorActionPreference = "Stop" @@ -26,7 +26,7 @@ steps: Expand-Archive -Path $ArchivePath -DestinationPath .build Rename-Item -Path ".build/microsoft-vscode-distro-$DistroVersion" -NewName distro condition: and(succeeded(), contains(variables['Agent.OS'], 'windows')) - displayName: Download distro + displayName: Download distro (Windows) - script: | mkdir -p .build @@ -36,7 +36,7 @@ steps: password $(github-distro-mixin-password) EOF condition: and(succeeded(), not(contains(variables['Agent.OS'], 'windows'))) - displayName: Setup distro auth + displayName: Setup distro auth (non-Windows) - script: | set -e @@ -53,4 +53,4 @@ steps: mv .build/microsoft-vscode-distro-$DistroVersion .build/distro cp remote/.yarnrc .build/distro/npm/remote/.yarnrc condition: and(succeeded(), not(contains(variables['Agent.OS'], 'windows'))) - displayName: Download distro + displayName: Download distro (non-Windows) diff --git a/code/build/azure-pipelines/linux/product-build-linux.yml b/code/build/azure-pipelines/linux/product-build-linux.yml index 2f43515414f..3923d7d105f 100644 --- a/code/build/azure-pipelines/linux/product-build-linux.yml +++ b/code/build/azure-pipelines/linux/product-build-linux.yml @@ -144,7 +144,7 @@ steps: VSCODE_HOST_MOUNT: "/mnt/vss/_work/1/s" ${{ if or(eq(parameters.VSCODE_ARCH, 'x64'), eq(parameters.VSCODE_ARCH, 'arm64')) }}: VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME: vscodehub.azurecr.io/vscode-linux-build-agent:centos7-devtoolset8-$(VSCODE_ARCH) - displayName: Install dependencies + displayName: Install dependencies (non-OSS) condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - script: node build/azure-pipelines/distro/mixin-npm @@ -173,7 +173,7 @@ steps: ELECTRON_SKIP_BINARY_DOWNLOAD: 1 PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Install dependencies + displayName: Install dependencies (OSS) condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - script: | @@ -252,7 +252,7 @@ steps: - script: yarn gulp "transpile-client-swc" "transpile-extensions" env: GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Transpile + displayName: Transpile client and extensions - ${{ if or(eq(parameters.VSCODE_RUN_UNIT_TESTS, true), eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true), eq(parameters.VSCODE_RUN_SMOKE_TESTS, true)) }}: - template: product-build-linux-test.yml diff --git a/code/build/azure-pipelines/product-build-pr.yml b/code/build/azure-pipelines/product-build-pr.yml index 90f85c5817f..b5036070730 100644 --- a/code/build/azure-pipelines/product-build-pr.yml +++ b/code/build/azure-pipelines/product-build-pr.yml @@ -13,6 +13,8 @@ variables: value: true - name: NPM_REGISTRY value: "none" + - name: CARGO_REGISTRY + value: "none" - name: VSCODE_CIBUILD value: ${{ in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI') }} - name: VSCODE_QUALITY diff --git a/code/build/azure-pipelines/product-build.yml b/code/build/azure-pipelines/product-build.yml index 2d4ad8aa84a..383fa1576bc 100644 --- a/code/build/azure-pipelines/product-build.yml +++ b/code/build/azure-pipelines/product-build.yml @@ -24,6 +24,10 @@ parameters: displayName: "Custom NPM Registry" type: string default: 'https://pkgs.dev.azure.com/monacotools/Monaco/_packaging/vscode/npm/registry/' + - name: CARGO_REGISTRY + displayName: "Custom Cargo Registry" + type: string + default: 'sparse+https://pkgs.dev.azure.com/monacotools/Monaco/_packaging/vscode/Cargo/index/' - name: VSCODE_BUILD_WIN32 displayName: "🎯 Windows x64" type: boolean @@ -98,6 +102,8 @@ variables: value: ${{ ne(variables['Build.Repository.Uri'], 'https://github.com/microsoft/vscode.git') }} - name: NPM_REGISTRY value: ${{ parameters.NPM_REGISTRY }} + - name: CARGO_REGISTRY + value: ${{ parameters.CARGO_REGISTRY }} - name: VSCODE_QUALITY value: ${{ parameters.VSCODE_QUALITY }} - name: VSCODE_BUILD_STAGE_WINDOWS diff --git a/code/build/azure-pipelines/product-compile.yml b/code/build/azure-pipelines/product-compile.yml index 5b5be806934..f089eced728 100644 --- a/code/build/azure-pipelines/product-compile.yml +++ b/code/build/azure-pipelines/product-compile.yml @@ -103,22 +103,23 @@ steps: - script: yarn npm-run-all -lp core-ci-pr extensions-ci-pr hygiene eslint valid-layers-check vscode-dts-compile-check tsec-compile-check env: GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Compile & Hygiene + displayName: Compile & Hygiene (OSS) - ${{ else }}: - script: yarn npm-run-all -lp core-ci extensions-ci hygiene eslint valid-layers-check vscode-dts-compile-check tsec-compile-check env: GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Compile & Hygiene + displayName: Compile & Hygiene (non-OSS) - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - script: | set -e yarn --cwd test/smoke compile yarn --cwd test/integration/browser compile - displayName: Compile test suites + displayName: Compile test suites (non-OSS) condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - task: AzureCLI@2 + displayName: Fetch secrets inputs: azureSubscription: "vscode-builds-subscription" scriptType: pscore @@ -136,10 +137,10 @@ steps: AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ AZURE_CLIENT_SECRET="$(AZURE_CLIENT_SECRET)" \ node build/azure-pipelines/upload-sourcemaps - displayName: Upload sourcemaps + displayName: Upload sourcemaps to Azure - script: ./build/azure-pipelines/common/extract-telemetry.sh - displayName: Extract Telemetry + displayName: Generate lists of telemetry events - script: tar -cz --ignore-failed-read --exclude='.build/node_modules_cache' --exclude='.build/node_modules_list.txt' --exclude='.build/distro' -f $(Build.ArtifactStagingDirectory)/compilation.tar.gz .build out-* test/integration/browser/out test/smoke/out test/automation/out displayName: Compress compilation artifact @@ -153,7 +154,7 @@ steps: - script: yarn download-builtin-extensions-cg env: GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Built-in extensions component details + displayName: Download component details of built-in extensions - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 displayName: "Component Detection" diff --git a/code/build/azure-pipelines/product-publish.yml b/code/build/azure-pipelines/product-publish.yml index aa4736874fc..32b0f730551 100644 --- a/code/build/azure-pipelines/product-publish.yml +++ b/code/build/azure-pipelines/product-publish.yml @@ -24,6 +24,7 @@ steps: displayName: Download all artifacts_processed text files - task: AzureCLI@2 + displayName: Fetch secrets inputs: azureSubscription: "vscode-builds-subscription" scriptType: pscore @@ -35,6 +36,7 @@ steps: Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_SECRET;issecret=true]$env:servicePrincipalKey" - task: AzureCLI@2 + displayName: Fetch Mooncake secrets inputs: azureSubscription: "vscode-builds-mooncake-subscription" scriptType: pscore @@ -76,7 +78,7 @@ steps: - publish: $(Pipeline.Workspace)/artifacts_processed_$(System.StageAttempt)/artifacts_processed_$(System.StageAttempt).txt artifact: artifacts_processed_$(System.StageAttempt) - displayName: Publish what artifacts were published for this stage attempt + displayName: Publish the artifacts processed for this stage attempt condition: always() - pwsh: | diff --git a/code/build/azure-pipelines/product-release.yml b/code/build/azure-pipelines/product-release.yml index 93f5fe9568a..7ab077f3699 100644 --- a/code/build/azure-pipelines/product-release.yml +++ b/code/build/azure-pipelines/product-release.yml @@ -9,6 +9,7 @@ steps: versionFilePath: .nvmrc - task: AzureCLI@2 + displayName: Fetch secrets inputs: azureSubscription: "vscode-builds-subscription" scriptType: pscore @@ -26,3 +27,4 @@ steps: AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ AZURE_CLIENT_SECRET="$(AZURE_CLIENT_SECRET)" \ node build/azure-pipelines/common/releaseBuild.js ${{ parameters.VSCODE_RELEASE }} + displayName: Release build diff --git a/code/build/azure-pipelines/publish-types/publish-types.yml b/code/build/azure-pipelines/publish-types/publish-types.yml index 6e2e6bedac1..fadb7c8381f 100644 --- a/code/build/azure-pipelines/publish-types/publish-types.yml +++ b/code/build/azure-pipelines/publish-types/publish-types.yml @@ -80,4 +80,4 @@ steps: --data '{"channel":"'"$CHANNEL"'", "link_names": true, "text":"'"$MESSAGE2"'"}' \ https://slack.com/api/chat.postMessage - displayName: Send message on Slack + displayName: Send message linking to changes on Slack diff --git a/code/build/azure-pipelines/sdl-scan.yml b/code/build/azure-pipelines/sdl-scan.yml index cdc36fa7c56..63beec73d02 100644 --- a/code/build/azure-pipelines/sdl-scan.yml +++ b/code/build/azure-pipelines/sdl-scan.yml @@ -50,6 +50,7 @@ stages: inputs: scanFolder: "$(Build.SourcesDirectory)" outputFormat: "pre" + - task: NodeTool@0 inputs: versionSource: fromFile @@ -143,11 +144,18 @@ stages: GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Download Symbols + - task: PSScriptAnalyzer@1 + inputs: + Path: '$(Build.SourcesDirectory)' + Settings: required + Recurse: true + - task: BinSkim@4 inputs: InputType: "Basic" Function: "analyze" TargetPattern: "guardianGlob" + AnalyzeIgnorePdbLoadError: true AnalyzeTargetGlob: '$(agent.builddirectory)\scanbin\**.dll;$(agent.builddirectory)\scanbin\**.exe;$(agent.builddirectory)\scanbin\**.node' AnalyzeLocalSymbolDirectories: '$(agent.builddirectory)\scanbin\VSCode-win32-$(VSCODE_ARCH)\pdb' @@ -162,6 +170,13 @@ stages: SignatureFreshness: 'OneDay' TreatStaleSignatureAs: 'Error' + - task: PublishSecurityAnalysisLogs@3 + inputs: + ArtifactName: CodeAnalysisLogs + ArtifactType: Container + PublishProcessedResults: false + AllTools: true + - task: TSAUpload@2 inputs: GdnPublishTsaOnboard: true diff --git a/code/build/azure-pipelines/win32/product-build-win32.yml b/code/build/azure-pipelines/win32/product-build-win32.yml index 7391638cf68..ebfbc701146 100644 --- a/code/build/azure-pipelines/win32/product-build-win32.yml +++ b/code/build/azure-pipelines/win32/product-build-win32.yml @@ -152,7 +152,7 @@ steps: - powershell: yarn gulp "transpile-client-swc" "transpile-extensions" env: GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Transpile + displayName: Transpile client and extensions - ${{ else }}: - ${{ if and(ne(parameters.VSCODE_CIBUILD, true), eq(parameters.VSCODE_QUALITY, 'insider')) }}: @@ -241,7 +241,7 @@ steps: displayName: Find ESRP CLI - powershell: node build\azure-pipelines\common\sign $env:EsrpCliDllPath windows $(ESRP-PKI) $(esrp-aad-username) $(esrp-aad-password) $(CodeSigningFolderPath) '*.dll,*.exe,*.node' - displayName: Codesign + displayName: Codesign executables and shared libraries - ${{ if eq(parameters.VSCODE_QUALITY, 'insider') }}: - powershell: node build\azure-pipelines\common\sign $env:EsrpCliDllPath windows-appx $(ESRP-PKI) $(esrp-aad-username) $(esrp-aad-password) $(CodeSigningFolderPath) '*.appx' diff --git a/code/build/gulpfile.scan.js b/code/build/gulpfile.scan.js index 62691fcc8cf..9e5b511b48f 100644 --- a/code/build/gulpfile.scan.js +++ b/code/build/gulpfile.scan.js @@ -13,6 +13,7 @@ const electron = require('@vscode/gulp-electron'); const { config } = require('./lib/electron'); const filter = require('gulp-filter'); const deps = require('./lib/dependencies'); +const { existsSync, readdirSync } = require('fs'); const root = path.dirname(__dirname); @@ -46,8 +47,9 @@ BUILD_TARGETS.forEach(buildTarget => { if (platform === 'win32') { tasks.push( () => electron.dest(destinationPdb, { ...config, platform, arch: arch === 'armhf' ? 'arm' : arch, pdbs: true }), - util.rimraf(path.join(destinationExe, 'swiftshader')), - util.rimraf(path.join(destinationExe, 'd3dcompiler_47.dll'))); + util.rimraf(path.join(destinationExe, 'd3dcompiler_47.dll')), + () => confirmPdbsExist(destinationExe, destinationPdb) + ); } if (platform === 'linux') { @@ -106,3 +108,15 @@ function nodeModules(destinationExe, destinationPdb, platform) { return exe; } + +function confirmPdbsExist(destinationExe, destinationPdb) { + readdirSync(destinationExe).forEach(file => { + if (file.endsWith('.dll') || file.endsWith('.exe')) { + const pdb = `${file}.pdb`; + if (!existsSync(path.join(destinationPdb, pdb))) { + throw new Error(`Missing pdb file for ${file}. Tried searching for ${pdb} in ${destinationPdb}.`); + } + } + }); + return Promise.resolve(); +} diff --git a/code/build/lib/compilation.js b/code/build/lib/compilation.js index 64e27dcf45c..5fecfc82ca3 100644 --- a/code/build/lib/compilation.js +++ b/code/build/lib/compilation.js @@ -237,7 +237,7 @@ function generateApiProposalNames() { catch { eol = os.EOL; } - const pattern = /vscode\.proposed\.([a-zA-Z]+)\.d\.ts$/; + const pattern = /vscode\.proposed\.([a-zA-Z\d]+)\.d\.ts$/; const proposalNames = new Set(); const input = es.through(); const output = input @@ -287,4 +287,4 @@ exports.watchApiProposalNamesTask = task.define('watch-api-proposal-names', () = .pipe(util.debounce(task)) .pipe(gulp.dest('src')); }); -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29tcGlsYXRpb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJjb21waWxhdGlvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7OztBQUVoRyxtQ0FBbUM7QUFDbkMseUJBQXlCO0FBQ3pCLDZCQUE2QjtBQUM3Qiw2QkFBNkI7QUFDN0IsMENBQTBDO0FBQzFDLDZCQUE2QjtBQUM3Qix5Q0FBNEM7QUFDNUMsK0JBQStCO0FBQy9CLHNDQUFzQztBQUN0QywwQ0FBMEM7QUFDMUMseUJBQXlCO0FBQ3pCLGlDQUFrQztBQUNsQyw4QkFBOEI7QUFDOUIsK0JBQStCO0FBQy9CLDBDQUF5QztBQUV6QyxNQUFNLEtBQUssR0FBRyxPQUFPLENBQUMsU0FBUyxDQUFDLENBQUM7QUFHakMsdUVBQXVFO0FBRXZFLE1BQU0sUUFBUSxHQUFHLElBQUEseUJBQWMsR0FBRSxDQUFDO0FBRWxDLFNBQVMsNEJBQTRCLENBQUMsR0FBVztJQUNoRCxNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLFNBQVMsRUFBRSxTQUFTLEdBQUcsRUFBRSxDQUFDLENBQUM7SUFDckQsTUFBTSxPQUFPLEdBQXVCLEVBQUUsQ0FBQztJQUN2QyxPQUFPLENBQUMsT0FBTyxHQUFHLEtBQUssQ0FBQztJQUN4QixPQUFPLENBQUMsU0FBUyxHQUFHLElBQUksQ0FBQztJQUN6QixJQUFJLE9BQU8sQ0FBQyxHQUFHLENBQUMscUJBQXFCLENBQUMsRUFBRSxDQUFDLENBQUMsc0NBQXNDO1FBQy9FLE9BQU8sQ0FBQyxTQUFTLEdBQUcsS0FBSyxDQUFDO0lBQzNCLENBQUM7SUFDRCxPQUFPLENBQUMsT0FBTyxHQUFHLE9BQU8sQ0FBQztJQUMxQixPQUFPLENBQUMsT0FBTyxHQUFHLE9BQU8sQ0FBQztJQUMxQixPQUFPLENBQUMsVUFBVSxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUMsT0FBTyxDQUFDLENBQUM7SUFDN0MsT0FBTyxDQUFDLE9BQU8sR0FBRyxNQUFN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 \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29tcGlsYXRpb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJjb21waWxhdGlvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7OztBQUVoRyxtQ0FBbUM7QUFDbkMseUJBQXlCO0FBQ3pCLDZCQUE2QjtBQUM3Qiw2QkFBNkI7QUFDN0IsMENBQTBDO0FBQzFDLDZCQUE2QjtBQUM3Qix5Q0FBNEM7QUFDNUMsK0JBQStCO0FBQy9CLHNDQUFzQztBQUN0QywwQ0FBMEM7QUFDMUMseUJBQXlCO0FBQ3pCLGlDQUFrQztBQUNsQyw4QkFBOEI7QUFDOUIsK0JBQStCO0FBQy9CLDBDQUF5QztBQUV6QyxNQUFNLEtBQUssR0FBRyxPQUFPLENBQUMsU0FBUyxDQUFDLENBQUM7QUFHakMsdUVBQXVFO0FBRXZFLE1BQU0sUUFBUSxHQUFHLElBQUEseUJBQWMsR0FBRSxDQUFDO0FBRWxDLFNBQVMsNEJBQTRCLENBQUMsR0FBVztJQUNoRCxNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLFNBQVMsRUFBRSxTQUFTLEdBQUcsRUFBRSxDQUFDLENBQUM7SUFDckQsTUFBTSxPQUFPLEdBQXVCLEVBQUUsQ0FBQztJQUN2QyxPQUFPLENBQUMsT0FBTyxHQUFHLEtBQUssQ0FBQztJQUN4QixPQUFPLENBQUMsU0FBUyxHQUFHLElBQUksQ0FBQztJQUN6QixJQUFJLE9BQU8sQ0FBQyxHQUFHLENBQUMscUJBQXFCLENBQUMsRUFBRSxDQUFDLENBQUMsc0NBQXNDO1FBQy9FLE9BQU8sQ0FBQyxTQUFTLEdBQUcsS0FBSyxDQUFDO0lBQzNCLENBQUM7SUFDRCxPQUFPLENBQUMsT0FBTyxHQUFHLE9BQU8sQ0FBQztJQUMxQixPQUFPLENBQUMsT0FBTyxHQUFHLE9BQU8sQ0FBQztJQUMxQixPQUFPLENBQUMsVUFBVSxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUMsT0FBTyxDQUFDLENBQUM7SUFDN0MsT0FBTyxDQUFDLE9BQU8sR0FBRyxNQUFN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 \ No newline at end of file diff --git a/code/build/lib/compilation.ts b/code/build/lib/compilation.ts index cf2ab921f1c..ebc9dedf2e5 100644 --- a/code/build/lib/compilation.ts +++ b/code/build/lib/compilation.ts @@ -277,7 +277,7 @@ function generateApiProposalNames() { eol = os.EOL; } - const pattern = /vscode\.proposed\.([a-zA-Z]+)\.d\.ts$/; + const pattern = /vscode\.proposed\.([a-zA-Z\d]+)\.d\.ts$/; const proposalNames = new Set(); const input = es.through(); diff --git a/code/build/lib/electron.js b/code/build/lib/electron.js index dae4cb862a4..f2546dceb10 100644 --- a/code/build/lib/electron.js +++ b/code/build/lib/electron.js @@ -9,7 +9,6 @@ const fs = require("fs"); const path = require("path"); const vfs = require("vinyl-fs"); const filter = require("gulp-filter"); -const _ = require("underscore"); const util = require("./util"); const getVersion_1 = require("./getVersion"); function isDocumentSuffix(str) { @@ -18,7 +17,14 @@ function isDocumentSuffix(str) { const root = path.dirname(path.dirname(__dirname)); const product = JSON.parse(fs.readFileSync(path.join(root, 'product.json'), 'utf8')); const commit = (0, getVersion_1.getVersion)(root); -const darwinCreditsTemplate = product.darwinCredits && _.template(fs.readFileSync(path.join(root, product.darwinCredits), 'utf8')); +function createTemplate(input) { + return (params) => { + return input.replace(/<%=\s*([^\s]+)\s*%>/g, (match, key) => { + return params[key] || match; + }); + }; +} +const darwinCreditsTemplate = product.darwinCredits && createTemplate(fs.readFileSync(path.join(root, product.darwinCredits), 'utf8')); /** * Generate a `DarwinDocumentType` given a list of file extensions, an icon name, and an optional suffix or file type name. * @param extensions A list of file extensions, such as `['bat', 'cmd']` @@ -183,12 +189,13 @@ function getElectron(arch) { return () => { const electron = require('@vscode/gulp-electron'); const json = require('gulp-json-editor'); - const electronOpts = _.extend({}, exports.config, { + const electronOpts = { + ...exports.config, platform: process.platform, arch: arch === 'armhf' ? 'arm' : arch, ffmpegChromium: false, keepDefaultApp: true - }); + }; return vfs.src('package.json') .pipe(json({ name: product.nameShort })) .pipe(electron(electronOpts)) @@ -212,4 +219,4 @@ if (require.main === module) { process.exit(1); }); } -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZWxlY3Ryb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJlbGVjdHJvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7OztBQUVoRyx5QkFBeUI7QUFDekIsNkJBQTZCO0FBQzdCLGdDQUFnQztBQUNoQyxzQ0FBc0M7QUFDdEMsZ0NBQWdDO0FBQ2hDLCtCQUErQjtBQUMvQiw2Q0FBMEM7QUFZMUMsU0FBUyxnQkFBZ0IsQ0FBQyxHQUFZO0lBQ3JDLE9BQU8sR0FBRyxLQUFLLFVBQVUsSUFBSSxHQUFHLEtBQUssUUFBUSxJQUFJLEdBQUcsS0FBSyxNQUFNLElBQUksR0FBRyxLQUFLLGFBQWEsQ0FBQztBQUMxRixDQUFDO0FBRUQsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUM7QUFDbkQsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLGNBQWMsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUM7QUFDckYsTUFBTSxNQUFNLEdBQUcsSUFBQSx1QkFBVSxFQUFDLElBQUksQ0FBQyxDQUFDO0FBRWhDLE1BQU0scUJBQXFCLEdBQUcsT0FBTyxDQUFDLGFBQWEsSUFBSSxDQUFDLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsT0FBTyxDQUFDLGFBQWEsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUM7QUFFbkk7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQWtCRztBQUNILFNBQVMsd0JBQXdCLENBQUMsVUFBb0IsRUFBRSxJQUFZLEVBQUUsWUFBNEMsRUFBRSxJQUFlO0lBQ2xJLDJGQUEyRjtJQUMzRixJQUFJLGdCQUFnQixDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsWUFB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 \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZWxlY3Ryb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJlbGVjdHJvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7OztBQUVoRyx5QkFBeUI7QUFDekIsNkJBQTZCO0FBQzdCLGdDQUFnQztBQUNoQyxzQ0FBc0M7QUFDdEMsK0JBQStCO0FBQy9CLDZDQUEwQztBQVkxQyxTQUFTLGdCQUFnQixDQUFDLEdBQVk7SUFDckMsT0FBTyxHQUFHLEtBQUssVUFBVSxJQUFJLEdBQUcsS0FBSyxRQUFRLElBQUksR0FBRyxLQUFLLE1BQU0sSUFBSSxHQUFHLEtBQUssYUFBYSxDQUFDO0FBQzFGLENBQUM7QUFFRCxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQztBQUNuRCxNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsY0FBYyxDQUFDLEVBQUUsTUFBTSxDQUFDLENBQUMsQ0FBQztBQUNyRixNQUFNLE1BQU0sR0FBRyxJQUFBLHVCQUFVLEVBQUMsSUFBSSxDQUFDLENBQUM7QUFFaEMsU0FBUyxjQUFjLENBQUMsS0FBYTtJQUNwQyxPQUFPLENBQUMsTUFBOEIsRUFBRSxFQUFFO1FBQ3pDLE9BQU8sS0FBSyxDQUFDLE9BQU8sQ0FBQyxzQkFBc0IsRUFBRSxDQUFDLEtBQUssRUFBRSxHQUFHLEVBQUUsRUFBRTtZQUMzRCxPQUFPLE1BQU0sQ0FBQyxHQUFHLENBQUMsSUFBSSxLQUFLLENBQUM7UUFDN0IsQ0FBQyxDQUFDLENBQUM7SUFDSixDQUFDLENBQUM7QUFDSCxDQUFDO0FBRUQsTUFBTSxxQkFBcUIsR0FBRyxPQUFPLENBQUMsYUFBYSxJQUFJLGNBQWMsQ0FBQyxFQUFFLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsSUFB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 \ No newline at end of file diff --git a/code/build/lib/electron.ts b/code/build/lib/electron.ts index 592708cc385..33b407398da 100644 --- a/code/build/lib/electron.ts +++ b/code/build/lib/electron.ts @@ -7,7 +7,6 @@ import * as fs from 'fs'; import * as path from 'path'; import * as vfs from 'vinyl-fs'; import * as filter from 'gulp-filter'; -import * as _ from 'underscore'; import * as util from './util'; import { getVersion } from './getVersion'; @@ -29,7 +28,15 @@ const root = path.dirname(path.dirname(__dirname)); const product = JSON.parse(fs.readFileSync(path.join(root, 'product.json'), 'utf8')); const commit = getVersion(root); -const darwinCreditsTemplate = product.darwinCredits && _.template(fs.readFileSync(path.join(root, product.darwinCredits), 'utf8')); +function createTemplate(input: string): (params: Record) => string { + return (params: Record) => { + return input.replace(/<%=\s*([^\s]+)\s*%>/g, (match, key) => { + return params[key] || match; + }); + }; +} + +const darwinCreditsTemplate = product.darwinCredits && createTemplate(fs.readFileSync(path.join(root, product.darwinCredits), 'utf8')); /** * Generate a `DarwinDocumentType` given a list of file extensions, an icon name, and an optional suffix or file type name. @@ -201,12 +208,13 @@ function getElectron(arch: string): () => NodeJS.ReadWriteStream { const electron = require('@vscode/gulp-electron'); const json = require('gulp-json-editor') as typeof import('gulp-json-editor'); - const electronOpts = _.extend({}, config, { + const electronOpts = { + ...config, platform: process.platform, arch: arch === 'armhf' ? 'arm' : arch, ffmpegChromium: false, keepDefaultApp: true - }); + }; return vfs.src('package.json') .pipe(json({ name: product.nameShort })) diff --git a/code/build/lib/i18n.js b/code/build/lib/i18n.js index 34fcb38e84a..4016442a796 100644 --- a/code/build/lib/i18n.js +++ b/code/build/lib/i18n.js @@ -10,7 +10,6 @@ const fs = require("fs"); const event_stream_1 = require("event-stream"); const jsonMerge = require("gulp-merge-json"); const File = require("vinyl"); -const Is = require("is"); const xml2js = require("xml2js"); const gulp = require("gulp"); const fancyLog = require("fancy-log"); @@ -41,19 +40,19 @@ var LocalizeInfo; (function (LocalizeInfo) { function is(value) { const candidate = value; - return Is.defined(candidate) && Is.string(candidate.key) && (Is.undef(candidate.comment) || (Is.array(candidate.comment) && candidate.comment.every(element => Is.string(element)))); + return candidate && typeof candidate.key === 'string' && (candidate.comment === undefined || (Array.isArray(candidate.comment) && candidate.comment.every(element => typeof element === 'string'))); } LocalizeInfo.is = is; })(LocalizeInfo || (LocalizeInfo = {})); var BundledFormat; (function (BundledFormat) { function is(value) { - if (Is.undef(value)) { + if (value === undefined) { return false; } const candidate = value; const length = Object.keys(value).length; - return length === 3 && Is.defined(candidate.keys) && Is.defined(candidate.messages) && Is.defined(candidate.bundles); + return length === 3 && !!candidate.keys && !!candidate.messages && !!candidate.bundles; } BundledFormat.is = is; })(BundledFormat || (BundledFormat = {})); @@ -124,7 +123,7 @@ class XLF { const key = keys[i]; let realKey; let comment; - if (Is.string(key)) { + if (typeof key === 'string') { realKey = key; comment = undefined; } @@ -400,7 +399,7 @@ function processCoreBundleFormat(fileHeader, languages, json, emitter) { }); sortedLanguages.forEach(language => { const stats = statistics[language.id]; - if (Is.undef(stats)) { + if (!stats) { log(`\tNo translations found for language ${language.id}. Using default language instead.`); } }); @@ -878,4 +877,4 @@ function encodeEntities(value) { function decodeEntities(value) { return value.replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&'); } -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaTE4bi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImkxOG4udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOzs7QUFFaEcsNkJBQTZCO0FBQzdCLHlCQUF5QjtBQUV6QiwrQ0FBa0U7QUFDbEUsNkNBQTZDO0FBQzdDLDhCQUE4QjtBQUM5Qix5QkFBeUI7QUFDekIsaUNBQWlDO0FBQ2pDLDZCQUE2QjtBQUM3QixzQ0FBc0M7QUFDdEMsMENBQTBDO0FBQzFDLGdEQUFnRDtBQUNoRCwrQ0FBaUg7QUFFakgsU0FBUyxHQUFHLENBQUMsT0FBWSxFQUFFLEdBQUcsSUFBVztJQUN4QyxRQUFRLENBQUMsVUFBVSxDQUFDLEtBQUssQ0FBQyxRQUFRLENBQUMsRUFBRSxPQUFPLEVBQUUsR0FBRyxJQUFJLENBQUMsQ0FBQztBQUN4RCxDQUFDO0FBWVksUUFBQSxnQkFBZ0IsR0FBZTtJQUMzQyxFQUFFLEVBQUUsRUFBRSxPQUFPLEVBQUUsVUFBVSxFQUFFLEtBQUssRUFBRSxhQUFhLEVBQUUsU0FBUyxFQUFFO0lBQzVELEVBQUUsRUFBRSxFQUFFLE9BQU8sRUFBRSxVQUFVLEVBQUUsS0FBSyxFQUFFLGFBQWEsRUFBRSxTQUFTLEVBQUU7SUFDNUQsRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLFVBQVUsRUFBRSxLQUFLLEVBQUU7SUFDL0IsRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLFVBQVUsRUFBRSxLQUFLLEVBQUU7SUFDL0IsRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLFVBQVUsRUFBRSxLQUFLLEVBQUU7SUFDL0IsRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLFVBQVUsRUFBRSxLQUFLLEVBQUU7SUFDL0IsRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLFVBQVUsRUFBRSxLQUFLLEVBQUU7SUFDL0IsRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLFVBQVUsRUFBRSxLQUFLLEVBQUU7SUFDL0IsRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLFVBQVUsRUFBRSxLQUFLLEVBQUU7Q0FDL0IsQ0FBQztBQUVGLDREQUE0RDtBQUMvQyxRQUFBLGNBQWMsR0FBZTtJQUN6QyxFQUFFLEVBQUUsRUFBRSxPQUFPLEVBQUUsVUFBVSxFQUFFLEtBQUssRUFBRTtJQUNsQyxFQUFFLEVBQUUsRUFBRSxJQUFJLEVBQUUsVUFBVSxFQUFFLEtBQUssRUFBRTtJQUMvQixFQUFFLEVBQUUsRUFBRSxJQUFJLEVBQUUsVUFBVSxFQUFFLEtBQUssRUFBRTtDQUMvQixDQUFDO0FBa0JGLElBQU8sWUFBWSxDQUtsQjtBQUxELFdBQU8sWUFBWTtJQUNsQixTQUFnQixFQUFFLENBQUMsS0FBVTtRQUM1QixNQUFNLFNBQVMsR0FBRyxLQUFxQixDQUFDO1FBQ3hDLE9BQU8sRUFBRSxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsSUFBSSxFQUFFLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLEtBQUssQ0FBQyxTQUFTLENBQUMsT0FBTyxDQUFDLElBQUksU0FBUyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ3RMLENBQUM7SUFIZSxlQUFFLEtBR2pCLENBQUE7QUFDRixDQUFDLEVBTE0sWUFBWSxLQUFaLFlBQVksUUFLbEI7QUFRRCxJQUFPLGFBQWEsQ0FXbkI7QUFYRCxXQUFPLGFBQWE7SUFDbkIsU0FBZ0IsRUFBRSxDQUFDLEtBQVU7UUFDNUIsSUFBSSxFQUFFLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUM7WUFDckIsT0FBTyxLQUFLLENBQUM7UUFDZCxDQUFDO1FBRUQsTUFBTSxTQUFTLEdBQUcsS0FBc0IsQ0FBQztRQUN6QyxNQUFNLE1BQU0sR0FBRyxNQUFNLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLE1BQU0sQ0FBQztRQUV6QyxPQUFPLE1BQU0sS0FBSyxDQUFDLElBQUksRUFBRSxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsUUFBUSxDQUFDLElBQUksRUFBRSxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsT0FBTyxDQUFDLENBQUM7SUFDdEgsQ0FBQztJQVRlLGdCQUFFLEtBU2pCLENBQUE7QUFDRixDQUFDLEVBWE0sYUFBYSxLQUFiLGFBQWEsUUFXbkI7QUFrQkQsTUFBYSxJQUFJO0lBQ1IsTUFBTSxHQUFhLEVBQUUsQ0FBQztJQUU5QixZQUFZLFNBQWlCLENBQUM7UUFDN0IsSUFBSSxNQUFNLEdBQUcsQ0FBQyxFQUFFLENBQUM7WUFDaEIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsSUFBSSxLQUFLLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO1FBQ25ELENBQUM7SUFDRixDQUFDO0lBRU0sTUFBTSxDQUFDLEtBQWE7UUFDMUIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDeEIsT0FBTyxJQUFJLENBQUM7SUFDYixDQUFDO0lBRU0sUUFBUTtRQUNkLE9BQU8sSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUM7SUFDN0IsQ0FBQztDQUNEO0FBakJELG9CQWlCQztBQUVELE1BQU0sU0FBUztJQUNOLE1BQU0sQ0FBVztJQUV6QixZQUFZLFFBQWdCO1FBQzNCLElBQUksQ0FBQyxNQUFNLEdBQUcsUUFBUSxDQUFDLEtBQUssQ0FBQyxZQUFZLENBQUMsQ0FBQztJQUM1QyxDQUFDO0lBRUQsSUFBVyxLQUFLO1FBQ2YsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDO0lBQ3BCLENBQUM7Q0FDRDtBQUVELE1BQWEsR0FBRztJQUtJO0lBSlgsTUFBTSxDQUFXO0lBQ2pCLEtBQUssQ0FBeUI7SUFDL0IsZ0JBQWdCLENBQVM7SUFFaEMsWUFBbUIsT0FBZTtRQUFmLFlBQU8sR0FBUCxPQUFPLENBQVE7UUFDakMsSUFBSSxDQUFDLE1BQU0sR0FBRyxFQUFFLENBQUM7UUFDakIsSUFBSSxDQUFDLEtBQUssR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ2pDLElBQUksQ0FBQyxnQkFBZ0IsR0FBRyxDQUFDLENBQUM7SUFDM0IsQ0FBQztJQUVNLFFBQVE7UUFDZCxJQUFJLENBQUMsWUFBWSxFQUFFLENBQUM7UUFFcEIsTUFBTSxLQUFLLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDN0MsS0FBSyxNQUFNLElBQUksSUFBSSxLQUFLLEVBQUUsQ0FBQztZQUMxQixJQUFJLENBQUMsYUFBYSxDQUFDLG1CQUFtQixJQUFJLG9EQUFvRCxFQUFFLENBQUMsQ0FBQyxDQUFDO1lBQ25HLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBTyxFQUFFLENBQU8sRUFBRSxFQUFFO2dCQUN4RCxPQUFPLENBQUMsQ0FBQyxFQUFFLEdBQUcsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLEdBQUcsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDL0MsQ0FBQyxDQUFDLENBQUM7WUFDSCxLQUFLLE1BQU0sSUFBSSxJQUFJLEtBQUssRUFBRSxDQUFDO2dCQUMxQixJQUFJLENBQUMsYUFBYSxDQUFDLElBQUksRUFBRSxJQUFJLENBQUMsQ0FBQztZQUNoQyxDQUFDO1lBQ0QsSUFBSSxDQUFDLGFBQWEsQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO1FBQ3RDLENBQUM7UUFDRCxJQUFJLENBQUMsWUFBWSxFQUFFLENBQUM7UUFDcEIsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUNqQyxDQUFDO0lBRU0sT0FBTyxDQUFDLFFBQWdCLEVBQUUsSUFBK0IsRUFBRSxRQUFrQjtRQUNuRixJQUFJLElBQUksQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFLENBQUM7WUFDdkIsT0FBTyxDQUFDLEdBQUcsQ0FBQyxhQUFhLEdBQUcsUUFBUSxDQUFDLENBQUM7WUFDdEMsT0FBTztRQUNSLENBQUM7UUFDRCxJQUFJLElBQUksQ0FBQyxNQUFNLEtBQUssUUFBUSxDQUFDLE1BQU0sRUFBRSxDQUFDO1lBQ3JDLE1BQU0sSUFBSSxLQUFLLENBQUMsbUJBQW1CLElBQUksQ0FBQyxNQUFNLGtCQUFrQixRQUFRLENBQUMsTUFBTSxJQUFJLENBQUMsQ0FBQztRQUN0RixDQUFDO1FBQ0QsSUFBSSxDQUFDLGdCQUFnQixJQUFJLElBQUksQ0FBQyxNQUFNLENBQUM7UUFDckMsSUFBSSxDQUFDLEtBQUssQ0FBQyxRQUFRLENBQUMsR0FBRyxFQUFFLENBQUM7UUFDMUIsTUFBTSxZQUFZLEdBQUcsSUFBSSxHQUFHLEVBQVUsQ0FBQztRQUN2QyxLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDLEVBQUUsRUFBRSxDQUFDO1lBQ3RDLE1BQU0sR0FBRyxHQUFHLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUNwQixJQUFJLE9BQTJCLENBQUM7WUFDaEMsSUFBSSxPQUEyQixDQUFDO1lBQ2hDLElBQUksRUFBRSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDO2dCQUNwQixPQUFPLEdBQUcsR0FBRyxDQUFDO2dCQUNkLE9BQU8sR0FBRyxTQUFTLENBQUM7WUFDckIsQ0FBQztpQkFBTSxJQUFJLFlBQVksQ0FBQyxFQUFFLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQztnQkFDakMsT0FBTyxHQUFHLEdBQUcsQ0FBQyxHQUFHLENBQUM7Z0JBQ2xCLElBQUksR0FBRyxDQUFDLE9BQU8sSUFBSSxHQUFHLENBQUMsT0FBTyxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQztvQkFDM0MsT0FBTyxHQUFHLEdBQUcsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUMsY0FBYyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDO2dCQUM1RSxDQUFDO1lBQ0YsQ0FBQztZQUNELElBQUksQ0FBQyxPQUFPLElBQUksWUFBWSxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDO2dCQUMzQyxTQUFTO1lBQ1YsQ0FBQztZQUNELFlBQVksQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLENBQUM7WUFDMUIsTUFBTSxPQUFPLEdBQVcsY0FBYyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQ3BELElBQUksQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDLENBQUMsSUFBSSxDQUFDLEVBQUUsRUFBRSxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsT0FBTyxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsQ0FBQyxDQUFDO1FBQ2hGLENBQUM7SUFDRixDQUFDO0lBRU8sYUFBYSxDQUFDLElBQVksRUFBRSxJQUFVO1FBQzdDLElBQUksQ0FBQyxJQUFJLENBQUMsRUFBRSxJQUFJLElBQUksQ0FBQyxPQUFPLEtBQUssU0FBUyxJQUFJLElBQUksQ0FBQyxPQUFPLEtBQUssSUFBSSxFQUFFLENBQUM7WUFDckUsTUFBTSxJQUFJLEtBQUssQ0FBQyxrQ0FBa0MsSUFBSSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsV0FBVyxJQUFJLEVBQUUsQ0FBQyxDQUFDO1FBQzFGLENBQUM7UUFDRCxJQUFJLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRSxDQUFDO1lBQy9CLEdBQUcsQ0FBQyxnQkFBZ0IsSUFBSSxDQUFDLEVBQUUsWUFBWSxJQUFJLHdCQUF3QixDQUFDLENBQUM7UUFDdEUsQ0FBQztRQUVELElBQUksQ0FBQyxhQUFhLENBQUMsbUJBQW1CLElBQUksQ0FBQyxFQUFFLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQztRQUN0RCxJQUFJLENBQUMsYUFBYSxDQUFDLHlCQUF5QixJQUFJLENBQUMsT0FBTyxXQUFXLEVBQUUsQ0FBQyxDQUFDLENBQUM7UUFFeEUsSUFBSSxJQUFJLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDbEIsSUFBSSxDQUFDLGFBQWEsQ0FBQyxTQUFTLElBQUksQ0FBQyxPQUFPLFNBQVMsRUFBRSxDQUFDLENBQUMsQ0FBQztRQUN2RCxDQUFDO1FBRUQsSUFBSSxDQUFDLGFBQWEsQ0FBQyxlQUFlLEVBQUUsQ0FBQyxDQUFDLENBQUM7SUFDeEMsQ0FBQztJQUVPLFlBQVk7UUFDbkIsSUFBSSxDQUFDLGFBQWEsQ0FBQyx3Q0FBd0MsRUFBRSxDQUFDLENBQUMsQ0FBQztRQUNoRSxJQUFJLENBQUMsYUFBYSxDQUFDLHFFQUFxRSxFQUFFLENBQUMsQ0FBQyxDQUFDO0lBQzlGLENBQUM7SUFFTyxZQUFZO1FBQ25CLElBQUksQ0FBQyxhQUFhLENBQUMsVUFBVSxFQUFFLENBQUMsQ0FBQyxDQUFDO0lBQ25DLENBQUM7SUFFTyxhQUFhLENBQUMsT0FBZSxFQUFFLE1BQWU7UUFDckQsTUFBTSxJQUFJLEdBQUcsSUFBSSxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDOUIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUNyQixJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQztJQUNuQyxDQUFDO0lBRUQsTUFBTSxDQUFDLEtBQUssR0FBRyxVQUFVLFNBQWlCO1FBQ3pDLE9BQU8sSUFBSSxPQUFPLENBQUMsQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLEVBQUU7WUFDdEMsTUFBTSxNQUFNLEdBQUcsSUFBSSxNQUFNLENBQUMsTUFBTSxFQUFFLENBQUM7WUFFbkMsTUFBTSxLQUFLLEdBQTJFLEVBQUUsQ0FBQztZQUV6RixNQUFNLENBQUMsV0FBVyxDQUFDLFNBQVMsRUFBRSxVQUFVLEdBQVEsRUFBRSxNQUFXO2dCQUM1RCxJQUFJLEdBQUcsRUFBRSxDQUFDO29CQUNULE1BQU0sQ0FBQyxJQUFJLEtBQUssQ0FBQyxvREFBb0QsR0FBRyxFQUFFLENBQUMsQ0FBQyxDQUFDO2dCQUM5RSxDQUFDO2dCQUVELE1BQU0sU0FBUyxHQUFVLE1BQU0sQ0FBQyxPQUFPLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQztnQkFDakQsSUFBSSxDQUFDLFNBQVMsRUFBRSxDQUFDO29CQUNoQixNQUFNLENBQUMsSUFBSSxLQUFLLENBQUMsZ0dBQWdHLENBQUMsQ0FBQyxDQUFDO2dCQUNySCxDQUFDO2dCQUVELFNBQVMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxJQUFJLEVBQUUsRUFBRTtvQkFDMUIsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUM7b0JBQzdCLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQzt3QkFDWCxNQUFNLENBQUMsSUFBSSxLQUFLLENBQUMsaUlBQWlJLENBQUMsQ0FBQyxDQUFDO29CQUN0SixDQUFDO29CQUNELE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxDQUFDLENBQUMsaUJBQWlCLENBQUMsQ0FBQztvQkFDM0MsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO3dCQUNmLE1BQU0sQ0FBQyxJQUFJLEtBQUssQ0FBQyxpSEFBaUgsQ0FBQyxDQUFDLENBQUM7b0JBQ3RJLENBQUM7b0JBQ0QsTUFBTSxRQUFRLEdBQTJCLEVBQUUsQ0FBQztvQkFFNUMsTUFBTSxVQUFVLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxZQUFZLENBQUMsQ0FBQztvQkFDOUMsSUFBSSxVQUFVLEVBQUUsQ0FBQzt3QkFDaEIsVUFBVSxDQUFDLE9BQU8sQ0FBQyxDQUFDLElBQVMsRUFBRSxFQUFFOzRCQUNoQyxNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQzs0QkFDdEIsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztnQ0FDbEIsT0FBTyxDQUFDLDJCQUEyQjs0QkFDcEMsQ0FBQzs0QkFFRCxJQUFJLEdBQUcsR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDOzRCQUN6QixJQUFJLE9BQU8sR0FBRyxLQUFLLFFBQVEsRUFBRSxDQUFDO2dDQUM3Qix5RUFBeUU7Z0NBQ3pFLEdBQUcsR0FBRyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7NEJBQzFCLENBQUM7NEJBQ0QsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO2dDQUNWLE1BQU0sQ0FBQyxJQUFJLEtBQUssQ0FBQyxpQ0FBaUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxJQUFJLEVBQUUsU0FBUyxFQUFFLENBQUMsQ0FBQyxvQkFBb0IsSUFBSSwrQkFBK0IsQ0FBQyxDQUFDLENBQUM7Z0NBQzlJLE9BQU87NEJBQ1IsQ0FBQzs0QkFDRCxRQUFRLENBQUMsR0FBRyxDQUFDLEdBQUcsY0FBYyxDQUFDLEdBQUcsQ0FBQyxDQUFDO3dCQUNyQyxDQUFDLENBQUMsQ0FBQzt3QkFDSCxLQUFLLENBQUMsSUFBSSxDQUFDLEVBQUUsUUFBUSxFQUFFLElBQUksRUFBRSxRQUFRLEVBQUUsUUFBUSxDQUFDLFdBQVcsRUFBRSxFQUFFLENBQUMsQ0FBQztvQkFDbEUsQ0FBQztnQkFDRixDQUFDLENBQUMsQ0FBQztnQkFFSCxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUM7WUFDaEIsQ0FBQyxDQUFDLENBQUM7UUFDSixDQUFDLENBQUMsQ0FBQztJQUNKLENBQUMsQ0FBQzs7QUFwSkgsa0JBcUpDO0FBRUQsU0FBUyxhQUFhLENBQUMsU0FBcUI7SUFDM0MsT0FBTyxTQUFTLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBVyxFQUFFLENBQVcsRUFBVSxFQUFFO1FBQzFELE9BQU8sQ0FBQyxDQUFDLEVBQUUsR0FBRyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxHQUFHLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDakQsQ0FBQyxDQUFDLENBQUM7QUFDSixDQUFDO0FBRUQsU0FBUyxhQUFhLENBQUMsT0FBZTtJQUNyQywrQkFBK0I7SUFDL0IsRUFBRTtJQUNGLDZDQUE2QztJQUM3Qyw4Q0FBOEM7SUFDOUMsMkNBQTJDO0lBQzNDLDRDQUE0QztJQUM1Qyx1Q0FBdUM7SUFDdkMsTUFBTSxNQUFNLEdBQUcseUlBQXlJLENBQUM7SUFDekosTUFBTSxNQUFNLEdBQUcsT0FBTyxDQUFDLE9BQU8sQ0FBQyxNQUFNLEVBQUUsQ0FBQyxLQUFLLEVBQUUsR0FBVyxFQUFFLEdBQVcsRUFBRSxFQUFVLEVBQUUsRUFBVSxFQUFFLEVBQVUsRUFBRSxFQUFFO1FBQzlHLHlDQUF5QztRQUN6QyxJQUFJLEVBQUUsRUFBRSxDQUFDO1lBQ1Isd0NBQXdDO1lBQ3hDLE9BQU8sRUFBRSxDQUFDO1FBQ1gsQ0FBQzthQUFNLElBQUksRUFBRSxFQUFFLENBQUM7WUFDZix5RUFBeUU7WUFDekUsb0NBQW9DO1lBQ3BDLE1BQU0sTUFBTSxHQUFHLEVBQUUsQ0FBQyxNQUFNLENBQUM7WUFDekIsSUFBSSxFQUFFLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxLQUFLLElBQUksRUFBRSxDQUFDO2dCQUM3QixPQUFPLEVBQUUsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLEtBQUssSUFBSSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQztZQUNoRCxDQUFDO2lCQUFNLENBQUM7Z0JBQ1AsT0FBTyxFQUFFLENBQUM7WUFDWCxDQUFDO1FBQ0YsQ0FBQzthQUFNLElBQUksRUFBRSxFQUFFLENBQUM7WUFDZiw0QkFBNEI7WUFDNUIsT0FBTyxLQUFLLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQzNCLENBQUM7YUFBTSxDQUFDO1lBQ1Asb0JBQW9CO1lBQ3BCLE9BQU8sS0FBSyxDQUFDO1FBQ2QsQ0FBQztJQUNGLENBQUMsQ0FBQyxDQUFDO0lBQ0gsT0FBTyxNQUFNLENBQUM7QUFDZixDQUFDO0FBRUQsU0FBUyxnQkFBZ0IsQ0FBQyxLQUFhO0lBQ3RDLE1BQU0sTUFBTSxHQUFhLEVBQUUsQ0FBQztJQUM1QixLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsS0FBSyxDQUFDLE1BQU0sRUFBRSxDQUFDLEVBQUUsRUFBRSxDQUFDO1FBQ3ZDLE1BQU0sRUFBRSxHQUFHLEtBQUssQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDM0IsUUFBUSxFQUFFLEVBQUUsQ0FBQztZQUNaLEtBQUssSUFBSTtnQkFDUixNQUFNLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDO2dCQUNwQixNQUFNO1lBQ1AsS0FBSyxHQUFHO2dCQUNQLE1BQU0sQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUM7Z0JBQ25CLE1BQU07WUFDUCxLQUFLLElBQUk7Z0JBQ1IsTUFBTSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQztnQkFDcEIsTUFBTTtZQUNQLEtBQUssSUFBSTtnQkFDUixNQUFNLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDO2dCQUNuQixNQUFNO1lBQ1AsS0FBSyxJQUFJO2dCQUNSLE1BQU0sQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUM7Z0JBQ25CLE1BQU07WUFDUCxLQUFLLElBQUk7Z0JBQ1IsTUFBTSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQztnQkFDbkIsTUFBTTtZQUNQLEtBQUssSUFBSTtnQkFDUixNQUFNLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDO2dCQUNuQixNQUFNO1lBQ1AsS0FBSyxJQUFJO2dCQUNSLE1BQU0sQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUM7Z0JBQ25CLE1BQU07WUFDUDtnQkFDQyxNQUFNLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDO1FBQ2xCLENBQUM7SUFDRixDQUFDO0lBQ0QsT0FBTyxNQUFNLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDO0FBQ3hCLENBQUM7QUFFRCxTQUFTLHVCQUF1QixDQUFDLFVBQWtCLEVBQUUsU0FBcUIsRUFBRSxJQUFtQixFQUFFLE9BQXNCO0lBQ3RILE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUM7SUFDOUIsTUFBTSxjQUFjLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQztJQUNyQyxNQUFNLGFBQWEsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDO0lBRW5DLE1BQU0sVUFBVSxHQUEyQixNQUFNLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDO0lBRS9ELE1BQU0sZUFBZSxHQUEyQyxNQUFNLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ3BGLE1BQU0sT0FBTyxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLENBQUM7SUFDekMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQUFDLE1BQU0sRUFBRSxFQUFFO1FBQzFCLE1BQU0sSUFBSSxHQUFHLFdBQVcsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNqQyxNQUFNLFFBQVEsR0FBRyxjQUFjLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDeEMsSUFBSSxDQUFDLFFBQVEsSUFBSSxJQUFJLENBQUMsTUFBTSxLQUFLLFFBQVEsQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUNsRCxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxzQkFBc0IsTUFBTSxzREFBc0QsQ0FBQyxDQUFDO1lBQzFHLE9BQU87UUFDUixDQUFDO1FBQ0QsTUFBTSxVQUFVLEdBQTJCLE1BQU0sQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDL0QsZUFBZSxDQUFDLE1BQU0sQ0FBQyxHQUFHLFVBQVUsQ0FBQztRQUNyQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUMsR0FBRyxFQUFFLENBQUMsRUFBRSxFQUFFO1lBQ25CLElBQUksT0FBTyxHQUFHLEtBQUssUUFBUSxFQUFFLENBQUM7Z0JBQzdCLFVBQVUsQ0FBQyxHQUFHLENBQUMsR0FBRyxRQUFRLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDL0IsQ0FBQztpQkFBTSxDQUFDO2dCQUNQLFVBQVUsQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLEdBQUcsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQ25DLENBQUM7UUFDRixDQUFDLENBQUMsQ0FBQztJQUNKLENBQUMsQ0FBQyxDQUFDO0lBRUgsTUFBTSxpQkFBaUIsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLFNBQVMsRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxZQUFZLEVBQUUsTUFBTSxDQUFDLENBQUM7SUFDdkYsSUFBSSxDQUFDLEVBQUUsQ0FBQyxVQUFVLENBQUMsaUJBQWlCLENBQUMsRUFBRSxDQUFDO1FBQ3ZDLEdBQUcsQ0FBQyx3REFBd0QsaUJBQWlCLEVBQUUsQ0FBQyxDQUFDO1FBQ2pGLEdBQUcsQ0FBQywwR0FBMEcsQ0FBQyxDQUFDO0lBQ2pILENBQUM7SUFDRCxNQUFNLGVBQWUsR0FBRyxhQUFhLENBQUMsU0FBUyxDQUFDLENBQUM7SUFDakQsZUFBZSxDQUFDLE9BQU8sQ0FBQyxDQUFDLFFBQVEsRUFBRSxFQUFFO1FBQ3BDLElBQUksT0FBTyxDQUFDLEdBQUcsQ0FBQyxzQkFBc0IsQ0FBQyxFQUFFLENBQUM7WUFDekMsR0FBRyxDQUFDLCtCQUErQixRQUFRLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQztRQUNuRCxDQUFDO1FBRUQsVUFBVSxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDNUIsTUFBTSxnQkFBZ0IsR0FBNkIsTUFBTSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUN2RSxNQUFNLGtCQUFrQixHQUFHLFFBQVEsQ0FBQyxhQUFhLElBQUksUUFBUSxDQUFDLEVBQUUsQ0FBQztRQUNqRSxNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLGlCQUFpQixFQUFFLHdCQUF3QixrQkFBa0IsRUFBRSxFQUFFLGNBQWMsRUFBRSxnQkFBZ0IsQ0FBQyxDQUFDO1FBQzlILElBQUksV0FBbUMsQ0FBQztRQUN4QyxJQUFJLEVBQUUsQ0FBQyxVQUFVLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQztZQUM3QixNQUFNLE9BQU8sR0FBRyxhQUFhLENBQUMsRUFBRSxDQUFDLFlBQVksQ0FBQyxRQUFRLEVBQUUsTUFBTSxDQUFDLENBQUMsQ0FBQztZQUNqRSxXQUFXLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUNuQyxDQUFDO1FBQ0QsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQUFDLE1BQU0sRUFBRSxFQUFFO1lBQzFCLE1BQU0sS0FBSyxHQUFHLFdBQVcsQ0FBQyxNQUFNLENBQUMsQ0FBQztZQUNsQyxJQUFJLGFBQTJELENBQUM7WUFDaEUsSUFBSSxXQUFXLEVBQUUsQ0FBQztnQkFDakIsYUFBYSxHQUFHLFdBQVcsQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUM7WUFDOUMsQ0FBQztZQUNELElBQUksQ0FBQyxhQUFhLEVBQUUsQ0FBQztnQkFDcEIsSUFBSSxPQUFPLENBQUMsR0FBRyxDQUFDLHNCQUFzQixDQUFDLEVBQUUsQ0FBQztvQkFDekMsR0FBRyxDQUFDLDBDQUEwQyxNQUFNLDJCQUEyQixDQUFDLENBQUM7Z0JBQ2xGLENBQUM7Z0JBQ0QsYUFBYSxHQUFHLGVBQWUsQ0FBQyxNQUFNLENBQUMsQ0FBQztnQkFDeEMsVUFBVSxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUMsR0FBRyxVQUFVLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQyxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsYUFBYSxDQUFDLENBQUMsTUFBTSxDQUFDO1lBQ3ZGLENBQUM7WUFDRCxNQUFNLGlCQUFpQixHQUFhLEVBQUUsQ0FBQztZQUN2QyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUMsT0FBTyxFQUFFLEVBQUU7Z0JBQ3pCLElBQUksR0FBRyxHQUFrQixJQUFJLENBQUM7Z0JBQzlCLElBQUksT0FBTyxPQUFPLEtBQUssUUFBUSxFQUFFLENBQUM7b0JBQ2pDLEdBQUcsR0FBRyxPQUFPLENBQUM7Z0JBQ2YsQ0FBQztxQkFBTSxDQUFDO29CQUNQLEdBQUcsR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDO2dCQUNuQixDQUFDO2dCQUNELElBQUksT0FBTyxHQUFXLGFBQWMsQ0FBQyxHQUFHLENBQUMsQ0FBQztnQkFDMUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDO29CQUNkLElBQUksT0FBTyxDQUFDLEdBQUcsQ0FBQyxzQkFBc0IsQ0FBQyxFQUFFLENBQUM7d0JBQ3pDLEdBQUcsQ0FBQyxzQ0FBc0MsR0FBRyxjQUFjLE1BQU0sMEJBQTBCLENBQUMsQ0FBQztvQkFDOUYsQ0FBQztvQkFDRCxPQUFPLEdBQUcsZUFBZSxDQUFDLE1BQU0sQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDO29CQUN2QyxVQUFVLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQyxHQUFHLFVBQVUsQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDLEdBQUcsQ0FBQyxDQUFDO2dCQUN2RCxDQUFDO2dCQUNELGlCQUFpQixDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQztZQUNqQyxDQUFDLENBQUMsQ0FBQztZQUNILGdCQUFnQixDQUFDLE1BQU0sQ0FBQyxHQUFHLGlCQUFpQixDQUFDO1FBQzlDLENBQUMsQ0FBQyxDQUFDO1FBQ0gsTUFBTSxDQUFDLElBQUksQ0FBQyxhQUFhLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxNQUFNLEVBQUUsRUFBRTtZQUM3QyxNQUFNLE9BQU8sR0FBRyxhQUFhLENBQUMsTUFBTSxDQUFDLENBQUM7WUFDdEMsTUFBTSxRQUFRLEdBQWE7Z0JBQzFCLFVBQVU7Z0JBQ1YsV0FBVyxNQUFNLFFBQVEsUUFBUSxDQUFDLEVBQUUsTUFBTTthQUMxQyxDQUFDO1lBQ0YsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQUFDLE1BQU0sRUFBRSxLQUFLLEVBQUUsRUFBRTtnQkFDakMsUUFBUSxDQUFDLElBQUksQ0FBQyxNQUFNLE1BQU0sTUFBTSxDQUFDLENBQUM7Z0JBQ2xDLE1BQU0sUUFBUSxHQUFHLGdCQUFnQixDQUFDLE1BQU0sQ0FBQyxDQUFDO2dCQUMxQyxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUM7b0JBQ2YsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsbUNBQW1DLE1BQU0sR0FBRyxDQUFDLENBQUM7b0JBQ3BFLE9BQU87Z0JBQ1IsQ0FBQztnQkFDRCxRQUFRLENBQUMsT0FBTyxDQUFDLENBQUMsT0FBTyxFQUFFLEtBQUssRUFBRSxFQUFFO29CQUNuQyxRQUFRLENBQUMsSUFBSSxDQUFDLFFBQVEsZ0JBQWdCLENBQUMsT0FBTyxDQUFDLEdBQUcsS0FBSyxHQUFHLFFBQVEsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsR0FBRyxFQUFFLENBQUMsQ0FBQztnQkFDM0YsQ0FBQyxDQUFDLENBQUM7Z0JBQ0gsUUFBUSxDQUFDLElBQUksQ0FBQyxLQUFLLEdBQUcsT0FBTyxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUM7WUFDNUQsQ0FBQyxDQUFDLENBQUM7WUFDSCxRQUFRLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDO1lBQ3JCLE9BQU8sQ0FBQyxLQUFLLENBQUMsSUFBSSxJQUFJLENBQUMsRUFBRSxJQUFJLEVBQUUsTUFBTSxHQUFHLE9BQU8sR0FBRyxRQUFRLENBQUMsRUFBRSxHQUFHLEtBQUssRUFBRSxRQUFRLEVBQUUsTUFBTSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLE9BQU8sQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO1FBQ2hJLENBQUMsQ0FBQyxDQUFDO0lBQ0osQ0FBQyxDQUFDLENBQUM7SUFDSCxNQUFNLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsRUFBRTtRQUNyQyxNQUFNLEtBQUssR0FBRyxVQUFVLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDOUIsR0FBRyxDQUFDLEdBQUcsR0FBRyxRQUFRLEtBQUssd0JBQXdCLENBQUMsQ0FBQztJQUNsRCxDQUFDLENBQUMsQ0FBQztJQUNILGVBQWUsQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLEVBQUU7UUFDbEMsTUFBTSxLQUFLLEdBQUcsVUFBVSxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUMsQ0FBQztRQUN0QyxJQUFJLEVBQUUsQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQztZQUNyQixHQUFHLENBQUMsd0NBQXdDLFFBQVEsQ0FBQyxFQUFFLG1DQUFtQyxDQUFDLENBQUM7UUFDN0YsQ0FBQztJQUNGLENBQUMsQ0FBQyxDQUFDO0FBQ0osQ0FBQztBQUVELFNBQWdCLGVBQWUsQ0FBQyxJQUFtRDtJQUNsRixPQUFPLElBQUEsc0JBQU8sRUFBQyxVQUErQixJQUFVO1FBQ3ZELE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQzFDLElBQUksUUFBUSxLQUFLLG1CQUFtQixFQUFFLENBQUM7WUFDdEMsSUFBSSxJQUFJLEdBQUcsSUFBSSxDQUFDO1lBQ2hCLElBQUksSUFBSSxDQUFDLFFBQVEsRUFBRSxFQUFFLENBQUM7Z0JBQ3JCLElBQUksR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFVLElBQUksQ0FBQyxRQUFTLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUM7WUFDN0QsQ0FBQztpQkFBTSxDQUFDO2dCQUNQLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLGtDQUFrQyxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQztnQkFDdEUsT0FBTztZQUNSLENBQUM7WUFDRCxJQUFJLGFBQWEsQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQztnQkFDNUIsdUJBQXVCLENBQUMsSUFBSSxDQUFDLFVBQVUsRUFBRSxJQUFJLENBQUMsU0FBUyxFQUFFLElBQUksRUFBRSxJQUFJLENBQUMsQ0FBQztZQUN0RSxDQUFDO1FBQ0YsQ0FBQztRQUNELElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDbEIsQ0FBQyxDQUFDLENBQUM7QUFDSixDQUFDO0FBakJELDBDQWlCQztBQUVELE1BQU0sYUFBYSxHQUFXLGVBQWUsRUFDNUMsZ0JBQWdCLEdBQVcsa0JBQWtCLEVBQzdDLGlCQUFpQixHQUFXLG1CQUFtQixFQUMvQyxZQUFZLEdBQVcsY0FBYyxFQUNyQyxhQUFhLEdBQVcsZUFBZSxDQUFDO0FBRXpDLFNBQWdCLFdBQVcsQ0FBQyxVQUFrQjtJQUM3QyxJQUFJLFFBQWdCLENBQUM7SUFFckIsSUFBSSxlQUFlLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUM7UUFDdEMsT0FBTyxFQUFFLElBQUksRUFBRSxhQUFhLEVBQUUsT0FBTyxFQUFFLGFBQWEsRUFBRSxDQUFDO0lBQ3hELENBQUM7U0FBTSxJQUFJLHNCQUFzQixDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsRUFBRSxDQUFDO1FBQ3BELE9BQU8sRUFBRSxJQUFJLEVBQUUsbUJBQW1CLEVBQUUsT0FBTyxFQUFFLGFBQWEsRUFBRSxDQUFDO0lBQzlELENBQUM7U0FBTSxJQUFJLGFBQWEsQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLEVBQUUsQ0FBQztRQUMzQyxPQUFPLEVBQUUsSUFBSSxFQUFFLFdBQVcsRUFBRSxPQUFPLEVBQUUsYUFBYSxFQUFFLENBQUM7SUFDdEQsQ0FBQztTQUFNLElBQUksV0FBVyxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsRUFBRSxDQUFDO1FBQ3pDLE9BQU8sRUFBRSxJQUFJLEVBQUUsU0FBUyxFQUFFLE9BQU8sRUFBRSxhQUFhLEVBQUUsQ0FBQztJQUNwRCxDQUFDO1NBQU0sSUFBSSxXQUFXLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUM7UUFDekMsT0FBTyxFQUFFLElBQUksRUFBRSxTQUFTLEVBQUUsT0FBTyxFQUFFLGdCQUFnQixFQUFFLENBQUM7SUFDdkQsQ0FBQztTQUFNLElBQUksYUFBYSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsRUFBRSxDQUFDO1FBQzNDLE9BQU8sRUFBRSxJQUFJLEVBQUUsV0FBVyxFQUFFLE9BQU8sRUFBRSxhQUFhLEVBQUUsQ0FBQztJQUN0RCxDQUFDO1NBQU0sSUFBSSx5QkFBeUIsQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLEVBQUUsQ0FBQztRQUN2RCxRQUFRLEdBQUcsVUFBVSxDQUFDLEtBQUssQ0FBQyxHQUFHLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQzlDLE9BQU8sRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxDQUFDO0lBQ3RELENBQUM7U0FBTSxJQUFJLDBCQUEwQixDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsRUFBRSxDQUFDO1FBQ3hELFFBQVEsR0FBRyxVQUFVLENBQUMsS0FBSyxDQUFDLEdBQUcsRUFBRSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDOUMsT0FBTyxFQUFFLElBQUksRUFBRSxRQUFRLEVBQUUsT0FBTyxFQUFFLGdCQUFnQixFQUFFLENBQUM7SUFDdEQsQ0FBQztTQUFNLElBQUksZ0JBQWdCLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUM7UUFDOUMsT0FBTyxFQUFFLElBQUksRUFBRSxjQUFjLEVBQUUsT0FBTyxFQUFFLGdCQUFnQixFQUFFLENBQUM7SUFDNUQsQ0FBQztJQUVELE1BQU0sSUFBSSxLQUFLLENBQUMseUNBQXlDLFVBQVUsRUFBRSxDQUFDLENBQUM7QUFDeEUsQ0FBQztBQTFCRCxrQ0EwQkM7QUFHRCxTQUFnQiwyQkFBMkI7SUFDMUMsT0FBTyxJQUFBLHNCQUFPLEVBQUMsVUFBK0IsSUFBVTtRQUN2RCxNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUMxQyxJQUFJLFFBQVEsS0FBSyxtQkFBbUIsRUFBRSxDQUFDO1lBQ3RDLElBQUksSUFBSSxDQUFDLFFBQVEsRUFBRSxFQUFFLENBQUM7Z0JBQ3JCLE1BQU0sSUFBSSxHQUF3QixNQUFNLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDO2dCQUN0RCxNQUFNLElBQUksR0FBa0IsSUFBSSxDQUFDLEtBQUssQ0FBRSxJQUFJLENBQUMsUUFBbUIsQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQztnQkFDbkYsS0FBSyxNQUFNLFVBQVUsSUFBSSxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUM7b0JBQ3BDLE1BQU0sZUFBZSxHQUFHLFdBQVcsQ0FBQyxVQUFVLENBQUMsQ0FBQztvQkFDaEQsTUFBTSxRQUFRLEdBQUcsZUFBZSxDQUFDLElBQUksQ0FBQztvQkFDdEMsTUFBTSxPQUFPLEdBQUcsZUFBZSxDQUFDLE9BQU8sQ0FBQztvQkFFeEMsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQztvQkFDbkMsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQyxVQUFVLENBQUMsQ0FBQztvQkFDM0MsSUFBSSxJQUFJLENBQUMsTUFBTSxLQUFLLFFBQVEsQ0FBQyxNQUFNLEVBQUUsQ0FBQzt3QkFDckMsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsb0RBQW9ELElBQUksQ0FBQyxRQUFRLGVBQWUsVUFBVSxFQUFFLENBQUMsQ0FBQzt3QkFDakgsT0FBTztvQkFDUixDQUFDO3lCQUFNLENBQUM7d0JBQ1AsSUFBSSxHQUFHLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDO3dCQUN6QixJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7NEJBQ1YsR0FBRyxHQUFHLElBQUksR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFDOzRCQUN2QixJQUFJLENBQUMsUUFBUSxDQUFDLEdBQUcsR0FBRyxDQUFDO3dCQUN0QixDQUFDO3dCQUNELEdBQUcsQ0FBQyxPQUFPLENBQUMsT0FBTyxVQUFVLEVBQUUsRUFBRSxJQUFJLEVBQUUsUUFBUSxDQUFDLENBQUM7b0JBQ2xELENBQUM7Z0JBQ0YsQ0FBQztnQkFDRCxLQUFLLE1BQU0sUUFBUSxJQUFJLElBQUksRUFBRSxDQUFDO29CQUM3QixNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7b0JBQzNCLE1BQU0sUUFBUSxHQUFHLEdBQUcsR0FBRyxDQUFDLE9BQU8sSUFBSSxRQUFRLENBQUMsT0FBTyxDQUFDLEtBQUssRUFBRSxHQUFHLENBQUMsTUFBTSxDQUFDO29CQUN0RSxNQUFNLE9BQU8sR0FBRyxJQUFJLElBQUksQ0FBQzt3QkFDeEIsSUFBSSxFQUFFLFFBQVE7d0JBQ2QsUUFBUSxFQUFFLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLFFBQVEsRUFBRSxFQUFFLE1BQU0sQ0FBQztxQkFDN0MsQ0FBQyxDQUFDO29CQUNILElBQUksQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUM7Z0JBQ3JCLENBQUM7WUFDRixDQUFDO2lCQUFNLENBQUM7Z0JBQ1AsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsSUFBSSxLQUFLLENBQUMsUUFBUSxJQUFJLENBQUMsUUFBUSxnQ0FBZ0MsQ0FBQyxDQUFDLENBQUM7Z0JBQ3JGLE9BQU87WUFDUixDQUFDO1FBQ0YsQ0FBQzthQUFNLENBQUM7WUFDUCxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxJQUFJLEtBQUssQ0FBQyxRQUFRLElBQUksQ0FBQyxRQUFRLGdDQUFnQyxDQUFDLENBQUMsQ0FBQztZQUNyRixPQUFPO1FBQ1IsQ0FBQztJQUNGLENBQUMsQ0FBQyxDQUFDO0FBQ0osQ0FBQztBQTVDRCxrRUE0Q0M7QUFFRCxTQUFTLDRCQUE0QixDQUFDLG1CQUEyQixFQUFFLHFCQUE4QjtJQUNoRyxNQUFNLE1BQU0sR0FBRyxxQkFBcUIsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7SUFDdEQsT0FBTyxJQUFJO1NBQ1QsR0FBRyxDQUFDO1FBQ0osZ0NBQWdDO1FBQ2hDLEdBQUcsTUFBTSxjQUFjLG1CQUFtQixvQ0FBb0M7UUFDOUUsK0ZBQStGO1FBQy9GLEdBQUcsTUFBTSxjQUFjLG1CQUFtQixtREFBbUQ7UUFDN0YsK0ZBQStGO1FBQy9GLEdBQUcsTUFBTSxjQUFjLG1CQUFtQixzQkFBc0I7S0FDaEUsQ0FBQztTQUNELElBQUksQ0FBQyxJQUFBLGtCQUFHLEVBQUMsVUFBVSxJQUFJLEVBQUUsUUFBUTtRQUNqQyxNQUFNLElBQUksR0FBRyxJQUFZLENBQUM7UUFDMUIsSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUUsRUFBRSxDQUFDO1lBQ3RCLDZCQUE2QjtZQUM3QixRQUFRLEVBQUUsQ0FBQztZQUNYLE9BQU87UUFDUixDQUFDO1FBQ0QsTUFBTSxTQUFTLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDOUMsSUFBSSxTQUFTLEtBQUssT0FBTyxFQUFFLENBQUM7WUFDM0IsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUM7WUFDaEQsSUFBQSxzQkFBVyxFQUFDLENBQUMsRUFBRSxRQUFRLEVBQUUsU0FBUyxFQUFFLENBQUMsQ0FBQztpQkFDcEMsSUFBSSxDQUFDLENBQUMsSUFBSSxFQUFFLEVBQUU7Z0JBQ2QsUUFBUSxDQUFDLFNBQVMsRUFBRSxJQUFJLElBQUksQ0FBQztvQkFDNUIsSUFBSSxFQUFFLGNBQWMsbUJBQW1CLG1CQUFtQjtvQkFDMUQsUUFBUSxFQUFFLE1BQU0sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsRUFBRSxNQUFNLENBQUM7aUJBQ25ELENBQUMsQ0FBQyxDQUFDO1lBQ0wsQ0FBQyxDQUFDO2lCQUNELEtBQUssQ0FBQyxDQUFDLEdBQUcsRUFBRSxFQUFFO2dCQUNkLFFBQVEsQ0FBQyxJQUFJLEtBQUssQ0FBQyxRQUFRLElBQUksQ0FBQyxRQUFRLGlDQUFpQyxHQUFHLEVBQUUsQ0FBQyxDQUFDLENBQUM7WUFDbEYsQ0FBQyxDQUFDLENBQUM7WUFDSixnQkFBZ0I7WUFDaEIsT0FBTyxLQUFLLENBQUM7UUFDZCxDQUFDO1FBRUQsd0JBQXdCO1FBQ3hCLElBQUksVUFBVSxDQUFDO1FBQ2YsSUFBSSxDQUFDO1lBQ0osVUFBVSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQztRQUN6RCxDQUFDO1FBQUMsT0FBTyxHQUFHLEVBQUUsQ0FBQztZQUNkLFFBQVEsQ0FBQyxJQUFJLEtBQUssQ0FBQyxRQUFRLElBQUksQ0FBQyxRQUFRLGlDQUFpQyxHQUFHLEVBQUUsQ0FBQyxDQUFDLENBQUM7WUFDakYsT0FBTztRQUNSLENBQUM7UUFFRCxpREFBaUQ7UUFDakQsS0FBSyxNQUFNLEdBQUcsSUFBSSxVQUFVLEVBQUUsQ0FBQztZQUM5QixJQUNDLE9BQU8sVUFBVSxDQUFDLEdBQUcsQ0FBQyxLQUFLLFFBQVE7Z0JBQ25DLENBQUMsT0FBTyxVQUFVLENBQUMsR0FBRyxDQUFDLENBQUMsT0FBTyxLQUFLLFFBQVEsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLEVBQ3ZGLENBQUM7Z0JBQ0YsUUFBUSxDQUFDLElBQUksS0FBSyxDQUFDLG9EQUFvRCxHQUFHLGlDQUFpQyxDQUFDLENBQUMsQ0FBQztnQkFDOUcsT0FBTztZQUNSLENBQUM7UUFDRixDQUFDO1FBRUQsUUFBUSxDQUFDLFNBQVMsRUFBRSxJQUFJLENBQUMsQ0FBQztJQUMzQixDQUFDLENBQUMsQ0FBQztTQUNGLElBQUksQ0FBQyxTQUFTLENBQUM7UUFDZixRQUFRLEVBQUUsY0FBYyxtQkFBbUIsbUJBQW1CO1FBQzlELFNBQVMsRUFBRSxFQUFFO1FBQ2IsWUFBWSxFQUFFLElBQUk7S0FDbEIsQ0FBQyxDQUFDLENBQUM7QUFDTixDQUFDO0FBRVksUUFBQSxtQkFBbUIsR0FBRztJQUNsQyxvQkFBb0I7SUFDcEIsOEJBQThCO0lBQzlCLG1DQUFtQztDQUNuQyxDQUFDO0FBRUYsU0FBZ0IsMkJBQTJCO0lBQzFDLElBQUksT0FBTyxHQUFXLENBQUMsQ0FBQztJQUN4QixJQUFJLGlCQUFpQixHQUFZLEtBQUssQ0FBQztJQUN2QyxJQUFJLHNCQUFzQixHQUFZLEtBQUssQ0FBQztJQUM1QyxPQUFPLElBQUEsc0JBQU8sRUFBQyxVQUErQixlQUFxQjtRQUNsRSxNQUFNLFlBQVksR0FBRyxJQUFJLENBQUM7UUFDMUIsTUFBTSxJQUFJLEdBQUcsRUFBRSxDQUFDLFFBQVEsQ0FBQyxlQUFlLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDL0MsSUFBSSxDQUFDLElBQUksQ0FBQyxXQUFXLEVBQUUsRUFBRSxDQUFDO1lBQ3pCLE9BQU87UUFDUixDQUFDO1FBQ0QsTUFBTSxtQkFBbUIsR0FBRyxJQUFJLENBQUMsUUFBUSxDQUFDLGVBQWUsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUNoRSxJQUFJLG1CQUFtQixLQUFLLGNBQWMsRUFBRSxDQUFDO1lBQzVDLE9BQU87UUFDUixDQUFDO1FBQ0QsMENBQTBDO1FBQzFDLE1BQU0sUUFBUSxHQUFHLEVBQUUsQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxlQUFlLENBQUMsSUFBSSxFQUFFLGNBQWMsQ0FBQyxFQUFFLE9BQU8sQ0FBQyxDQUFDO1FBQzNGLE1BQU0sWUFBWSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDMUMsTUFBTSxXQUFXLEdBQUcsWUFBWSxDQUFDLFNBQVMsR0FBRyxHQUFHLEdBQUcsWUFBWSxDQUFDLElBQUksQ0FBQztRQUVyRSxPQUFPLEVBQUUsQ0FBQztRQUNWLElBQUksUUFBcUMsQ0FBQztRQUMxQyxTQUFTLFVBQVU7WUFDbEIsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO2dCQUNmLFFBQVEsR0FBRyxJQUFJLEdBQUcsRUFBRSxDQUFDO1lBQ3RCLENBQUM7WUFDRCxPQUFPLFFBQVEsQ0FBQztRQUNqQixDQUFDO1FBQ0QsSUFBQSxvQkFBSyxFQUNKLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxxQkFBcUIsbUJBQW1CLG1CQUFtQixFQUFFLHFCQUFxQixtQkFBbUIsdUJBQXVCLENBQUMsRUFBRSxFQUFFLFVBQVUsRUFBRSxJQUFJLEVBQUUsQ0FBQyxFQUM5Siw0QkFBNEIsQ0FBQyxtQkFBbUIsRUFBRSwyQkFBbUIsQ0FBQyxRQUFRLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FDNUYsQ0FBQyxJQUFJLENBQUMsSUFBQSxzQkFBTyxFQUFDLFVBQVUsSUFBVTtZQUNsQyxJQUFJLElBQUksQ0FBQyxRQUFRLEVBQUUsRUFBRSxDQUFDO2dCQUNyQixNQUFNLE1BQU0sR0FBVyxJQUFJLENBQUMsUUFBa0IsQ0FBQztnQkFDL0MsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7Z0JBQzFDLElBQUksUUFBUSxLQUFLLGtCQUFrQixFQUFFLENBQUM7b0JBQ3JDLE1BQU0sSUFBSSxHQUFtQixJQUFJLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQztvQkFDakUsVUFBVSxFQUFFLENBQUMsR0FBRyxDQUFDLGNBQWMsV0FBVyxVQUFVLEVBQUUsSUFBSSxDQUFDLENBQUM7Z0JBQzdELENBQUM7cUJBQU0sSUFBSSxRQUFRLEtBQUssbUJBQW1CLEVBQUUsQ0FBQztvQkFDN0MsTUFBTSxJQUFJLEdBQTJCLElBQUksQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDO29CQUN6RSxNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsUUFBUSxDQUFDLHFCQUFxQixtQkFBbUIsRUFBRSxFQUFFLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7b0JBQ25HLEtBQUssTUFBTSxJQUFJLElBQUksSUFBSSxFQUFFLENBQUM7d0JBQ3pCLE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQzt3QkFDL0IsTUFBTSxJQUFJLEdBQW1CLE1BQU0sQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUM7d0JBQ2pELEtBQUssSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxXQUFXLENBQUMsUUFBUSxDQUFDLE1BQU0sRUFBRSxDQUFDLEVBQUUsRUFBRSxDQUFDOzRCQUN0RCxNQUFNLE9BQU8sR0FBRyxXQUFXLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDOzRCQUN4QyxNQUFNLEVBQUUsR0FBRyxFQUFFLE9BQU8sRUFBRSxHQUFHLFlBQVksQ0FBQyxFQUFFLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztnQ0FDNUQsQ0FBQyxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFpQjtnQ0FDckMsQ0FBQyxDQUFDLEVBQUUsR0FBRyxFQUFFLFdBQVcsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFXLEVBQUUsT0FBTyxFQUFFLFNBQVMsRUFBRSxDQUFDOzRCQUU5RCxJQUFJLENBQUMsR0FBRyxDQUFDLEdBQUcsT0FBTyxDQUFDLENBQUMsQ0FBQyxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDO3dCQUN0RCxDQUFDO3dCQUNELFVBQVUsRUFBRSxDQUFDLEdBQUcsQ0FBQyxjQUFjLFdBQVcsSUFBSSxPQUFPLElBQUksSUFBSSxFQUFFLEVBQUUsSUFBSSxDQUFDLENBQUM7b0JBQ3hFLENBQUM7Z0JBQ0YsQ0FBQztxQkFBTSxJQUFJLFFBQVEsS0FBSyxrQkFBa0IsRUFBRSxDQUFDO29CQUM1QyxNQUFNLElBQUksR0FBbUIsSUFBSSxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUM7b0JBQ2pFLFVBQVUsRUFBRSxDQUFDLEdBQUcsQ0FBQyxjQUFjLFdBQVcsU0FBUyxFQUFFLElBQUksQ0FBQyxDQUFDO2dCQUM1RCxDQUFDO3FCQUFNLENBQUM7b0JBQ1AsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsSUFBSSxLQUFLLENBQUMsR0FBRyxJQUFJLENBQUMsSUFBSSxvQ0FBb0MsQ0FBQyxDQUFDLENBQUM7b0JBQ2hGLE9BQU87Z0JBQ1IsQ0FBQztZQUNGLENBQUM7UUFDRixDQUFDLEVBQUU7WUFDRixJQUFJLFFBQVEsRUFBRSxJQUFJLEdBQUcsQ0FBQyxFQUFFLENBQUM7Z0JBQ3hCLE1BQU0sT0FBTyxHQUFHLElBQUksSUFBSSxDQUFDO29CQUN4QixJQUFJLEVBQUUsSUFBSSxDQUFDLElBQUksQ0FBQyxpQkFBaUIsRUFBRSxXQUFXLEdBQUcsTUFBTSxDQUFDO29CQUN4RCxRQUFRLEVBQUUsTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFBLHFCQUFVLEVBQUMsUUFBUSxDQUFDLEVBQUUsTUFBTSxDQUFDO2lCQUNuRCxDQUFDLENBQUM7Z0JBQ0gsWUFBWSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQztZQUM3QixDQUFDO1lBQ0QsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQztZQUNqQixPQUFPLEVBQUUsQ0FBQztZQUNWLElBQUksT0FBTyxLQUFLLENBQUMsSUFBSSxpQkFBaUIsSUFBSSxDQUFDLHNCQUFzQixFQUFFLENBQUM7Z0JBQ25FLHNCQUFzQixHQUFHLElBQUksQ0FBQztnQkFDOUIsWUFBWSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQztZQUMxQixDQUFDO1FBQ0YsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUNMLENBQUMsRUFBRTtRQUNGLGlCQUFpQixHQUFHLElBQUksQ0FBQztRQUN6QixJQUFJLE9BQU8sS0FBSyxDQUFDLEVBQUUsQ0FBQztZQUNuQixzQkFBc0IsR0FBRyxJQUFJLENBQUM7WUFDOUIsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUNsQixDQUFDO0lBQ0YsQ0FBQyxDQUFDLENBQUM7QUFDSixDQUFDO0FBbkZELGtFQW1GQztBQUVELFNBQWdCLG9CQUFvQjtJQUNuQyxPQUFPLElBQUEsc0JBQU8sRUFBQyxVQUErQixJQUFVO1FBQ3ZELElBQUksV0FBbUIsRUFDdEIsWUFBb0IsQ0FBQztRQUN0QixJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxLQUFLLGlCQUFpQixFQUFFLENBQUM7WUFDcEQsV0FBVyxHQUFHLFlBQVksQ0FBQztZQUMzQixZQUFZLEdBQUcsY0FBYyxDQUFDO1FBQy9CLENBQUM7YUFBTSxDQUFDO1lBQ1AsTUFBTSxJQUFJLEtBQUssQ0FBQyxzQkFBc0IsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDLENBQUM7UUFDcEQsQ0FBQztRQUVELE1BQU0sR0FBRyxHQUFHLElBQUksR0FBRyxDQUFDLFdBQVcsQ0FBQyxFQUMvQixJQUFJLEdBQWEsRUFBRSxFQUNuQixRQUFRLEdBQWEsRUFBRSxDQUFDO1FBRXpCLE1BQU0sS0FBSyxHQUFHLElBQUksU0FBUyxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQztRQUN0RCxJQUFJLGdCQUFnQixHQUFHLEtBQUssQ0FBQztRQUM3QixLQUFLLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsRUFBRTtZQUMxQixJQUFJLElBQUksQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFLENBQUM7Z0JBQ3ZCLE9BQU87WUFDUixDQUFDO1lBQ0QsTUFBTSxTQUFTLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUNqQyxRQUFRLFNBQVMsRUFBRSxDQUFDO2dCQUNuQixLQUFLLEdBQUc7b0JBQ1AsZ0JBQWdCO29CQUNoQixPQUFPO2dCQUNSLEtBQUssR0FBRztvQkFDUCxnQkFBZ0IsR0FBRyxZQUFZLEtBQUssSUFBSSxJQUFJLGtCQUFrQixLQUFLLElBQUksQ0FBQztvQkFDeEUsT0FBTztZQUNULENBQUM7WUFDRCxJQUFJLENBQUMsZ0JBQWdCLEVBQUUsQ0FBQztnQkFDdkIsT0FBTztZQUNSLENBQUM7WUFDRCxNQUFNLFFBQVEsR0FBYSxJQUFJLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1lBQzNDLElBQUksUUFBUSxDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUUsQ0FBQztnQkFDM0IsTUFBTSxJQUFJLEtBQUssQ0FBQyxrQ0FBa0MsSUFBSSxFQUFFLENBQUMsQ0FBQztZQUMzRCxDQUFDO2lCQUFNLENBQUM7Z0JBQ1AsTUFBTSxHQUFHLEdBQUcsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDO2dCQUN4QixNQUFNLEtBQUssR0FBRyxRQUFRLENBQUMsQ0FBQyxDQUFDLENBQUM7Z0JBQzFCLElBQUksR0FBRyxDQUFDLE1BQU0sR0FBRyxDQUFDLElBQUksS0FBSyxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQztvQkFDeEMsSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQztvQkFDZixRQUFRLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDO2dCQUN0QixDQUFDO1lBQ0YsQ0FBQztRQUNGLENBQUMsQ0FBQyxDQUFDO1FBRUgsTUFBTSxZQUFZLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUFFLElBQUksQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsR0FBRyxDQUFDLENBQUM7UUFDbEgsR0FBRyxDQUFDLE9BQU8sQ0FBQyxZQUFZLEVBQUUsSUFBSSxFQUFFLFFBQVEsQ0FBQyxDQUFDO1FBRTFDLGlFQUFpRTtRQUNqRSxNQUFNLFdBQVcsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLFdBQVcsRUFBRSxZQUFZLENBQUMsQ0FBQztRQUN6RCxNQUFNLE9BQU8sR0FBRyxJQUFJLElBQUksQ0FBQyxFQUFFLElBQUksRUFBRSxXQUFXLEVBQUUsUUFBUSxFQUFFLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLFFBQVEsRUFBRSxFQUFFLE9BQU8sQ0FBQyxFQUFFLENBQUMsQ0FBQztRQUNoRyxJQUFJLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQ3JCLENBQUMsQ0FBQyxDQUFDO0FBQ0osQ0FBQztBQXRERCxvREFzREM7QUFFRCxTQUFTLGNBQWMsQ0FBQyxJQUFZLEVBQUUsUUFBYTtJQUNsRCxNQUFNLE1BQU0sR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ25DLE1BQU0sQ0FBQyxFQUFFLENBQUMsR0FBRztRQUNaLDhGQUE4RjtRQUM5RiwyREFBMkQ7UUFDM0QsOEZBQThGO1FBQzlGLDhGQUE4RjtRQUM5RixpREFBaUQ7S0FDakQsQ0FBQztJQUNGLEtBQUssTUFBTSxHQUFHLElBQUksTUFBTSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDO1FBQ3pDLE1BQU0sQ0FBQyxHQUFHLENBQUMsR0FBRyxRQUFRLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDN0IsQ0FBQztJQUVELElBQUksT0FBTyxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUMsTUFBTSxFQUFFLElBQUksRUFBRSxJQUFJLENBQUMsQ0FBQztJQUNqRCxJQUFJLE9BQU8sQ0FBQyxRQUFRLEtBQUssT0FBTyxFQUFFLENBQUM7UUFDbEMsT0FBTyxHQUFHLE9BQU8sQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFLE1BQU0sQ0FBQyxDQUFDO0lBQzFDLENBQUM7SUFDRCxPQUFPLElBQUksSUFBSSxDQUFDO1FBQ2YsSUFBSSxFQUFFLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxHQUFHLFlBQVksQ0FBQztRQUNwQyxRQUFRLEVBQUUsTUFBTSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsTUFBTSxDQUFDO0tBQ3RDLENBQUMsQ0FBQztBQUNKLENBQUM7QUFTRCxNQUFNLGVBQWUsR0FBRyxPQUFPLENBQUM7QUFPaEMsU0FBUywyQkFBMkIsQ0FBQyxjQUE4QjtJQUNsRSxNQUFNLE1BQU0sR0FBMkIsRUFBRSxDQUFDO0lBQzFDLEtBQUssTUFBTSxHQUFHLElBQUksTUFBTSxDQUFDLElBQUksQ0FBQyxjQUFjLENBQUMsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDO1FBQ3RELE1BQU0sS0FBSyxHQUFHLGNBQWMsQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUNsQyxNQUFNLENBQUMsR0FBRyxDQUFDLEdBQUcsT0FBTyxLQUFLLEtBQUssUUFBUSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUM7SUFDakUsQ0FBQztJQUNELE9BQU8sTUFBTSxDQUFDO0FBQ2YsQ0FBQztBQUVELFNBQWdCLG9CQUFvQixDQUFDLHlCQUE0QztJQUNoRixNQUFNLGFBQWEsR0FBaUMsRUFBRSxDQUFDO0lBQ3ZELE1BQU0sUUFBUSxHQUFhLEVBQUUsT0FBTyxFQUFFLGVBQWUsRUFBRSxRQUFRLEVBQUUsRUFBRSxFQUFFLENBQUM7SUFDdEUsTUFBTSxlQUFlLEdBQTZCLEVBQUUsQ0FBQztJQUNyRCxNQUFNLE1BQU0sR0FBVSxFQUFFLENBQUM7SUFDekIsT0FBTyxJQUFBLHNCQUFPLEVBQUMsVUFBK0IsR0FBUztRQUN0RCxJQUFJLE9BQU8sR0FBRyxJQUFJLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ3RFLGtIQUFrSDtRQUNsSCxNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLFFBQVEsRUFBRSxNQUFNLENBQUMsRUFBRSxNQUFNLENBQUMsQ0FBQztRQUM1RSxJQUFJLDJCQUFtQixDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsS0FBSyxRQUFRLENBQUMsRUFBRSxDQUFDO1lBQ25ELE9BQU8sR0FBRyxpQkFBaUIsQ0FBQztRQUM3QixDQUFDO1FBQ0QsTUFBTSxRQUFRLEdBQUcsR0FBRyxDQUFDLFFBQVEsQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUN6QyxHQUFHLENBQUMsU0FBUyxPQUFPLEtBQUssUUFBUSxFQUFFLENBQUMsQ0FBQztRQUNyQyxNQUFNLFlBQVksR0FBRyxJQUFBLDhCQUFtQixFQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQ25ELGFBQWEsQ0FBQyxJQUFJLENBQUMsWUFBWSxDQUFDLENBQUM7UUFDakMsWUFBWSxDQUFDLElBQUksQ0FDaEIsYUFBYSxDQUFDLEVBQUU7WUFDZixhQUFhLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxFQUFFO2dCQUM1QixNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDO2dCQUN2QixNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDO2dCQUVyQyxJQUFJLE9BQU8sS0FBSyxpQkFBaUIsRUFBRSxDQUFDO29CQUNuQyxvQ0FBb0M7b0JBQ3BDLElBQUksT0FBTyxHQUFHLGVBQWUsQ0FBQyxRQUFRLENBQUMsQ0FBQztvQkFDeEMsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDO3dCQUNkLE9BQU8sR0FBRyxlQUFlLENBQUMsUUFBUSxDQUFDLEdBQUcsRUFBRSxPQUFPLEVBQUUsZUFBZSxFQUFFLFFBQVEsRUFBRSxFQUFFLEVBQUUsQ0FBQztvQkFDbEYsQ0FBQztvQkFDRCwyQ0FBMkM7b0JBQzNDLE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsR0FBRyxFQUFFLFVBQVUsR0FBRyxDQUFDLENBQUMsQ0FBQztvQkFDdEQsT0FBTyxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLFdBQVcsR0FBRyxDQUFDLENBQUMsQ0FBQyxHQUFHLDJCQUEyQixDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQztnQkFDaEcsQ0FBQztxQkFBTSxDQUFDO29CQUNQLFFBQVEsQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxVQUFVLEdBQUcsQ0FBQyxDQUFDLENBQUMsR0FBRywyQkFBMkIsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7Z0JBQ2hHLENBQUM7WUFDRixDQUFDLENBQUMsQ0FBQztRQUNKLENBQUMsQ0FDRCxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUMsRUFBRTtZQUNoQixNQUFNLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQ3JCLENBQUMsQ0FBQyxDQUFDO0lBQ0osQ0FBQyxFQUFFO1FBQ0YsT0FBTyxDQUFDLEdBQUcsQ0FBQyxhQUFhLENBQUM7YUFDeEIsSUFBSSxDQUFDLEdBQUcsRUFBRTtZQUNWLElBQUksTUFBTSxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQztnQkFDdkIsTUFBTSxNQUFNLENBQUM7WUFDZCxDQUFDO1lBQ0QsTUFBTSxrQkFBa0IsR0FBRyxjQUFjLENBQUMsUUFBUSxFQUFFLFFBQVEsQ0FBQyxDQUFDO1lBQzlELHlCQUF5QixDQUFDLElBQUksQ0FBQyxFQUFFLEVBQUUsRUFBRSxRQUFRLEVBQUUsWUFBWSxFQUFFLGdCQUFnQixFQUFFLENBQUMsQ0FBQztZQUVqRixJQUFJLENBQUMsS0FBSyxDQUFDLGtCQUFrQixDQUFDLENBQUM7WUFDL0IsS0FBSyxNQUFNLFdBQVcsSUFBSSxlQUFlLEVBQUUsQ0FBQztnQkFDM0MsTUFBTSxpQkFBaUIsR0FBRyxjQUFjLENBQUMsY0FBYyxXQUFXLEVBQUUsRUFBRSxlQUFlLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQztnQkFDcEcsSUFBSSxDQUFDLEtBQUssQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDO2dCQUU5Qix5QkFBeUIsQ0FBQyxJQUFJLENBQUMsRUFBRSxFQUFFLEVBQUUsV0FBVyxFQUFFLFlBQVksRUFBRSxjQUFjLFdBQVcsWUFBWSxFQUFFLENBQUMsQ0FBQztZQUMxRyxDQUFDO1lBQ0QsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUNsQixDQUFDLENBQUM7YUFDRCxLQUFLLENBQUMsQ0FBQyxNQUFNLEVBQUUsRUFBRTtZQUNqQixJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxNQUFNLENBQUMsQ0FBQztRQUM1QixDQUFDLENBQUMsQ0FBQztJQUNMLENBQUMsQ0FBQyxDQUFDO0FBQ0osQ0FBQztBQTdERCxvREE2REM7QUFFRCxTQUFnQixlQUFlLENBQUMsUUFBa0IsRUFBRSxlQUEwQjtJQUM3RSxNQUFNLGFBQWEsR0FBaUMsRUFBRSxDQUFDO0lBRXZELE9BQU8sSUFBQSxzQkFBTyxFQUFDLFVBQStCLEdBQVM7UUFDdEQsTUFBTSxNQUFNLEdBQUcsSUFBSSxDQUFDO1FBQ3BCLE1BQU0sWUFBWSxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQUFDO1FBQ3hELGFBQWEsQ0FBQyxJQUFJLENBQUMsWUFBWSxDQUFDLENBQUM7UUFDakMsWUFBWSxDQUFDLElBQUksQ0FDaEIsYUFBYSxDQUFDLEVBQUU7WUFDZixhQUFhLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxFQUFFO2dCQUM1QixNQUFNLGNBQWMsR0FBRyxhQUFhLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxJQUFJLENBQUMsUUFBUSxFQUFFLFFBQVEsRUFBRSxlQUFlLENBQUMsQ0FBQztnQkFDMUYsTUFBTSxDQUFDLEtBQUssQ0FBQyxjQUFjLENBQUMsQ0FBQztZQUM5QixDQUFDLENBQUMsQ0FBQztRQUNKLENBQUMsQ0FDRCxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUMsRUFBRTtZQUNoQixJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxNQUFNLENBQUMsQ0FBQztRQUM1QixDQUFDLENBQUMsQ0FBQztJQUNKLENBQUMsRUFBRTtRQUNGLE9BQU8sQ0FBQyxHQUFHLENBQUMsYUFBYSxDQUFDO2FBQ3hCLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO2FBQ2pDLEtBQUssQ0FBQyxNQUFNLENBQUMsRUFBRTtZQUNmLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLE1BQU0sQ0FBQyxDQUFDO1FBQzVCLENBQUMsQ0FBQyxDQUFDO0lBQ0wsQ0FBQyxDQUFDLENBQUM7QUFDSixDQUFDO0FBeEJELDBDQXdCQztBQUVELFNBQVMsYUFBYSxDQUFDLElBQVksRUFBRSxRQUF3QixFQUFFLFFBQWtCLEVBQUUsU0FBb0I7SUFDdEcsTUFBTSxPQUFPLEdBQWEsRUFBRSxDQUFDO0lBQzdCLElBQUksZUFBMEIsQ0FBQztJQUMvQixJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLEtBQUssU0FBUyxFQUFFLENBQUM7UUFDdkMsZUFBZSxHQUFHLElBQUksU0FBUyxDQUFDLEVBQUUsQ0FBQyxZQUFZLENBQUMsSUFBSSxHQUFHLE1BQU0sRUFBRSxNQUFNLENBQUMsQ0FBQyxDQUFDO0lBQ3pFLENBQUM7U0FBTSxDQUFDO1FBQ1AsZUFBZSxHQUFHLElBQUksU0FBUyxDQUFDLEVBQUUsQ0FBQyxZQUFZLENBQUMsSUFBSSxHQUFHLFNBQVMsRUFBRSxNQUFNLENBQUMsQ0FBQyxDQUFDO0lBQzVFLENBQUM7SUFDRCxlQUFlLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsRUFBRTtRQUNwQyxJQUFJLElBQUksQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUFFLENBQUM7WUFDckIsTUFBTSxTQUFTLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUNqQyxJQUFJLFNBQVMsS0FBSyxHQUFHLElBQUksU0FBUyxLQUFLLEdBQUcsRUFBRSxDQUFDO2dCQUM1QyxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQ3BCLENBQUM7aUJBQU0sQ0FBQztnQkFDUCxNQUFNLFFBQVEsR0FBYSxJQUFJLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO2dCQUMzQyxNQUFNLEdBQUcsR0FBRyxRQUFRLENBQUMsQ0FBQyxDQUFDLENBQUM7Z0JBQ3hCLElBQUksVUFBVSxHQUFHLElBQUksQ0FBQztnQkFDdEIsSUFBSSxHQUFHLEVBQUUsQ0FBQztvQkFDVCxNQUFNLGlCQUFpQixHQUFHLFFBQVEsQ0FBQyxHQUFHLENBQUMsQ0FBQztvQkFDeEMsSUFBSSxpQkFBaUIsRUFBRSxDQUFDO3dCQUN2QixVQUFVLEdBQUcsR0FBRyxHQUFHLElBQUksaUJBQWlCLEVBQUUsQ0FBQztvQkFDNUMsQ0FBQztnQkFDRixDQUFDO2dCQUVELE9BQU8sQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLENBQUM7WUFDMUIsQ0FBQztRQUNGLENBQUM7SUFDRixDQUFDLENBQUMsQ0FBQztJQUVILE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDckMsTUFBTSxRQUFRLEdBQUcsR0FBRyxRQUFRLElBQUksUUFBUSxDQUFDLEVBQUUsTUFBTSxDQUFDO0lBQ2xELE1BQU0sT0FBTyxHQUFHLEtBQUssQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxFQUFFLE1BQU0sQ0FBQyxDQUFDLFFBQVEsRUFBRSxFQUFFLFNBQVMsQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUV2RyxPQUFPLElBQUksSUFBSSxDQUFDO1FBQ2YsSUFBSSxFQUFFLFFBQVE7UUFDZCxRQUFRLEVBQUUsTUFBTSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUM7S0FDOUIsQ0FBQyxDQUFDO0FBQ0osQ0FBQztBQUVELFNBQVMsY0FBYyxDQUFDLEtBQWE7SUFDcEMsTUFBTSxNQUFNLEdBQWEsRUFBRSxDQUFDO0lBQzVCLEtBQUssSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxLQUFLLENBQUMsTUFBTSxFQUFFLENBQUMsRUFBRSxFQUFFLENBQUM7UUFDdkMsTUFBTSxFQUFFLEdBQUcsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ3BCLFFBQVEsRUFBRSxFQUFFLENBQUM7WUFDWixLQUFLLEdBQUc7Z0JBQ1AsTUFBTSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQztnQkFDcEIsTUFBTTtZQUNQLEtBQUssR0FBRztnQkFDUCxNQUFNLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDO2dCQUNwQixNQUFNO1lBQ1AsS0FBSyxHQUFHO2dCQUNQLE1BQU0sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUM7Z0JBQ3JCLE1BQU07WUFDUDtnQkFDQyxNQUFNLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDO1FBQ2xCLENBQUM7SUFDRixDQUFDO0lBQ0QsT0FBTyxNQUFNLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDO0FBQ3hCLENBQUM7QUFFRCxTQUFTLGNBQWMsQ0FBQyxLQUFhO0lBQ3BDLE9BQU8sS0FBSyxDQUFDLE9BQU8sQ0FBQyxPQUFPLEVBQUUsR0FBRyxDQUFDLENBQUMsT0FBTyxDQUFDLE9BQU8sRUFBRSxHQUFHLENBQUMsQ0FBQyxPQUFPLENBQUMsUUFBUSxFQUFFLEdBQUcsQ0FBQyxDQUFDO0FBQ2pGLENBQUMifQ== \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaTE4bi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImkxOG4udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOzs7QUFFaEcsNkJBQTZCO0FBQzdCLHlCQUF5QjtBQUV6QiwrQ0FBa0U7QUFDbEUsNkNBQTZDO0FBQzdDLDhCQUE4QjtBQUM5QixpQ0FBaUM7QUFDakMsNkJBQTZCO0FBQzdCLHNDQUFzQztBQUN0QywwQ0FBMEM7QUFDMUMsZ0RBQWdEO0FBQ2hELCtDQUFpSDtBQUVqSCxTQUFTLEdBQUcsQ0FBQyxPQUFZLEVBQUUsR0FBRyxJQUFXO0lBQ3hDLFFBQVEsQ0FBQyxVQUFVLENBQUMsS0FBSyxDQUFDLFFBQVEsQ0FBQyxFQUFFLE9BQU8sRUFBRSxHQUFHLElBQUksQ0FBQyxDQUFDO0FBQ3hELENBQUM7QUFZWSxRQUFBLGdCQUFnQixHQUFlO0lBQzNDLEVBQUUsRUFBRSxFQUFFLE9BQU8sRUFBRSxVQUFVLEVBQUUsS0FBSyxFQUFFLGFBQWEsRUFBRSxTQUFTLEVBQUU7SUFDNUQsRUFBRSxFQUFFLEVBQUUsT0FBTyxFQUFFLFVBQVUsRUFBRSxLQUFLLEVBQUUsYUFBYSxFQUFFLFNBQVMsRUFBRTtJQUM1RCxFQUFFLEVBQUUsRUFBRSxJQUFJLEVBQUUsVUFBVSxFQUFFLEtBQUssRUFBRTtJQUMvQixFQUFFLEVBQUUsRUFBRSxJQUFJLEVBQUUsVUFBVSxFQUFFLEtBQUssRUFBRTtJQUMvQixFQUFFLEVBQUUsRUFBRSxJQUFJLEVBQUUsVUFBVSxFQUFFLEtBQUssRUFBRTtJQUMvQixFQUFFLEVBQUUsRUFBRSxJQUFJLEVBQUUsVUFBVSxFQUFFLEtBQUssRUFBRTtJQUMvQixFQUFFLEVBQUUsRUFBRSxJQUFJLEVBQUUsVUFBVSxFQUFFLEtBQUssRUFBRTtJQUMvQixFQUFFLEVBQUUsRUFBRSxJQUFJLEVBQUUsVUFBVSxFQUFFLEtBQUssRUFBRTtJQUMvQixFQUFFLEVBQUUsRUFBRSxJQUFJLEVBQUUsVUFBVSxFQUFFLEtBQUss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 \ No newline at end of file diff --git a/code/build/lib/i18n.resources.json b/code/build/lib/i18n.resources.json index 303ac03be1e..2d03fbd5712 100644 --- a/code/build/lib/i18n.resources.json +++ b/code/build/lib/i18n.resources.json @@ -214,6 +214,10 @@ "name": "vs/workbench/contrib/tags", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/speech", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/surveys", "project": "vscode-workbench" diff --git a/code/build/lib/i18n.ts b/code/build/lib/i18n.ts index 8e543059605..6f931a18f27 100644 --- a/code/build/lib/i18n.ts +++ b/code/build/lib/i18n.ts @@ -9,7 +9,6 @@ import * as fs from 'fs'; import { map, merge, through, ThroughStream } from 'event-stream'; import * as jsonMerge from 'gulp-merge-json'; import * as File from 'vinyl'; -import * as Is from 'is'; import * as xml2js from 'xml2js'; import * as gulp from 'gulp'; import * as fancyLog from 'fancy-log'; @@ -69,7 +68,7 @@ interface LocalizeInfo { module LocalizeInfo { export function is(value: any): value is LocalizeInfo { const candidate = value as LocalizeInfo; - return Is.defined(candidate) && Is.string(candidate.key) && (Is.undef(candidate.comment) || (Is.array(candidate.comment) && candidate.comment.every(element => Is.string(element)))); + return candidate && typeof candidate.key === 'string' && (candidate.comment === undefined || (Array.isArray(candidate.comment) && candidate.comment.every(element => typeof element === 'string'))); } } @@ -81,14 +80,14 @@ interface BundledFormat { module BundledFormat { export function is(value: any): value is BundledFormat { - if (Is.undef(value)) { + if (value === undefined) { return false; } const candidate = value as BundledFormat; const length = Object.keys(value).length; - return length === 3 && Is.defined(candidate.keys) && Is.defined(candidate.messages) && Is.defined(candidate.bundles); + return length === 3 && !!candidate.keys && !!candidate.messages && !!candidate.bundles; } } @@ -183,7 +182,7 @@ export class XLF { const key = keys[i]; let realKey: string | undefined; let comment: string | undefined; - if (Is.string(key)) { + if (typeof key === 'string') { realKey = key; comment = undefined; } else if (LocalizeInfo.is(key)) { @@ -474,7 +473,7 @@ function processCoreBundleFormat(fileHeader: string, languages: Language[], json }); sortedLanguages.forEach(language => { const stats = statistics[language.id]; - if (Is.undef(stats)) { + if (!stats) { log(`\tNo translations found for language ${language.id}. Using default language instead.`); } }); diff --git a/code/build/lib/typings/is.d.ts b/code/build/lib/typings/is.d.ts deleted file mode 100644 index 66e7501c03f..00000000000 --- a/code/build/lib/typings/is.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -declare module 'is' { - function a(value: any, type: string): boolean; - function defined(value: any): boolean; - function undef(value: any): boolean; - function object(value: any): boolean; - function string(value: any): value is string; - function boolean(value: any): boolean; - function array(value: any): boolean; - function empty(value: Object | Array): boolean; - function equal | Function | Date>(value: T, other: T): boolean; -} \ No newline at end of file diff --git a/code/build/package.json b/code/build/package.json index 98d4b6578d1..ca6b448b393 100644 --- a/code/build/package.json +++ b/code/build/package.json @@ -11,7 +11,7 @@ "@types/byline": "^4.2.32", "@types/cssnano": "^4.0.0", "@types/debounce": "^1.0.0", - "@types/debug": "4.1.5", + "@types/debug": "^4.1.5", "@types/fancy-log": "^1.3.0", "@types/fs-extra": "^9.0.12", "@types/glob": "^7.1.1", @@ -29,13 +29,11 @@ "@types/mkdirp": "^1.0.1", "@types/mocha": "^9.1.1", "@types/node": "18.x", - "@types/p-limit": "^2.2.0", "@types/pump": "^1.0.1", "@types/rimraf": "^2.0.4", "@types/through": "^0.0.29", "@types/through2": "^2.0.36", "@types/tmp": "^0.2.1", - "@types/underscore": "^1.8.9", "@types/workerpool": "^6.4.0", "@types/xml2js": "0.0.33", "@vscode/iconv-lite-umd": "0.7.0", @@ -53,12 +51,12 @@ "mime": "^1.4.1", "mkdirp": "^1.0.4", "node-fetch": "2", - "p-limit": "^3.1.0", "source-map": "0.6.1", "ternary-stream": "^3.0.0", "through2": "^4.0.2", "tmp": "^0.2.1", - "vscode-universal-bundler": "^0.0.2" + "vscode-universal-bundler": "^0.0.2", + "workerpool": "^6.4.0" }, "scripts": { "compile": "../node_modules/.bin/tsc -p tsconfig.build.json", @@ -66,11 +64,8 @@ "npmCheckJs": "../node_modules/.bin/tsc --noEmit" }, "optionalDependencies": { - "tree-sitter": "https://github.com/joaomoreno/node-tree-sitter/releases/download/v0.20.0/tree-sitter-0.20.0.tgz", - "tree-sitter-typescript": "^0.20.1", + "tree-sitter": "^0.20.5", + "tree-sitter-typescript": "^0.20.3", "vscode-gulp-watch": "^5.0.3" - }, - "dependencies": { - "workerpool": "^6.4.0" } } diff --git a/code/build/yarn.lock b/code/build/yarn.lock index 0b3f151633b..f3dd7803415 100644 --- a/code/build/yarn.lock +++ b/code/build/yarn.lock @@ -400,10 +400,12 @@ resolved "https://registry.yarnpkg.com/@types/debounce/-/debounce-1.0.0.tgz#417560200331e1bb84d72da85391102c2fcd61b7" integrity sha1-QXVgIAMx4buE1y2oU5EQLC/NYbc= -"@types/debug@4.1.5": - version "4.1.5" - resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd" - integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ== +"@types/debug@^4.1.5": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.9.tgz#906996938bc672aaf2fb8c0d3733ae1dda05b005" + integrity sha512-8Hz50m2eoS56ldRlepxSBa6PWEVCtzUo/92HgLc2qTMnotJNIm7xP+UZhyWoYsyOdd5dxZ+NZLb24rsKyFs2ow== + dependencies: + "@types/ms" "*" "@types/events@*": version "1.2.0" @@ -533,6 +535,11 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.1.1.tgz#e7c4f1001eefa4b8afbd1eee27a237fee3bf29c4" integrity sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw== +"@types/ms@*": + version "0.7.32" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.32.tgz#f6cd08939ae3ad886fcc92ef7f0109dacddf61ab" + integrity sha512-xPSg0jm4mqgEkNhowKgZFBNtwoEwF6gJ4Dhww+GFpm3IgtNseHQZ5IqdNwnquZEoANxyDAKDRAdVo4Z72VvD/g== + "@types/node-fetch@^2.5.0": version "2.5.8" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.8.tgz#e199c835d234c7eb0846f6618012e558544ee2fb" @@ -551,13 +558,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.13.tgz#f64277c341150c979e42b00e4ac289290c9df469" integrity sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q== -"@types/p-limit@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@types/p-limit/-/p-limit-2.2.0.tgz#94a608e9b258a6c6156a13d1a14fd720dba70b97" - integrity sha512-fGFbybl1r0oE9mqgfc2EHHUin9ZL5rbQIexWI6jYRU1ADVn4I3LHzT+g/kpPpZsfp8PB94CQ655pfAjNF8LP6A== - dependencies: - p-limit "*" - "@types/pump@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/pump/-/pump-1.0.1.tgz#ae8157cefef04d1a4d24c1cc91d403c2f5da5cd0" @@ -599,11 +599,6 @@ dependencies: "@types/node" "*" -"@types/underscore@^1.8.9": - version "1.8.9" - resolved "https://registry.yarnpkg.com/@types/underscore/-/underscore-1.8.9.tgz#fef41f800cd23db1b4f262ddefe49cd952d82323" - integrity sha512-vfzZGgZKRFy7KEWcBGfIFk+h6B+thDCLfkD1exMBMRlUsx2icA+J6y4kAbZs/TjSTeY1duw89QUU133TSzr60Q== - "@types/undertaker-registry@*": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/undertaker-registry/-/undertaker-registry-1.0.1.tgz#4306d4a03d7acedb974b66530832b90729e1d1da" @@ -711,16 +706,6 @@ ansi-gray@^0.1.1: dependencies: ansi-wrap "0.1.0" -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -748,19 +733,6 @@ anymatch@^3.1.1, anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" -aproba@^1.0.3: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== - -are-we-there-yet@~1.1.2: - version "1.1.7" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz#b15474a932adab4ff8a50d9adfa7e4e926f21146" - integrity sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g== - dependencies: - delegates "^1.0.0" - readable-stream "^2.0.6" - argparse@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" @@ -1030,11 +1002,6 @@ cloneable-readable@^1.0.0: process-nextick-args "^2.0.0" readable-stream "^2.3.5" -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= - color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -1121,11 +1088,6 @@ config-chain@^1.1.11: ini "^1.3.4" proto-list "~1.2.1" -console-control-strings@^1.0.0, console-control-strings@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= - core-js@^3.6.5: version "3.15.2" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.15.2.tgz#740660d2ff55ef34ce664d7e2455119c5bdd3d61" @@ -1161,10 +1123,10 @@ css-what@^6.1.0: resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== -debug@4, debug@^4.1.0, debug@^4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" - integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" @@ -1175,13 +1137,6 @@ debug@^2.6.8: dependencies: ms "2.0.0" -debug@^4.1.1, debug@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" - integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== - dependencies: - ms "2.1.2" - decompress-response@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" @@ -1189,13 +1144,6 @@ decompress-response@^3.3.0: dependencies: mimic-response "^1.0.0" -decompress-response@^4.2.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986" - integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw== - dependencies: - mimic-response "^2.0.0" - decompress-response@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" @@ -1230,16 +1178,6 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= - -detect-libc@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= - detect-libc@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" @@ -1324,11 +1262,6 @@ electron-osx-sign@^0.4.16: minimist "^1.2.0" plist "^3.0.1" -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - encodeurl@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -1526,20 +1459,6 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - get-intrinsic@^1.0.2: version "1.1.3" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385" @@ -1699,11 +1618,6 @@ has-symbols@^1.0.3: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== -has-unicode@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= - has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -1743,9 +1657,9 @@ http-proxy-agent@^4.0.1: debug "4" https-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" - integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== dependencies: agent-base "6" debug "4" @@ -1797,18 +1711,6 @@ is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= - dependencies: - number-is-nan "^1.0.0" - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - is-glob@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" @@ -2078,11 +1980,6 @@ mimic-response@^1.0.0, mimic-response@^1.0.1: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== -mimic-response@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" - integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== - mimic-response@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" @@ -2142,18 +2039,16 @@ nan@^2.14.0: resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== +nan@^2.17.0: + version "2.18.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.18.0.tgz#26a6faae7ffbeb293a39660e88a76b82e30b7554" + integrity sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w== + napi-build-utils@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== -node-abi@^2.21.0: - version "2.30.1" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.30.1.tgz#c437d4b1fe0e285aaf290d45b45d4d7afedac4cf" - integrity sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w== - dependencies: - semver "^5.4.1" - node-abi@^3.3.0: version "3.30.0" resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.30.0.tgz#d84687ad5d24ca81cdfa912a36f2c5c19b137359" @@ -2203,16 +2098,6 @@ npm-conf@^1.1.3: config-chain "^1.1.11" pify "^3.0.0" -npmlog@^4.0.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" - nth-check@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" @@ -2220,12 +2105,7 @@ nth-check@^2.0.1: dependencies: boolbase "^1.0.0" -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= - -object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -2261,13 +2141,6 @@ p-cancelable@^1.0.0: resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== -p-limit@*, p-limit@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - parse-node-version@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parse-node-version/-/parse-node-version-1.0.1.tgz#e2b5dbede00e7fa9bc363607f53327e8b073189b" @@ -2357,26 +2230,7 @@ plugin-error@1.0.1, plugin-error@^1.0.1: source-map "^0.6.1" supports-color "^6.1.0" -prebuild-install@^6.0.1: - version "6.1.4" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-6.1.4.tgz#ae3c0142ad611d58570b89af4986088a4937e00f" - integrity sha512-Z4vpywnK1lBg+zdPCVCsKq0xO66eEV9rWo2zrROGGiRS4JtueBOdlB1FnY8lcy7JsUud/Q3ijUxyWN26Ika0vQ== - dependencies: - detect-libc "^1.0.3" - expand-template "^2.0.3" - github-from-package "0.0.0" - minimist "^1.2.3" - mkdirp-classic "^0.5.3" - napi-build-utils "^1.0.1" - node-abi "^2.21.0" - npmlog "^4.0.1" - pump "^3.0.0" - rc "^1.2.7" - simple-get "^3.0.3" - tar-fs "^2.0.0" - tunnel-agent "^0.6.0" - -prebuild-install@^7.0.1: +prebuild-install@^7.0.1, prebuild-install@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45" integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw== @@ -2465,7 +2319,7 @@ read@^1.0.7: string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.3.5: +readable-stream@^2.0.2, readable-stream@^2.3.5: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -2546,7 +2400,7 @@ semver-compare@^1.0.0: resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= -semver@^5.1.0, semver@^5.4.1: +semver@^5.1.0: version "5.7.2" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== @@ -2570,11 +2424,6 @@ serialize-error@^7.0.1: dependencies: type-fest "^0.13.1" -set-blocking@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= - shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -2596,25 +2445,11 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" -signal-exit@^3.0.0: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - simple-concat@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== -simple-get@^3.0.3: - version "3.1.1" - resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.1.tgz#cc7ba77cfbe761036fbfce3d021af25fc5584d55" - integrity sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA== - dependencies: - decompress-response "^4.2.0" - once "^1.3.1" - simple-concat "^1.0.0" - simple-get@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" @@ -2644,24 +2479,6 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== -string-width@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -"string-width@^1.0.2 || 2 || 3 || 4": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -2676,20 +2493,6 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= - dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-bom-buf@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-bom-buf/-/strip-bom-buf-1.0.0.tgz#1cb45aaf57530f4caf86c7f75179d2c9a51dd572" @@ -2825,19 +2628,20 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= -tree-sitter-typescript@^0.20.1: - version "0.20.1" - resolved "https://registry.yarnpkg.com/tree-sitter-typescript/-/tree-sitter-typescript-0.20.1.tgz#6b338a1414f5ed13cc39e60275ddeaa0f25870a9" - integrity sha512-wqpnhdVYX26ATNXeZtprib4+mF2GlYQB1cjRPibYGxDRiugx5OfjWwLE4qPPxEGdp2ZLSmZVesGUjLWzfKo6rA== +tree-sitter-typescript@^0.20.3: + version "0.20.3" + resolved "https://registry.yarnpkg.com/tree-sitter-typescript/-/tree-sitter-typescript-0.20.3.tgz#454560314c419f5364cd4585a48d675e44f55edc" + integrity sha512-5+RZ9G3/VOxxSzyniVc5dfNhfan1eOxQvUdTgXhpsGIYlmSW3HwIuPEJ7r65FWH2WnJWirOu11Pm0usmkx2JOg== dependencies: nan "^2.14.0" -"tree-sitter@https://github.com/joaomoreno/node-tree-sitter/releases/download/v0.20.0/tree-sitter-0.20.0.tgz": - version "0.20.0" - resolved "https://github.com/joaomoreno/node-tree-sitter/releases/download/v0.20.0/tree-sitter-0.20.0.tgz#5679001aaa698c7cddc38ea23b49b9361b69215f" +tree-sitter@^0.20.5: + version "0.20.5" + resolved "https://registry.yarnpkg.com/tree-sitter/-/tree-sitter-0.20.5.tgz#554741ee06b984824dd5082353aa2a28bcefa271" + integrity sha512-xjxkKCKV7F2F5HWmyRE4bosoxkbxe9lYvFRc/nzmtHNqFNTwYwh0oWVVEt0VnbupZHMirEQW7vDx8ddJn72tjg== dependencies: - nan "^2.14.0" - prebuild-install "^6.0.1" + nan "^2.17.0" + prebuild-install "^7.1.1" tslib@^1.10.0: version "1.14.1" @@ -2998,17 +2802,10 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -wide-align@^1.1.0: - version "1.1.5" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" - integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== - dependencies: - string-width "^1.0.2 || 2 || 3 || 4" - workerpool@^6.4.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.4.0.tgz#f8d5cfb45fde32fa3b7af72ad617c3369567a462" - integrity sha512-i3KR1mQMNwY2wx20ozq2EjISGtQWDIfV56We+yGJ5yDs8jTwQiLLaqHlkBHITlCuJnYlVRmXegxFxZg7gqI++A== + version "6.5.0" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.0.tgz#f8474762c5e2f81646994aa113685f6c424a2416" + integrity sha512-r64Ea3glXY2RVzMeNxB+4J+0YHAVzUdV4cM5nHi4BBC2LvnO1pWFAIYKYuGcPElbg1/7eEiaPtZ/jzCjIUuGBg== wrappy@1: version "1.0.2" @@ -3052,8 +2849,3 @@ yazl@^2.2.2: integrity sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw== dependencies: buffer-crc32 "~0.2.3" - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/code/cli/Cargo.lock b/code/cli/Cargo.lock index 1e75e0541fa..4152c9b2b67 100644 --- a/code/cli/Cargo.lock +++ b/code/cli/Cargo.lock @@ -189,12 +189,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "base64" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" - [[package]] name = "base64" version = "0.21.2" @@ -342,7 +336,7 @@ name = "code-cli" version = "0.1.0" dependencies = [ "async-trait", - "base64 0.21.2", + "base64", "bytes", "cfg-if", "chrono", @@ -382,9 +376,9 @@ dependencies = [ "tokio-util", "tunnels", "url", - "uuid 1.3.3", + "uuid", "winapi", - "winreg 0.50.0", + "winreg", "zbus", "zip", ] @@ -1320,9 +1314,9 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" dependencies = [ "lazy_static", "libc", @@ -1831,11 +1825,11 @@ checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" [[package]] name = "reqwest" -version = "0.11.18" +version = "0.11.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" +checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" dependencies = [ - "base64 0.21.2", + "base64", "bytes", "encoding_rs", "futures-core", @@ -1856,6 +1850,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "system-configuration", "tokio", "tokio-native-tls", "tokio-util", @@ -1865,7 +1860,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "winreg 0.10.1", + "winreg", ] [[package]] @@ -2099,17 +2094,6 @@ dependencies = [ "serde", ] -[[package]] -name = "sha-1" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - [[package]] name = "sha1" version = "0.10.5" @@ -2242,6 +2226,27 @@ dependencies = [ "winapi", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tar" version = "0.4.38" @@ -2359,9 +2364,9 @@ dependencies = [ [[package]] name = "tokio-native-tls" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", "tokio", @@ -2380,9 +2385,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.17.2" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" +checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" dependencies = [ "futures-util", "log", @@ -2462,19 +2467,19 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] name = "tungstenite" -version = "0.17.3" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" dependencies = [ - "base64 0.13.0", "byteorder", "bytes", + "data-encoding", "http", "httparse", "log", "native-tls", "rand 0.8.5", - "sha-1", + "sha1", "thiserror", "url", "utf-8", @@ -2483,7 +2488,7 @@ dependencies = [ [[package]] name = "tunnels" version = "0.1.0" -source = "git+https://github.com/microsoft/dev-tunnels?rev=3141ad7be00e18c4231f7c4fb6c11f9219ac49af#3141ad7be00e18c4231f7c4fb6c11f9219ac49af" +source = "git+https://github.com/microsoft/dev-tunnels?rev=97233d20448e1c3cb0e0fd9114acf68c7e5c0249#97233d20448e1c3cb0e0fd9114acf68c7e5c0249" dependencies = [ "async-trait", "chrono", @@ -2501,7 +2506,7 @@ dependencies = [ "tokio-util", "tungstenite", "url", - "uuid 0.8.2", + "uuid", ] [[package]] @@ -2584,18 +2589,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "0.8.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" -dependencies = [ - "getrandom 0.2.7", -] - -[[package]] -name = "uuid" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" +checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" dependencies = [ "getrandom 0.2.7", "serde", @@ -2709,9 +2705,9 @@ checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] name = "wasm-streams" -version = "0.2.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bbae3363c08332cadccd13b67db371814cd214c2524020932f0804b8cf7c078" +checksum = "b4609d447824375f43e1ffbc051b50ad8f4b3ae8219680c94452ea05eb240ac7" dependencies = [ "futures-util", "js-sys", @@ -2963,15 +2959,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "winreg" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" -dependencies = [ - "winapi", -] - [[package]] name = "winreg" version = "0.50.0" diff --git a/code/cli/Cargo.toml b/code/cli/Cargo.toml index 03a6f573952..f6de2a2c616 100644 --- a/code/cli/Cargo.toml +++ b/code/cli/Cargo.toml @@ -15,7 +15,7 @@ name = "code" futures = "0.3.28" clap = { version = "4.3.0", features = ["derive", "env"] } open = "4.1.0" -reqwest = { version = "0.11.18", default-features = false, features = ["json", "stream", "native-tls"] } +reqwest = { version = "0.11.22", default-features = false, features = ["json", "stream", "native-tls"] } tokio = { version = "1.28.2", features = ["full"] } tokio-util = { version = "0.7.8", features = ["compat", "codec"] } flate2 = { version = "1.0.26", default-features = false, features = ["zlib"] } @@ -26,7 +26,7 @@ sysinfo = { version = "0.29.0", default-features = false } serde = { version = "1.0.163", features = ["derive"] } serde_json = "1.0.96" rmp-serde = "1.1.1" -uuid = { version = "1.3.3", features = ["serde", "v4"] } +uuid = { version = "1.4", features = ["serde", "v4"] } dirs = "5.0.1" rand = "0.8.5" opentelemetry = { version = "0.19.0", features = ["rt-tokio"] } @@ -34,7 +34,7 @@ serde_bytes = "0.11.9" chrono = { version = "0.4.26", features = ["serde", "std", "clock"], default-features = false } gethostname = "0.4.3" libc = "0.2.144" -tunnels = { git = "https://github.com/microsoft/dev-tunnels", rev = "3141ad7be00e18c4231f7c4fb6c11f9219ac49af", default-features = false, features = ["connections"] } +tunnels = { git = "https://github.com/microsoft/dev-tunnels", rev = "97233d20448e1c3cb0e0fd9114acf68c7e5c0249", default-features = false, features = ["connections"] } keyring = { version = "2.0.3", default-features = false, features = ["linux-secret-service-rt-tokio-crypto-openssl"] } dialoguer = "0.10.4" hyper = { version = "0.14.26", features = ["server", "http1", "runtime"] } diff --git a/code/cli/src/auth.rs b/code/cli/src/auth.rs index 5a62753fae5..0214b335247 100644 --- a/code/cli/src/auth.rs +++ b/code/cli/src/auth.rs @@ -622,11 +622,11 @@ impl Auth { return Ok(StoredCredential::from_response(body, provider)); } - return Err(Auth::handle_grant_error( + Err(Auth::handle_grant_error( provider.grant_uri(), status_code, body, - )); + )) } /// GH doesn't have a refresh token, but does limit to the 10 most recently @@ -772,7 +772,7 @@ impl Auth { error!(this.log, "failed to keep token alive: {:?}", e); return Err(e.into()); } - Err(e) if matches!(e, AnyError::RefreshTokenNotAvailableError(_)) => { + Err(AnyError::RefreshTokenNotAvailableError(_)) => { return Ok(()); } Err(e) => { diff --git a/code/cli/src/bin/code/legacy_args.rs b/code/cli/src/bin/code/legacy_args.rs index 808b4aa30a8..6a533426608 100644 --- a/code/cli/src/bin/code/legacy_args.rs +++ b/code/cli/src/bin/code/legacy_args.rs @@ -118,7 +118,7 @@ mod tests { "themes", "--show-versions", ]; - let cli = try_parse_legacy(args.into_iter()).unwrap(); + let cli = try_parse_legacy(args).unwrap(); if let Some(Commands::Extension(extension_args)) = cli.subcommand { if let ExtensionSubcommand::List(list_args) = extension_args.subcommand { @@ -145,7 +145,7 @@ mod tests { "--pre-release", "--force", ]; - let cli = try_parse_legacy(args.into_iter()).unwrap(); + let cli = try_parse_legacy(args).unwrap(); if let Some(Commands::Extension(extension_args)) = cli.subcommand { if let ExtensionSubcommand::Install(install_args) = extension_args.subcommand { @@ -169,7 +169,7 @@ mod tests { #[test] fn test_parses_uninstall_extension() { let args = vec!["code", "--uninstall-extension", "connor4312.codesong"]; - let cli = try_parse_legacy(args.into_iter()).unwrap(); + let cli = try_parse_legacy(args).unwrap(); if let Some(Commands::Extension(extension_args)) = cli.subcommand { if let ExtensionSubcommand::Uninstall(uninstall_args) = extension_args.subcommand { @@ -196,7 +196,7 @@ mod tests { "--extensions-dir", "bar", ]; - let cli = try_parse_legacy(args.into_iter()).unwrap(); + let cli = try_parse_legacy(args).unwrap(); if let Some(Commands::Extension(extension_args)) = cli.subcommand { assert_eq!( @@ -223,7 +223,7 @@ mod tests { #[test] fn test_status() { let args = vec!["code", "--status"]; - let cli = try_parse_legacy(args.into_iter()).unwrap(); + let cli = try_parse_legacy(args).unwrap(); if let Some(Commands::Status) = cli.subcommand { // no-op diff --git a/code/cli/src/commands/serve_web.rs b/code/cli/src/commands/serve_web.rs index 7f200d4341f..959763a431d 100644 --- a/code/cli/src/commands/serve_web.rs +++ b/code/cli/src/commands/serve_web.rs @@ -15,7 +15,6 @@ use hyper::service::{make_service_fn, service_fn}; use hyper::{Body, Request, Response, Server}; use tokio::io::{AsyncBufReadExt, BufReader}; use tokio::pin; -use tokio::process::Command; use crate::async_pipe::{ get_socket_name, get_socket_rw_stream, listen_socket_rw_stream, AsyncPipe, @@ -29,6 +28,7 @@ use crate::tunnels::shutdown_signal::ShutdownRequest; use crate::update_service::{ unzip_downloaded_release, Platform, Release, TargetKind, UpdateService, }; +use crate::util::command::new_script_command; use crate::util::errors::AnyError; use crate::util::http::{self, ReqwestSimpleHttp}; use crate::util::io::SilentCopyProgress; @@ -679,17 +679,7 @@ impl ConnectionManager { let socket_path = get_socket_name(); - #[cfg(not(windows))] - let mut cmd = Command::new(&executable); - #[cfg(windows)] - let mut cmd = { - let mut cmd = Command::new("cmd"); - cmd.arg("/Q"); - cmd.arg("/C"); - cmd.arg(&executable); - cmd - }; - + let mut cmd = new_script_command(&executable); cmd.stdin(std::process::Stdio::null()); cmd.stderr(std::process::Stdio::piped()); cmd.stdout(std::process::Stdio::piped()); diff --git a/code/cli/src/commands/tunnels.rs b/code/cli/src/commands/tunnels.rs index c0b3f3f01f9..8c4270ad453 100644 --- a/code/cli/src/commands/tunnels.rs +++ b/code/cli/src/commands/tunnels.rs @@ -51,6 +51,7 @@ use crate::{ }, util::{ app_lock::AppMutex, + command::new_std_command, errors::{wrap, AnyError, CodeError}, prereqs::PreReqChecker, }, @@ -604,7 +605,7 @@ async fn serve_with_csa( // reuse current args, but specify no-forward since tunnels will // already be running in this process, and we cannot do a login let args = std::env::args().skip(1).collect::>(); - let exit = std::process::Command::new(current_exe) + let exit = new_std_command(current_exe) .args(args) .spawn() .map_err(|e| wrap(e, "error respawning after update"))? diff --git a/code/cli/src/desktop/version_manager.rs b/code/cli/src/desktop/version_manager.rs index bb0f1011ab6..f2a093b5c77 100644 --- a/code/cli/src/desktop/version_manager.rs +++ b/code/cli/src/desktop/version_manager.rs @@ -18,7 +18,10 @@ use crate::{ log, state::{LauncherPaths, PersistedState}, update_service::Platform, - util::errors::{AnyError, InvalidRequestedVersion}, + util::{ + command::new_std_command, + errors::{AnyError, InvalidRequestedVersion}, + }, }; /// Parsed instance that a user can request. @@ -110,7 +113,7 @@ impl CodeVersionManager { // Check whether the user is supplying a path to the CLI directly (e.g. #164622) if let Ok(true) = path.metadata().map(|m| m.is_file()) { - let result = std::process::Command::new(path) + let result = new_std_command(path) .args(["--version"]) .output() .map(|o| o.status.success()); @@ -270,7 +273,7 @@ fn detect_installed_program(log: &log::Logger) -> io::Result> { // the `Location:` line for the path. info!(log, "Searching for installations on your machine, this is done once and will take about 10 seconds..."); - let stdout = std::process::Command::new("system_profiler") + let stdout = new_std_command("system_profiler") .args(["SPApplicationsDataType", "-detailLevel", "mini"]) .output()? .stdout; diff --git a/code/cli/src/rpc.rs b/code/cli/src/rpc.rs index a9a66153735..0972ad05475 100644 --- a/code/cli/src/rpc.rs +++ b/code/cli/src/rpc.rs @@ -531,6 +531,7 @@ impl RpcDispatcher { struct StreamRec { write: Option>, q: Vec>, + ended: bool, } #[derive(Clone, Default)] @@ -540,13 +541,24 @@ struct Streams { impl Streams { pub async fn remove(&self, id: u32) { - let stream = self.map.lock().unwrap().remove(&id); - if let Some(s) = stream { - // if there's no 'write' right now, it'll shut down in the write_loop - if let Some(mut w) = s.write { - let _ = w.shutdown().await; + let mut remove = None; + + { + let mut map = self.map.lock().unwrap(); + if let Some(s) = map.get_mut(&id) { + if let Some(w) = s.write.take() { + map.remove(&id); + remove = Some(w); + } else { + s.ended = true; // will shut down in write loop + } } } + + // do this outside of the sync lock: + if let Some(mut w) = remove { + let _ = w.shutdown().await; + } } pub fn write(&self, id: u32, buf: Vec) { @@ -566,6 +578,7 @@ impl Streams { StreamRec { write: Some(stream), q: Vec::new(), + ended: false, }, ); } @@ -595,8 +608,13 @@ async fn write_loop( }; if stream_rec.q.is_empty() { - stream_rec.write = Some(w); - return; + if stream_rec.ended { + lock.remove(&id); + break; + } else { + stream_rec.write = Some(w); + return; + } } std::mem::swap(&mut stream_rec.q, &mut items_vec); @@ -691,3 +709,46 @@ pub enum MaybeSync { Future(BoxFuture<'static, Option>>), Sync(Option>), } + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_remove() { + let streams = Streams::default(); + let (writer, mut reader) = tokio::io::duplex(1024); + streams.insert(1, tokio::io::split(writer).1); + streams.remove(1).await; + + assert!(streams.map.lock().unwrap().get(&1).is_none()); + let mut buffer = Vec::new(); + assert_eq!(reader.read_to_end(&mut buffer).await.unwrap(), 0); + } + + #[tokio::test] + async fn test_write() { + let streams = Streams::default(); + let (writer, mut reader) = tokio::io::duplex(1024); + streams.insert(1, tokio::io::split(writer).1); + streams.write(1, vec![1, 2, 3]); + + let mut buffer = [0; 3]; + assert_eq!(reader.read_exact(&mut buffer).await.unwrap(), 3); + assert_eq!(buffer, [1, 2, 3]); + } + + #[tokio::test] + async fn test_write_with_immediate_end() { + let streams = Streams::default(); + let (writer, mut reader) = tokio::io::duplex(1); + streams.insert(1, tokio::io::split(writer).1); + streams.write(1, vec![1, 2, 3]); // spawn write loop + streams.write(1, vec![4, 5, 6]); // enqueued while writing + streams.remove(1).await; // end stream + + let mut buffer = Vec::new(); + assert_eq!(reader.read_to_end(&mut buffer).await.unwrap(), 6); + assert_eq!(buffer, vec![1, 2, 3, 4, 5, 6]); + } +} diff --git a/code/cli/src/self_update.rs b/code/cli/src/self_update.rs index bb5d2d2249b..936c6627ac0 100644 --- a/code/cli/src/self_update.rs +++ b/code/cli/src/self_update.rs @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -use std::{fs, path::Path, process::Command}; +use std::{fs, path::Path}; use tempfile::tempdir; use crate::{ @@ -11,6 +11,7 @@ use crate::{ options::Quality, update_service::{unzip_downloaded_release, Platform, Release, TargetKind, UpdateService}, util::{ + command::new_std_command, errors::{wrap, AnyError, CodeError, CorruptDownload}, http, io::{ReportCopyProgress, SilentCopyProgress}, @@ -118,7 +119,7 @@ impl<'a> SelfUpdate<'a> { } fn validate_cli_is_good(exe_path: &Path) -> Result<(), AnyError> { - let o = Command::new(exe_path) + let o = new_std_command(exe_path) .args(["--version"]) .output() .map_err(|e| CorruptDownload(format!("could not execute new binary, aborting: {}", e)))?; diff --git a/code/cli/src/tunnels/code_server.rs b/code/cli/src/tunnels/code_server.rs index 16655533754..e17e32f8840 100644 --- a/code/cli/src/tunnels/code_server.rs +++ b/code/cli/src/tunnels/code_server.rs @@ -14,7 +14,9 @@ use crate::tunnels::paths::{get_server_folder_name, SERVER_FOLDER_NAME}; use crate::update_service::{ unzip_downloaded_release, Platform, Release, TargetKind, UpdateService, }; -use crate::util::command::{capture_command, capture_command_and_check_status, kill_tree}; +use crate::util::command::{ + capture_command, capture_command_and_check_status, kill_tree, new_script_command, +}; use crate::util::errors::{wrap, AnyError, CodeError, ExtensionInstallFailed, WrappedError}; use crate::util::http::{self, BoxedHttp}; use crate::util::io::SilentCopyProgress; @@ -587,17 +589,7 @@ impl<'a> ServerBuilder<'a> { } fn get_base_command(&self) -> Command { - #[cfg(not(windows))] - let mut cmd = Command::new(&self.server_paths.executable); - #[cfg(windows)] - let mut cmd = { - let mut cmd = Command::new("cmd"); - cmd.arg("/Q"); - cmd.arg("/C"); - cmd.arg(&self.server_paths.executable); - cmd - }; - + let mut cmd = new_script_command(&self.server_paths.executable); cmd.stdin(std::process::Stdio::null()) .args(self.server_params.code_server_args.command_arguments()); cmd diff --git a/code/cli/src/tunnels/control_server.rs b/code/cli/src/tunnels/control_server.rs index a344cc5d560..df2c8b2b820 100644 --- a/code/cli/src/tunnels/control_server.rs +++ b/code/cli/src/tunnels/control_server.rs @@ -12,6 +12,7 @@ use crate::state::LauncherPaths; use crate::tunnels::protocol::{HttpRequestParams, METHOD_CHALLENGE_ISSUE}; use crate::tunnels::socket_signal::CloseReason; use crate::update_service::{Platform, Release, TargetKind, UpdateService}; +use crate::util::command::new_tokio_command; use crate::util::errors::{ wrap, AnyError, CodeError, MismatchedLaunchModeError, NoAttachedServerError, }; @@ -1019,7 +1020,7 @@ where }; } - let mut p = tokio::process::Command::new(¶ms.command); + let mut p = new_tokio_command(¶ms.command); p.args(¶ms.args); p.envs(¶ms.env); p.stdin(pipe_if!(stdin.is_some())); @@ -1060,7 +1061,7 @@ async fn handle_spawn_cli( "requested to spawn cli {} with args {:?}", params.command, params.args ); - let mut p = tokio::process::Command::new(¶ms.command); + let mut p = new_tokio_command(¶ms.command); p.args(¶ms.args); // CLI args to spawn a server; contracted with clients that they should _not_ provide these. diff --git a/code/cli/src/tunnels/dev_tunnels.rs b/code/cli/src/tunnels/dev_tunnels.rs index 676e77efeba..98e09987cbe 100644 --- a/code/cli/src/tunnels/dev_tunnels.rs +++ b/code/cli/src/tunnels/dev_tunnels.rs @@ -1190,6 +1190,7 @@ fn privacy_to_tunnel_acl(privacy: PortPrivacy) -> TunnelAccessControl { is_deny: false, is_inverse: false, organization: None, + expiration: None, subjects: vec![], scopes: vec![TUNNEL_ACCESS_SCOPES_CONNECT.to_string()], }); diff --git a/code/cli/src/tunnels/nosleep_windows.rs b/code/cli/src/tunnels/nosleep_windows.rs index ed8d3f7a434..e0070221032 100644 --- a/code/cli/src/tunnels/nosleep_windows.rs +++ b/code/cli/src/tunnels/nosleep_windows.rs @@ -24,11 +24,11 @@ struct Request(*mut c_void); impl Request { pub fn new() -> io::Result { - let mut reason: Vec = TUNNEL_ACTIVITY_NAME.encode_utf16().collect(); + let mut reason: Vec = TUNNEL_ACTIVITY_NAME.encode_utf16().chain([0u16]).collect(); let mut context = REASON_CONTEXT { Version: POWER_REQUEST_CONTEXT_VERSION, Flags: POWER_REQUEST_CONTEXT_SIMPLE_STRING, - ..Default::default() + Reason: unsafe { std::mem::zeroed() }, }; unsafe { *context.Reason.SimpleReasonString_mut() = reason.as_mut_ptr() }; diff --git a/code/cli/src/tunnels/service_windows.rs b/code/cli/src/tunnels/service_windows.rs index 3d2dc9f0c55..395a707f351 100644 --- a/code/cli/src/tunnels/service_windows.rs +++ b/code/cli/src/tunnels/service_windows.rs @@ -6,13 +6,11 @@ use async_trait::async_trait; use shell_escape::windows::escape as shell_escape; use std::os::windows::process::CommandExt; -use std::{ - path::PathBuf, - process::{Command, Stdio}, -}; +use std::{path::PathBuf, process::Stdio}; use winapi::um::winbase::{CREATE_NEW_PROCESS_GROUP, DETACHED_PROCESS}; use winreg::{enums::HKEY_CURRENT_USER, RegKey}; +use crate::util::command::new_std_command; use crate::{ constants::TUNNEL_ACTIVITY_NAME, log, @@ -54,7 +52,7 @@ impl CliServiceManager for WindowsService { let key = WindowsService::open_key()?; let mut reg_str = String::new(); - let mut cmd = Command::new(&exe); + let mut cmd = new_std_command(&exe); reg_str.push_str(shell_escape(exe.to_string_lossy()).as_ref()); let mut add_arg = |arg: &str| { @@ -102,7 +100,7 @@ impl CliServiceManager for WindowsService { // Start as a hidden subprocess to avoid showing cmd.exe on startup. // Fixes https://github.com/microsoft/vscode/issues/184058 // I also tried the winapi ShowWindow, but that didn't yield fruit. - Command::new(std::env::current_exe().unwrap()) + new_std_command(std::env::current_exe().unwrap()) .args(std::env::args().skip(1)) .env(DID_LAUNCH_AS_HIDDEN_PROCESS, "1") .stderr(Stdio::null()) diff --git a/code/cli/src/tunnels/wsl_detect.rs b/code/cli/src/tunnels/wsl_detect.rs index ec386feb26e..0701a89033a 100644 --- a/code/cli/src/tunnels/wsl_detect.rs +++ b/code/cli/src/tunnels/wsl_detect.rs @@ -12,7 +12,9 @@ pub fn is_wsl_installed(_log: &log::Logger) -> bool { #[cfg(windows)] pub fn is_wsl_installed(log: &log::Logger) -> bool { - use std::{path::PathBuf, process::Command}; + use std::path::PathBuf; + + use crate::util::command::new_std_command; let system32 = { let sys_root = match std::env::var("SystemRoot") { @@ -37,7 +39,7 @@ pub fn is_wsl_installed(log: &log::Logger) -> bool { // Windows builds >= 22000 let maybe_wsl = system32.join("wsl.exe"); if maybe_wsl.exists() { - if let Ok(s) = Command::new(maybe_wsl).arg("--status").output() { + if let Ok(s) = new_std_command(maybe_wsl).arg("--status").output() { if s.status.success() { trace!(log, "wsl availability detected via subprocess"); return true; diff --git a/code/cli/src/util/command.rs b/code/cli/src/util/command.rs index ad1f3a1d13e..fb9fb4f91d5 100644 --- a/code/cli/src/util/command.rs +++ b/code/cli/src/util/command.rs @@ -57,7 +57,7 @@ where I: IntoIterator, S: AsRef, { - Command::new(&command_str) + new_tokio_command(&command_str) .args(args) .stdin(Stdio::null()) .stdout(Stdio::piped()) @@ -70,15 +70,64 @@ where }) } +/// Makes a new Command, setting flags to avoid extra windows on win32 +#[cfg(windows)] +pub fn new_tokio_command(exe: impl AsRef) -> Command { + let mut p = tokio::process::Command::new(exe); + p.creation_flags(winapi::um::winbase::CREATE_NO_WINDOW); + p +} + +/// Makes a new Command, setting flags to avoid extra windows on win32 +#[cfg(not(windows))] +pub fn new_tokio_command(exe: impl AsRef) -> Command { + tokio::process::Command::new(exe) +} + +/// Makes a new command to run the target script. For windows, ensures it's run +/// in a cmd.exe context. +#[cfg(windows)] +pub fn new_script_command(script: impl AsRef) -> Command { + let mut cmd = new_tokio_command("cmd"); + cmd.arg("/Q"); + cmd.arg("/C"); + cmd.arg(script); + cmd +} + +/// Makes a new command to run the target script. For windows, ensures it's run +/// in a cmd.exe context. +#[cfg(not(windows))] +pub fn new_script_command(script: impl AsRef) -> Command { + new_tokio_command(script) // it's assumed scripts are already +x and don't need extra handling +} + +/// Makes a new Command, setting flags to avoid extra windows on win32 +#[cfg(windows)] +pub fn new_std_command(exe: impl AsRef) -> std::process::Command { + let mut p = std::process::Command::new(exe); + std::os::windows::process::CommandExt::creation_flags( + &mut p, + winapi::um::winbase::CREATE_NO_WINDOW, + ); + p +} + +/// Makes a new Command, setting flags to avoid extra windows on win32 +#[cfg(not(windows))] +pub fn new_std_command(exe: impl AsRef) -> std::process::Command { + std::process::Command::new(exe) +} + /// Kills and processes and all of its children. -#[cfg(target_os = "windows")] +#[cfg(windows)] pub async fn kill_tree(process_id: u32) -> Result<(), CodeError> { capture_command("taskkill", &["/t", "/pid", &process_id.to_string()]).await?; Ok(()) } /// Kills and processes and all of its children. -#[cfg(not(target_os = "windows"))] +#[cfg(not(windows))] pub async fn kill_tree(process_id: u32) -> Result<(), CodeError> { use futures::future::join_all; use tokio::io::{AsyncBufReadExt, BufReader}; diff --git a/code/extensions/csharp/cgmanifest.json b/code/extensions/csharp/cgmanifest.json index cd80dd226f5..176ee56da48 100644 --- a/code/extensions/csharp/cgmanifest.json +++ b/code/extensions/csharp/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "dotnet/csharp-tmLanguage", "repositoryUrl": "https://github.com/dotnet/csharp-tmLanguage", - "commitHash": "29a37e5e2d66deefe5a06afad793a820871f4fdf" + "commitHash": "772323937fedd65c6dc1c8ce6ea41d97415ed7d1" } }, "license": "MIT", @@ -15,4 +15,4 @@ } ], "version": 1 -} \ No newline at end of file +} diff --git a/code/extensions/csharp/syntaxes/csharp.tmLanguage.json b/code/extensions/csharp/syntaxes/csharp.tmLanguage.json index ee0a7fac2a8..c4266da462d 100644 --- a/code/extensions/csharp/syntaxes/csharp.tmLanguage.json +++ b/code/extensions/csharp/syntaxes/csharp.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/dotnet/csharp-tmLanguage/commit/29a37e5e2d66deefe5a06afad793a820871f4fdf", + "version": "https://github.com/dotnet/csharp-tmLanguage/commit/772323937fedd65c6dc1c8ce6ea41d97415ed7d1", "name": "C#", "scopeName": "source.cs", "patterns": [ @@ -210,6 +210,9 @@ { "include": "#else-part" }, + { + "include": "#switch-statement" + }, { "include": "#goto-statement" }, @@ -235,10 +238,10 @@ "include": "#checked-unchecked-statement" }, { - "include": "#context-control-statement" + "include": "#lock-statement" }, { - "include": "#context-control-paren-statement" + "include": "#using-statement" }, { "include": "#labeled-statement" @@ -275,10 +278,13 @@ "include": "#comment" }, { - "include": "#expression-operator-expression" + "include": "#checked-unchecked-expression" }, { - "include": "#type-operator-expression" + "include": "#typeof-or-default-expression" + }, + { + "include": "#nameof-expression" }, { "include": "#default-literal-expression" @@ -299,20 +305,14 @@ "include": "#type-builtin" }, { - "include": "#language-variable" - }, - { - "include": "#switch-statement-or-expression" + "include": "#this-or-base-expression" }, { - "include": "#with-expression" + "include": "#switch-expression" }, { "include": "#conditional-operator" }, - { - "include": "#assignment-expression" - }, { "include": "#expression-operators" }, @@ -370,39 +370,36 @@ ] }, "extern-alias-directive": { - "begin": "\\b(extern)\\s+(alias)\\b", + "begin": "\\s*(extern)\\b\\s*(alias)\\b\\s*(@?[_[:alpha:]][_[:alnum:]]*)", "beginCaptures": { "1": { - "name": "keyword.other.directive.extern.cs" + "name": "keyword.other.extern.cs" }, "2": { - "name": "keyword.other.directive.alias.cs" - } - }, - "end": "(?=;)", - "patterns": [ - { - "match": "\\@?[_[:alpha:]][_[:alnum:]]*", + "name": "keyword.other.alias.cs" + }, + "3": { "name": "variable.other.alias.cs" } - ] + }, + "end": "(?=;)" }, "using-directive": { "patterns": [ { - "begin": "\\b(?:(global)\\s+)?(using)\\s+(static)\\b\\s*(?:(unsafe)\\b\\s*)?", + "begin": "(\\b(global)\\b\\s+)?\\b(using)\\b\\s+(static)\\b\\s+(\\b(unsafe)\\b\\s+)?", "beginCaptures": { - "1": { - "name": "keyword.other.directive.global.cs" - }, "2": { - "name": "keyword.other.directive.using.cs" + "name": "keyword.other.global.cs" }, "3": { - "name": "keyword.other.directive.static.cs" + "name": "keyword.other.using.cs" }, "4": { - "name": "storage.modifier.unsafe.cs" + "name": "keyword.other.static.cs" + }, + "6": { + "name": "storage.modifier.cs" } }, "end": "(?=;)", @@ -413,22 +410,19 @@ ] }, { - "begin": "\\b(?:(global)\\s+)?(using)\\b\\s*(?:(unsafe)\\b\\s*)?(@?[_[:alpha:]][_[:alnum:]]*)\\s*(=)", + "begin": "(\\b(global)\\b\\s+)?\\b(using)\\b\\s+(\\b(unsafe)\\b\\s+)?(?=(@?[_[:alpha:]][_[:alnum:]]*)\\s*=)", "beginCaptures": { - "1": { - "name": "keyword.other.directive.global.cs" - }, "2": { - "name": "keyword.other.directive.using.cs" + "name": "keyword.other.global.cs" }, "3": { - "name": "storage.modifier.unsafe.cs" - }, - "4": { - "name": "entity.name.type.alias.cs" + "name": "keyword.other.using.cs" }, "5": { - "name": "keyword.operator.assignment.cs" + "name": "storage.modifier.cs" + }, + "6": { + "name": "entity.name.type.alias.cs" } }, "end": "(?=;)", @@ -438,17 +432,20 @@ }, { "include": "#type" + }, + { + "include": "#operator-assignment" } ] }, { - "begin": "\\b(?:(global)\\s+)?(using)\\b\\s*+(?!\\(|var\\b)", + "begin": "(\\b(global)\\b\\s+)?\\b(using)\\s*(?!\\(|\\s|var)", "beginCaptures": { - "1": { - "name": "keyword.other.directive.global.cs" - }, "2": { - "name": "keyword.other.directive.using.cs" + "name": "keyword.other.global.cs" + }, + "3": { + "name": "keyword.other.using.cs" } }, "end": "(?=;)", @@ -458,10 +455,7 @@ }, { "name": "entity.name.type.namespace.cs", - "match": "\\@?[_[:alpha:]][_[:alnum:]]*" - }, - { - "include": "#punctuation-accessor" + "match": "@?[_[:alpha:]][_[:alnum:]]*" }, { "include": "#operator-assignment" @@ -557,7 +551,7 @@ "begin": "\\b(namespace)\\s+", "beginCaptures": { "1": { - "name": "storage.type.namespace.cs" + "name": "keyword.other.namespace.cs" } }, "end": "(?<=\\})|(?=;)", @@ -600,7 +594,7 @@ ] }, "storage-modifier": { - "name": "storage.modifier.$1.cs", + "name": "storage.modifier.cs", "match": "(?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s+\n(\\g)\\s*\n(<([^<>]+)>)?\\s*\n(?=\\()", "beginCaptures": { "1": { - "name": "storage.type.delegate.cs" + "name": "keyword.other.delegate.cs" }, "2": { "patterns": [ @@ -718,7 +712,7 @@ "match": "(enum)\\s+(@?[_[:alpha:]][_[:alnum:]]*)", "captures": { "1": { - "name": "storage.type.enum.cs" + "name": "keyword.other.enum.cs" }, "2": { "name": "entity.name.type.enum.cs" @@ -802,7 +796,7 @@ "begin": "(?x)\n(interface)\\b\\s+\n(@?[_[:alpha:]][_[:alnum:]]*)", "beginCaptures": { "1": { - "name": "storage.type.interface.cs" + "name": "keyword.other.interface.cs" }, "2": { "name": "entity.name.type.interface.cs" @@ -859,7 +853,7 @@ "begin": "(?x)\n(record)\\b\\s+\n(@?[_[:alpha:]][_[:alnum:]]*)", "beginCaptures": { "1": { - "name": "storage.type.record.cs" + "name": "keyword.other.record.cs" }, "2": { "name": "entity.name.type.class.cs" @@ -919,10 +913,10 @@ "begin": "(?x)\n(\\b(record)\\b\\s+)?\n(struct)\\b\\s+\n(@?[_[:alpha:]][_[:alnum:]]*)", "beginCaptures": { "2": { - "name": "storage.type.record.cs" + "name": "keyword.other.record.cs" }, "3": { - "name": "storage.type.struct.cs" + "name": "keyword.other.struct.cs" }, "4": { "name": "entity.name.type.struct.cs" @@ -990,11 +984,19 @@ "patterns": [ { "match": "\\b(in|out)\\b", - "name": "storage.modifier.$1.cs" + "captures": { + "1": { + "name": "storage.modifier.cs" + } + } }, { "match": "(@?[_[:alpha:]][_[:alnum:]]*)\\b", - "name": "entity.name.type.type-parameter.cs" + "captures": { + "1": { + "name": "entity.name.type.type-parameter.cs" + } + } }, { "include": "#comment" @@ -1031,7 +1033,7 @@ "begin": "(where)\\s+(@?[_[:alpha:]][_[:alnum:]]*)\\s*(:)", "beginCaptures": { "1": { - "name": "storage.modifier.where.cs" + "name": "keyword.other.where.cs" }, "2": { "name": "entity.name.type.type-parameter.cs" @@ -1043,18 +1045,18 @@ "end": "(?=\\{|where|;|=>)", "patterns": [ { - "name": "storage.type.class.cs", + "name": "keyword.other.class.cs", "match": "\\bclass\\b" }, { - "name": "storage.type.struct.cs", + "name": "keyword.other.struct.cs", "match": "\\bstruct\\b" }, { "match": "(new)\\s*(\\()\\s*(\\))", "captures": { "1": { - "name": "keyword.operator.expression.new.cs" + "name": "keyword.other.new.cs" }, "2": { "name": "punctuation.parenthesis.open.cs" @@ -1110,7 +1112,7 @@ ] }, "property-declaration": { - "begin": "(?x)\n\n# The negative lookahead below ensures that we don't match nested types\n# or other declarations as properties.\n(?![[:word:][:space:]]*\\b(?:class|interface|struct|enum|event)\\b)\n\n(?\n (?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\\s+\n)\n(?\\g\\s*\\.\\s*)?\n(?\\g)\\s*\n(?=\\{|=>|//|/\\*|$)", + "begin": "(?x)\n\n# The negative lookahead below ensures that we don't match nested types\n# or other declarations as properties.\n(?![[:word:][:space:]]*\\b(?:class|interface|struct|enum|event)\\b)\n\n(?\n (?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\\s+\n)\n(?\\g\\s*\\.\\s*)?\n(?\\g)\\s*\n(?=\\{|=>|$)", "beginCaptures": { "1": { "patterns": [ @@ -1142,7 +1144,7 @@ "include": "#property-accessors" }, { - "include": "#accessor-getter-expression" + "include": "#expression-body" }, { "include": "#variable-initializer" @@ -1173,7 +1175,7 @@ ] }, "8": { - "name": "variable.language.this.cs" + "name": "keyword.other.this.cs" } }, "end": "(?<=\\})|(?=;)", @@ -1188,7 +1190,7 @@ "include": "#property-accessors" }, { - "include": "#accessor-getter-expression" + "include": "#expression-body" }, { "include": "#variable-initializer" @@ -1196,10 +1198,10 @@ ] }, "event-declaration": { - "begin": "(?x)\n\\b(event)\\b\\s*\n(?\n (?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\\s+\n)\n(?\\g\\s*\\.\\s*)?\n(\\g)\\s* # first event name\n(?=\\{|;|,|=|//|/\\*|$)", + "begin": "(?x)\n\\b(event)\\b\\s*\n(?\n (?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\\s+\n)\n(?\\g\\s*\\.\\s*)?\n(?\\g(?:\\s*,\\s*\\g)*)\\s*\n(?=\\{|;|$)", "beginCaptures": { "1": { - "name": "storage.type.event.cs" + "name": "keyword.other.event.cs" }, "2": { "patterns": [ @@ -1219,7 +1221,15 @@ ] }, "9": { - "name": "entity.name.variable.event.cs" + "patterns": [ + { + "name": "entity.name.variable.event.cs", + "match": "@?[_[:alpha:]][_[:alnum:]]*" + }, + { + "include": "#punctuation-comma" + } + ] } }, "end": "(?<=\\})|(?=;)", @@ -1230,29 +1240,8 @@ { "include": "#event-accessors" }, - { - "name": "entity.name.variable.event.cs", - "match": "@?[_[:alpha:]][_[:alnum:]]*" - }, { "include": "#punctuation-comma" - }, - { - "begin": "=", - "beginCaptures": { - "0": { - "name": "keyword.operator.assignment.cs" - } - }, - "end": "(?<=,)|(?=;)", - "patterns": [ - { - "include": "#expression" - }, - { - "include": "#punctuation-comma" - } - ] } ] }, @@ -1270,6 +1259,22 @@ } }, "patterns": [ + { + "name": "storage.modifier.cs", + "match": "\\b(private|protected|internal)\\b" + }, + { + "name": "keyword.other.get.cs", + "match": "\\b(get)\\b" + }, + { + "name": "keyword.other.set.cs", + "match": "\\b(set)\\b" + }, + { + "name": "keyword.other.init.cs", + "match": "\\b(init)\\b" + }, { "include": "#comment" }, @@ -1277,36 +1282,13 @@ "include": "#attribute-section" }, { - "name": "storage.modifier.$1.cs", - "match": "\\b(private|protected|internal)\\b" + "include": "#expression-body" }, { - "begin": "\\b(get)\\b\\s*(?=\\{|;|=>|//|/\\*|$)", - "beginCaptures": { - "1": { - "name": "storage.type.accessor.$1.cs" - } - }, - "end": "(?<=\\}|;)|(?=\\})", - "patterns": [ - { - "include": "#accessor-getter" - } - ] + "include": "#block" }, { - "begin": "\\b(set|init)\\b\\s*(?=\\{|;|=>|//|/\\*|$)", - "beginCaptures": { - "1": { - "name": "storage.type.accessor.$1.cs" - } - }, - "end": "(?<=\\}|;)|(?=\\})", - "patterns": [ - { - "include": "#accessor-setter" - } - ] + "include": "#punctuation-semicolon" } ] }, @@ -1325,114 +1307,24 @@ }, "patterns": [ { - "include": "#comment" - }, - { - "include": "#attribute-section" + "name": "keyword.other.add.cs", + "match": "\\b(add)\\b" }, { - "begin": "\\b(add|remove)\\b\\s*(?=\\{|;|=>|//|/\\*|$)", - "beginCaptures": { - "1": { - "name": "storage.type.accessor.$1.cs" - } - }, - "end": "(?<=\\}|;)|(?=\\})", - "patterns": [ - { - "include": "#accessor-setter" - } - ] - } - ] - }, - "accessor-getter": { - "patterns": [ - { - "begin": "\\{", - "beginCaptures": { - "0": { - "name": "punctuation.curlybrace.open.cs" - } - }, - "end": "\\}", - "endCaptures": { - "0": { - "name": "punctuation.curlybrace.close.cs" - } - }, - "contentName": "meta.accessor.getter.cs", - "patterns": [ - { - "include": "#statement" - } - ] + "name": "keyword.other.remove.cs", + "match": "\\b(remove)\\b" }, { - "include": "#accessor-getter-expression" + "include": "#comment" }, { - "include": "#punctuation-semicolon" - } - ] - }, - "accessor-getter-expression": { - "begin": "=>", - "beginCaptures": { - "0": { - "name": "keyword.operator.arrow.cs" - } - }, - "end": "(?=;|\\})", - "contentName": "meta.accessor.getter.cs", - "patterns": [ - { - "include": "#ref-modifier" + "include": "#attribute-section" }, { - "include": "#expression" - } - ] - }, - "accessor-setter": { - "patterns": [ - { - "begin": "\\{", - "beginCaptures": { - "0": { - "name": "punctuation.curlybrace.open.cs" - } - }, - "end": "\\}", - "endCaptures": { - "0": { - "name": "punctuation.curlybrace.close.cs" - } - }, - "contentName": "meta.accessor.setter.cs", - "patterns": [ - { - "include": "#statement" - } - ] + "include": "#expression-body" }, { - "begin": "=>", - "beginCaptures": { - "0": { - "name": "keyword.operator.arrow.cs" - } - }, - "end": "(?=;|\\})", - "contentName": "meta.accessor.setter.cs", - "patterns": [ - { - "include": "#ref-modifier" - }, - { - "include": "#expression" - } - ] + "include": "#block" }, { "include": "#punctuation-semicolon" @@ -1533,10 +1425,13 @@ ] }, "constructor-initializer": { - "begin": "\\b(base|this)\\b\\s*(?=\\()", + "begin": "\\b(?:(base)|(this))\\b\\s*(?=\\()", "beginCaptures": { "1": { - "name": "variable.language.$1.cs" + "name": "keyword.other.base.cs" + }, + "2": { + "name": "keyword.other.this.cs" } }, "end": "(?<=\\))", @@ -1573,7 +1468,7 @@ ] }, "operator-declaration": { - "begin": "(?x)\n(?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s*\n\\b(?operator)\\b\\s*\n(?[+\\-*/%&|\\^!=~<>]+|true|false)\\s*\n(?=\\()", + "begin": "(?x)\n(?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s*\n(?(?:\\b(?:operator)))\\s*\n(?(?:\\+|-|\\*|/|%|&|\\||\\^|\\<\\<|\\>\\>|==|!=|\\>|\\<|\\>=|\\<=|!|~|\\+\\+|--|true|false))\\s*\n(?=\\()", "beginCaptures": { "1": { "patterns": [ @@ -1583,7 +1478,7 @@ ] }, "6": { - "name": "storage.type.operator.cs" + "name": "keyword.other.operator-decl.cs" }, "7": { "name": "entity.name.function.cs" @@ -1614,7 +1509,7 @@ "match": "\\b(explicit)\\b", "captures": { "1": { - "name": "storage.modifier.explicit.cs" + "name": "keyword.other.explicit.cs" } } }, @@ -1622,14 +1517,14 @@ "match": "\\b(implicit)\\b", "captures": { "1": { - "name": "storage.modifier.implicit.cs" + "name": "keyword.other.implicit.cs" } } } ] }, "2": { - "name": "storage.type.operator.cs" + "name": "keyword.other.operator-decl.cs" }, "3": { "patterns": [ @@ -1712,19 +1607,19 @@ "begin": "(?", - "beginCaptures": { - "0": { - "name": "keyword.operator.arrow.cs" - } - }, - "end": "(?=,|})", - "patterns": [ + "include": "#literal" + }, { - "include": "#expression" - } - ] - }, - { - "begin": "\\b(when)\\b", - "beginCaptures": { - "1": { - "name": "keyword.control.conditional.when.cs" - } - }, - "end": "(?==>|,|})", - "patterns": [ + "include": "#switch-var-pattern" + }, { - "include": "#case-guard" - } - ] - }, - { - "begin": "(?!\\s)", - "end": "(?=\\bwhen\\b|=>|,|})", - "patterns": [ + "include": "#switch-property-expression" + }, { - "include": "#pattern" - } - ] - } - ] - }, - "case-guard": { - "patterns": [ - { - "include": "#parenthesized-expression" - }, - { - "include": "#expression" - } - ] - }, - "is-expression": { - "begin": "(?=?", - "beginCaptures": { - "0": { - "name": "keyword.operator.relational.cs" - } - }, - "end": "(?=[)}\\],;:?=&|^]|!=|\\b(and|or|when)\\b)", - "patterns": [ - { - "include": "#expression" } ] }, - "var-pattern": { - "begin": "\\b(var)\\b", + "switch-pattern": { + "begin": "(?x) # e.g. int x OR var x\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s+\n(\\g)\\b\\s*", "beginCaptures": { "1": { - "name": "storage.type.var.cs" - } - }, - "end": "(?=[)}\\],;:?=&|^]|!=|\\b(and|or|when)\\b)", - "patterns": [ - { - "include": "#designation-pattern" - } - ] - }, - "designation-pattern": { - "patterns": [ - { - "include": "#intrusive" - }, - { - "begin": "\\(", - "beginCaptures": { - "0": { - "name": "punctuation.parenthesis.open.cs" - } - }, - "end": "\\)", - "endCaptures": { - "0": { - "name": "punctuation.parenthesis.close.cs" - } - }, "patterns": [ { - "include": "#punctuation-comma" - }, - { - "include": "#designation-pattern" + "include": "#type" } ] }, - { - "include": "#simple-designation-pattern" - } - ] - }, - "simple-designation-pattern": { - "patterns": [ - { - "include": "#discard-pattern" - }, - { - "match": "@?[_[:alpha:]][_[:alnum:]]*", + "2": { "name": "entity.name.variable.local.cs" } - ] - }, - "type-pattern": { - "begin": "(?=@?[_[:alpha:]][_[:alnum:]]*)", - "end": "(?=[)}\\],;:?=&|^]|!=|\\b(and|or|when)\\b)", + }, + "end": "(?==>)", "patterns": [ { - "begin": "\\G", - "end": "(?!\\G[@_[:alpha:]])(?=[\\({@_[:alpha:])}\\],;:=&|^]|(?:\\s|^)\\?|!=|\\b(and|or|when)\\b)", - "patterns": [ - { - "include": "#intrusive" - }, - { - "include": "#type-subpattern" - } - ] + "include": "#comment" }, { - "begin": "(?=[\\({@_[:alpha:]])", - "end": "(?=[)}\\],;:?=&|^]|!=|\\b(and|or|when)\\b)", - "patterns": [ - { - "include": "#intrusive" - }, - { - "include": "#positional-pattern" - }, - { - "include": "#property-pattern" - }, - { - "include": "#simple-designation-pattern" - } - ] + "include": "#switch-when-clause" } ] }, - "type-subpattern": { - "patterns": [ - { - "include": "#type-builtin" - }, - { - "begin": "(@?[_[:alpha:]][_[:alnum:]]*)\\s*(::)", - "beginCaptures": { - "1": { - "name": "entity.name.type.alias.cs" - }, - "2": { - "name": "punctuation.separator.coloncolon.cs" - } - }, - "end": "(?<=[_[:alnum:]])|(?=[.<\\[\\({)}\\],;:?=&|^]|!=|\\b(and|or|when)\\b)", - "patterns": [ - { - "include": "#intrusive" - }, - { - "match": "\\@?[_[:alpha:]][_[:alnum:]]*", - "name": "entity.name.type.cs" - } - ] - }, - { - "match": "\\@?[_[:alpha:]][_[:alnum:]]*", - "name": "entity.name.type.cs" - }, - { - "begin": "\\.", - "beginCaptures": { - "0": { - "name": "punctuation.accessor.cs" - } - }, - "end": "(?<=[_[:alnum:]])|(?=[<\\[\\({)}\\],;:?=&|^]|!=|\\b(and|or|when)\\b)", + "switch-property-expression": { + "begin": "(?x) # e.g. int x OR var x\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)?\\s*\n(\\{)", + "beginCaptures": { + "1": { "patterns": [ { - "include": "#intrusive" - }, - { - "match": "\\@?[_[:alpha:]][_[:alnum:]]*", - "name": "entity.name.type.cs" + "include": "#type" } ] }, - { - "include": "#type-arguments" - }, - { - "include": "#type-array-suffix" - }, - { - "match": "(?\\((?:[^\\(\\)]|\\g)+\\))\\s*", + "beginCaptures": { + "1": { + "name": "keyword.other.var.cs" + }, + "2": { "patterns": [ { - "include": "#subpattern" - }, - { - "include": "#punctuation-comma" + "include": "#tuple-declaration-deconstruction-element-list" } ] + } + }, + "end": "(?==>)", + "patterns": [ + { + "include": "#comment" }, { - "begin": "(?<=\\})", - "end": "(?=[)}\\],;:?=&|^]|!=|\\b(and|or|when)\\b)", - "patterns": [ - { - "include": "#intrusive" - }, - { - "include": "#simple-designation-pattern" - } - ] + "include": "#switch-when-clause" } ] }, - "subpattern": { + "switch-when-clause": { + "begin": "(?)", "patterns": [ { - "match": "(@?[_[:alpha:]][_[:alnum:]]*(?:\\s*\\.\\s*@?[_[:alpha:]][_[:alnum:]]*)*)\\s*(:)", + "include": "#comment" + }, + { + "include": "#expression" + }, + { + "include": "#punctuation-comma" + }, + { + "match": "\\(", "captures": { - "1": { - "patterns": [ - { - "match": "\\@?[_[:alpha:]][_[:alnum:]]*", - "name": "variable.other.object.property.cs" - }, - { - "include": "#punctuation-accessor" - } - ] - }, - "2": { - "name": "punctuation.separator.colon.cs" + "0": { + "name": "punctuation.parenthesis.open.cs" } } }, { - "include": "#pattern" + "match": "\\)", + "captures": { + "0": { + "name": "punctuation.parenthesis.close.cs" + } + } } ] }, - "list-pattern": { - "begin": "(?=\\[)", - "end": "(?=[)}\\],;:?=&|^]|!=|\\b(and|or|when)\\b)", + "switch-label": { "patterns": [ { - "begin": "\\[", + "begin": "(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\n)\\s+\n(\\g)\\s+\n\\b(in)\\b", "captures": { "1": { - "name": "storage.type.var.cs" + "name": "keyword.other.var.cs" }, "2": { "patterns": [ @@ -2719,7 +2176,7 @@ "match": "(?x) # match foreach (var (x, y) in ...)\n(?:\\b(var)\\b\\s*)?\n(?\\((?:[^\\(\\)]|\\g)+\\))\\s+\n\\b(in)\\b", "captures": { "1": { - "name": "storage.type.var.cs" + "name": "keyword.other.var.cs" }, "2": { "patterns": [ @@ -2737,6 +2194,9 @@ "include": "#expression" } ] + }, + { + "include": "#statement" } ] }, @@ -2757,7 +2217,7 @@ "begin": "(?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref local\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*[?*]\\s*)? # nullable or pointer suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\n)\\s+\n(\\g)\\s*\n(?!=>)\n(?=,|;|=|\\))", + "begin": "(?x)\n(?:\n (?:(\\busing)\\s+)?\n (?:(\\bref)\\s+(?:(\\breadonly)\\s+)?)?(\\bvar\\b)| # ref local\n (?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref local\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\n)\\s+\n(\\g)\\s*\n(?!=>)\n(?=,|;|=|\\))", "beginCaptures": { "1": { - "name": "storage.modifier.ref.cs" + "name": "keyword.other.using.cs" }, "2": { - "name": "storage.modifier.readonly.cs" + "name": "storage.modifier.cs" }, "3": { - "name": "storage.type.var.cs" + "name": "storage.modifier.cs" }, "4": { + "name": "keyword.other.var.cs" + }, + "5": { "patterns": [ { "include": "#type" } ] }, - "9": { + "10": { "name": "entity.name.variable.local.cs" } }, - "end": "(?=[;)}])", + "end": "(?=;|\\))", "patterns": [ { "name": "entity.name.variable.local.cs", @@ -3068,7 +2486,7 @@ "begin": "(?x)\n(?\\b(?:const)\\b)\\s*\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s+\n(\\g)\\s*\n(?=,|;|=)", "beginCaptures": { "1": { - "name": "storage.modifier.const.cs" + "name": "storage.modifier.cs" }, "2": { "patterns": [ @@ -3099,49 +2517,9 @@ ] }, "local-function-declaration": { - "begin": "(?x)\n\\b((?:(?:async|unsafe|static|extern)\\s+)*)\n(?\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n \\s*(?:,\\s*)* # commata for multi-dimensional arrays\n \\]\n (?:\\s*\\?)? # arrays can be nullable reference types\n )*\n)\\s+\n(\\g)\\s*\n(<[^<>]+>)?\\s*\n(?=\\()", - "beginCaptures": { - "1": { - "patterns": [ - { - "include": "#storage-modifier" - } - ] - }, - "2": { - "patterns": [ - { - "include": "#type" - } - ] - }, - "7": { - "name": "entity.name.function.cs" - }, - "8": { - "patterns": [ - { - "include": "#type-parameter-list" - } - ] - } - }, - "end": "(?<=\\})|(?=;)", "patterns": [ { - "include": "#comment" - }, - { - "include": "#parenthesized-parameter-list" - }, - { - "include": "#generic-constraints" - }, - { - "include": "#expression-body" - }, - { - "include": "#block" + "include": "#method-declaration" } ] }, @@ -3149,7 +2527,7 @@ "begin": "(?x) # e.g. var (x, y) = GetPoint();\n(?:\\b(var)\\b\\s*)\n(?\\((?:[^\\(\\)]|\\g)+\\))\\s*\n(?=;|=|\\))", "beginCaptures": { "1": { - "name": "storage.type.var.cs" + "name": "keyword.other.var.cs" }, "2": { "patterns": [ @@ -3257,7 +2635,7 @@ "match": "(?x) # e.g. int x OR var x\n(?:\n \\b(var)\\b|\n (?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\n)\\s+\n(\\g)\\b\\s*\n(?=[,)\\]])", "captures": { "1": { - "name": "storage.type.var.cs" + "name": "keyword.other.var.cs" }, "2": { "patterns": [ @@ -3275,7 +2653,7 @@ "match": "(?x) # e.g. int x OR var x\n(?:\n \\b(var)\\b|\n (?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\n)\\s+\n(\\g)\\b\\s*\n(?=[,)])", "captures": { "1": { - "name": "storage.type.var.cs" + "name": "keyword.other.var.cs" }, "2": { "patterns": [ @@ -3289,13 +2667,16 @@ } } }, - "expression-operator-expression": { - "begin": "\\b(checked|unchecked|nameof)\\s*(\\()", + "checked-unchecked-expression": { + "begin": "(?>>?|\\|)?=(?!=|>)", - "beginCaptures": { - "0": { - "patterns": [ - { - "include": "#assignment-operators" - } - ] - } - }, - "end": "(?=[,\\)\\];}])", - "patterns": [ - { - "include": "#ref-modifier" - }, - { - "include": "#expression" - } - ] - }, - "assignment-operators": { + "expression-operators": { "patterns": [ { "name": "keyword.operator.assignment.compound.cs", @@ -4006,19 +3391,11 @@ }, { "name": "keyword.operator.assignment.compound.bitwise.cs", - "match": "\\&=|\\^=|<<=|>>>?=|\\|=" + "match": "\\&=|\\^=|<<=|>>=|\\|=" }, - { - "name": "keyword.operator.assignment.cs", - "match": "\\=" - } - ] - }, - "expression-operators": { - "patterns": [ { "name": "keyword.operator.bitwise.shift.cs", - "match": "<<|>>>?" + "match": "<<|>>" }, { "name": "keyword.operator.comparison.cs", @@ -4036,6 +3413,10 @@ "name": "keyword.operator.bitwise.cs", "match": "\\&|~|\\^|\\|" }, + { + "name": "keyword.operator.assignment.cs", + "match": "\\=" + }, { "name": "keyword.operator.decrement.cs", "match": "--" @@ -4046,50 +3427,56 @@ }, { "name": "keyword.operator.arithmetic.cs", - "match": "\\+|-(?!>)|\\*|/|%" + "match": "%|\\*|/|-|\\+" }, { "name": "keyword.operator.null-coalescing.cs", "match": "\\?\\?" - }, - { - "name": "keyword.operator.range.cs", - "match": "\\.\\." } ] }, - "with-expression": { - "begin": "(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?(?!\\?))? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n \\s*(?:,\\s*)* # commata for multi-dimensional arrays\n \\]\n (?:\\s*\\?(?!\\?))? # arrays can be nullable reference types\n )*\n )\n)?", + "match": "(?x)\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)?", "captures": { "1": { - "name": "keyword.operator.expression.as.cs" + "name": "keyword.other.as.cs" }, "2": { "patterns": [ @@ -4169,20 +3556,34 @@ } } }, - "language-variable": { - "patterns": [ - { - "name": "variable.language.$1.cs", - "match": "\\b(base|this)\\b" + "is-expression": { + "match": "(?x)\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)?", + "captures": { + "1": { + "name": "keyword.other.is.cs" }, - { - "name": "variable.other.$1.cs", - "match": "\\b(value)\\b" + "2": { + "patterns": [ + { + "include": "#type" + } + ] } - ] + } + }, + "this-or-base-expression": { + "match": "\\b(?:(base)|(this))\\b", + "captures": { + "1": { + "name": "keyword.other.base.cs" + }, + "2": { + "name": "keyword.other.this.cs" + } + } }, "invocation-expression": { - "begin": "(?x)\n(?:\n (?:(\\?)\\s*)? # preceding null-conditional operator?\n (\\.)\\s*| # preceding dot?\n (->)\\s* # preceding pointer arrow?\n)?\n(@?[_[:alpha:]][_[:alnum:]]*)\\s* # method name\n(\n <\n (?\n [^<>()]+|\n <\\g+>|\n \\(\\g+\\)\n )+\n >\\s*\n)? # type arguments\n(?=\\() # open paren of argument list", + "begin": "(?x)\n(?:(\\?)\\s*)? # preceding null-conditional operator?\n(?:(\\.)\\s*)? # preceding dot?\n(@?[_[:alpha:]][_[:alnum:]]*)\\s* # method name\n(?\\s*<([^<>]|\\g)+>\\s*)?\\s* # type arguments\n(?=\\() # open paren of argument list", "beginCaptures": { "1": { "name": "keyword.operator.null-conditional.cs" @@ -4191,12 +3592,9 @@ "name": "punctuation.accessor.cs" }, "3": { - "name": "punctuation.accessor.pointer.cs" - }, - "4": { "name": "entity.name.function.cs" }, - "5": { + "4": { "patterns": [ { "include": "#type-arguments" @@ -4212,7 +3610,7 @@ ] }, "element-access-expression": { - "begin": "(?x)\n(?:\n (?:(\\?)\\s*)? # preceding null-conditional operator?\n (\\.)\\s*| # preceding dot?\n (->)\\s* # preceding pointer arrow?\n)?\n(?:(@?[_[:alpha:]][_[:alnum:]]*)\\s*)? # property name\n(?:(\\?)\\s*)? # null-conditional operator?\n(?=\\[) # open bracket of argument list", + "begin": "(?x)\n(?:(\\?)\\s*)? # preceding null-conditional operator?\n(?:(\\.)\\s*)? # preceding dot?\n(?:(@?[_[:alpha:]][_[:alnum:]]*)\\s*)? # property name\n(?:(\\?)\\s*)? # null-conditional operator?\n(?=\\[) # open bracket of argument list", "beginCaptures": { "1": { "name": "keyword.operator.null-conditional.cs" @@ -4221,12 +3619,9 @@ "name": "punctuation.accessor.cs" }, "3": { - "name": "punctuation.accessor.pointer.cs" - }, - "4": { "name": "variable.other.object.property.cs" }, - "5": { + "4": { "name": "keyword.operator.null-conditional.cs" } }, @@ -4240,7 +3635,7 @@ "member-access-expression": { "patterns": [ { - "match": "(?x)\n(?:\n (?:(\\?)\\s*)? # preceding null-conditional operator?\n (\\.)\\s*| # preceding dot?\n (->)\\s* # preceding pointer arrow?\n)\n(@?[_[:alpha:]][_[:alnum:]]*)\\s* # property name\n(?![_[:alnum:]]|\\(|(\\?)?\\[|<) # next character is not alpha-numeric, nor a (, [, or <. Also, test for ?[", + "match": "(?x)\n(?:(\\?)\\s*)? # preceding null-conditional operator?\n(\\.)\\s* # preceding dot\n(@?[_[:alpha:]][_[:alnum:]]*)\\s* # property name\n(?![_[:alnum:]]|\\(|(\\?)?\\[|<) # next character is not alpha-numeric, nor a (, [, or <. Also, test for ?[", "captures": { "1": { "name": "keyword.operator.null-conditional.cs" @@ -4249,9 +3644,6 @@ "name": "punctuation.accessor.cs" }, "3": { - "name": "punctuation.accessor.pointer.cs" - }, - "4": { "name": "variable.other.object.property.cs" } } @@ -4275,7 +3667,7 @@ } }, { - "match": "(?x)\n(@?[_[:alpha:]][_[:alnum:]]*)\n(?=\n \\s*(?:(?:\\?\\s*)?\\.|->)\n \\s*@?[_[:alpha:]][_[:alnum:]]*\n)", + "match": "(?x)\n(@?[_[:alpha:]][_[:alnum:]]*)\n(?=\n (\\s*\\?)?\n \\s*\\.\\s*@?[_[:alpha:]][_[:alnum:]]*\n)", "captures": { "1": { "name": "variable.other.object.cs" @@ -4298,7 +3690,7 @@ "begin": "(?x)\n(new)(?:\\s+\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n))?\\s*\n(?=\\()", "beginCaptures": { "1": { - "name": "keyword.operator.expression.new.cs" + "name": "keyword.other.new.cs" }, "2": { "patterns": [ @@ -4316,10 +3708,10 @@ ] }, "object-creation-expression-with-no-parameters": { - "match": "(?x)\n(new)\\s+\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s*\n(?=\\{|//|/\\*|$)", + "match": "(?x)\n(new)\\s+\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s*\n(?=\\{|$)", "captures": { "1": { - "name": "keyword.operator.expression.new.cs" + "name": "keyword.other.new.cs" }, "2": { "patterns": [ @@ -4334,7 +3726,7 @@ "begin": "(?x)\n\\b(new|stackalloc)\\b\\s*\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)?\\s*\n(?=\\[)", "beginCaptures": { "1": { - "name": "keyword.operator.expression.$1.cs" + "name": "keyword.other.new.cs" }, "2": { "patterns": [ @@ -4352,17 +3744,14 @@ ] }, "anonymous-object-creation-expression": { - "begin": "\\b(new)\\b\\s*(?=\\{|//|/\\*|$)", + "begin": "\\b(new)\\b\\s*(?=\\{|$)", "beginCaptures": { "1": { - "name": "keyword.operator.expression.new.cs" + "name": "keyword.other.new.cs" } }, "end": "(?<=\\})", "patterns": [ - { - "include": "#comment" - }, { "include": "#initializer-expression" } @@ -4437,10 +3826,10 @@ ] }, "parameter": { - "match": "(?x)\n(?:(?:\\b(ref|params|out|in|this)\\b)\\s+)?\n(?\n (?:\n (?:ref\\s+)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^()]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s+\n(\\g)", + "match": "(?x)\n(?:(?:\\b(ref|params|out|in|this)\\b)\\s+)?\n(?\n (?:\n (?:ref\\s+)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s+\n(\\g)", "captures": { "1": { - "name": "storage.modifier.$1.cs" + "name": "storage.modifier.cs" }, "2": { "patterns": [ @@ -4524,25 +3913,11 @@ "argument": { "patterns": [ { - "name": "storage.modifier.$1.cs", - "match": "\\b(ref|in)\\b" + "name": "storage.modifier.cs", + "match": "\\b(ref|out|in)\\b" }, { - "begin": "\\b(out)\\b", - "beginCaptures": { - "1": { - "name": "storage.modifier.out.cs" - } - }, - "end": "(?=,|\\)|\\])", - "patterns": [ - { - "include": "#declaration-expression-local" - }, - { - "include": "#expression" - } - ] + "include": "#declaration-expression-local" }, { "include": "#expression" @@ -4553,7 +3928,7 @@ "begin": "(?x)\n\\b(from)\\b\\s*\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)?\n\\s+(\\g)\\b\\s*\n\\b(in)\\b\\s*", "beginCaptures": { "1": { - "name": "keyword.operator.expression.query.from.cs" + "name": "keyword.query.from.cs" }, "2": { "patterns": [ @@ -4566,7 +3941,7 @@ "name": "entity.name.variable.range-variable.cs" }, "8": { - "name": "keyword.operator.expression.query.in.cs" + "name": "keyword.query.in.cs" } }, "end": "(?=;|\\))", @@ -4605,7 +3980,7 @@ "begin": "(?x)\n\\b(let)\\b\\s*\n(@?[_[:alpha:]][_[:alnum:]]*)\\b\\s*\n(=)\\s*", "beginCaptures": { "1": { - "name": "keyword.operator.expression.query.let.cs" + "name": "keyword.query.let.cs" }, "2": { "name": "entity.name.variable.range-variable.cs" @@ -4628,7 +4003,7 @@ "begin": "(?x)\n\\b(where)\\b\\s*", "beginCaptures": { "1": { - "name": "keyword.operator.expression.query.where.cs" + "name": "keyword.query.where.cs" } }, "end": "(?=;|\\))", @@ -4645,7 +4020,7 @@ "begin": "(?x)\n\\b(join)\\b\\s*\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)?\n\\s+(\\g)\\b\\s*\n\\b(in)\\b\\s*", "beginCaptures": { "1": { - "name": "keyword.operator.expression.query.join.cs" + "name": "keyword.query.join.cs" }, "2": { "patterns": [ @@ -4658,7 +4033,7 @@ "name": "entity.name.variable.range-variable.cs" }, "8": { - "name": "keyword.operator.expression.query.in.cs" + "name": "keyword.query.in.cs" } }, "end": "(?=;|\\))", @@ -4684,7 +4059,7 @@ "match": "\\b(on)\\b\\s*", "captures": { "1": { - "name": "keyword.operator.expression.query.on.cs" + "name": "keyword.query.on.cs" } } }, @@ -4692,7 +4067,7 @@ "match": "\\b(equals)\\b\\s*", "captures": { "1": { - "name": "keyword.operator.expression.query.equals.cs" + "name": "keyword.query.equals.cs" } } }, @@ -4700,7 +4075,7 @@ "match": "(?x)\n\\b(into)\\b\\s*\n(@?[_[:alpha:]][_[:alnum:]]*)\\b\\s*", "captures": { "1": { - "name": "keyword.operator.expression.query.into.cs" + "name": "keyword.query.into.cs" }, "2": { "name": "entity.name.variable.range-variable.cs" @@ -4711,7 +4086,7 @@ "begin": "\\b(orderby)\\b\\s*", "beginCaptures": { "1": { - "name": "keyword.operator.expression.query.orderby.cs" + "name": "keyword.query.orderby.cs" } }, "end": "(?=;|\\))", @@ -4731,10 +4106,13 @@ ] }, "ordering-direction": { - "match": "\\b(ascending|descending)\\b", + "match": "\\b(?:(ascending)|(descending))\\b", "captures": { "1": { - "name": "keyword.operator.expression.query.$1.cs" + "name": "keyword.query.ascending.cs" + }, + "2": { + "name": "keyword.query.descending.cs" } } }, @@ -4742,7 +4120,7 @@ "begin": "\\b(select)\\b\\s*", "beginCaptures": { "1": { - "name": "keyword.operator.expression.query.select.cs" + "name": "keyword.query.select.cs" } }, "end": "(?=;|\\))", @@ -4759,7 +4137,7 @@ "begin": "\\b(group)\\b\\s*", "beginCaptures": { "1": { - "name": "keyword.operator.expression.query.group.cs" + "name": "keyword.query.group.cs" } }, "end": "(?=;|\\))", @@ -4782,7 +4160,7 @@ "match": "\\b(by)\\b\\s*", "captures": { "1": { - "name": "keyword.operator.expression.query.by.cs" + "name": "keyword.query.by.cs" } } }, @@ -4790,7 +4168,7 @@ "match": "(?x)\n\\b(into)\\b\\s*\n(@?[_[:alpha:]][_[:alnum:]]*)\\b\\s*", "captures": { "1": { - "name": "keyword.operator.expression.query.into.cs" + "name": "keyword.query.into.cs" }, "2": { "name": "entity.name.variable.range-variable.cs" @@ -4800,138 +4178,119 @@ "anonymous-method-expression": { "patterns": [ { - "begin": "(?x)\n((?:\\b(?:async|static)\\b\\s*)*)\n(?:\n (@?[_[:alpha:]][_[:alnum:]]*)\\b|\n (\\()\n (?(?:[^()]|\\(\\g\\))*)\n (\\))\n)\\s*\n(=>)", + "begin": "(?x)\n(?:\\b(async)\\b\\s*)?\n(@?[_[:alpha:]][_[:alnum:]]*)\\b\\s*\n(=>)", "beginCaptures": { "1": { - "patterns": [ - { - "match": "async|static", - "name": "storage.modifier.$0.cs" - } - ] + "name": "storage.modifier.cs" }, "2": { "name": "entity.name.variable.parameter.cs" }, "3": { - "name": "punctuation.parenthesis.open.cs" + "name": "keyword.operator.arrow.cs" + } + }, + "end": "(?=\\)|;|}|,)", + "patterns": [ + { + "include": "#block" }, - "4": { + { + "include": "#ref-modifier" + }, + { + "include": "#expression" + } + ] + }, + { + "begin": "(?x)\n(?:\\b(async)\\b\\s*)?\n(\\(.*?\\))\\s*\n(=>)", + "beginCaptures": { + "1": { + "name": "storage.modifier.cs" + }, + "2": { "patterns": [ { - "include": "#comment" - }, - { - "include": "#explicit-anonymous-function-parameter" - }, - { - "include": "#implicit-anonymous-function-parameter" - }, - { - "include": "#default-argument" - }, - { - "include": "#punctuation-comma" + "include": "#lambda-parameter-list" } ] }, - "5": { - "name": "punctuation.parenthesis.close.cs" - }, - "6": { + "3": { "name": "keyword.operator.arrow.cs" } }, - "end": "(?=[,;)}])", + "end": "(?=\\)|;|}|,)", "patterns": [ { - "include": "#intrusive" + "include": "#block" }, { - "begin": "(?={)", - "end": "(?=[,;)}])", - "patterns": [ - { - "include": "#block" - }, - { - "include": "#intrusive" - } - ] + "include": "#ref-modifier" }, { - "begin": "\\b(ref)\\b|(?=\\S)", - "beginCaptures": { - "1": { - "name": "storage.modifier.ref.cs" - } - }, - "end": "(?=[,;)}])", - "patterns": [ - { - "include": "#expression" - } - ] + "include": "#expression" } ] }, { - "begin": "(?x)\n((?:\\b(?:async|static)\\b\\s*)*)\n\\b(delegate)\\b\\s*", + "begin": "(?x)\n(?:\\b(async)\\b\\s*)?\n(?:\\b(delegate)\\b\\s*)", "beginCaptures": { "1": { - "patterns": [ - { - "match": "async|static", - "name": "storage.modifier.$0.cs" - } - ] + "name": "storage.modifier.cs" }, "2": { - "name": "storage.type.delegate.cs" + "name": "keyword.other.delegate.cs" } }, - "end": "(?<=})|(?=[,;)}])", + "end": "(?=\\)|;|}|,)", "patterns": [ { - "include": "#intrusive" + "include": "#parenthesized-parameter-list" }, { - "begin": "\\(", - "beginCaptures": { - "0": { - "name": "punctuation.parenthesis.open.cs" - } - }, - "end": "\\)", - "endCaptures": { - "0": { - "name": "punctuation.parenthesis.close.cs" - } - }, - "patterns": [ - { - "include": "#intrusive" - }, - { - "include": "#explicit-anonymous-function-parameter" - }, - { - "include": "#punctuation-comma" - } - ] + "include": "#block" }, { - "include": "#block" + "include": "#expression" } ] } ] }, - "explicit-anonymous-function-parameter": { - "match": "(?x)\n(?:\\b(ref|params|out|in)\\b\\s*)?\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?<(?:[^<>]|\\g)*>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^()]|\\g)*\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s*\n\\b(\\g)\\b", + "lambda-parameter-list": { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.parenthesis.open.cs" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.parenthesis.close.cs" + } + }, + "patterns": [ + { + "include": "#comment" + }, + { + "include": "#attribute-section" + }, + { + "include": "#lambda-parameter" + }, + { + "include": "#punctuation-comma" + } + ] + }, + "lambda-parameter": { + "match": "(?x)\n(?:\\b(ref|out|in)\\b)?\\s*\n(?:(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s+)?\n(\\g)\\b\\s*\n(?=[,)])", "captures": { "1": { - "name": "storage.modifier.$1.cs" + "name": "storage.modifier.cs" }, "2": { "patterns": [ @@ -4945,25 +4304,8 @@ } } }, - "implicit-anonymous-function-parameter": { - "match": "\\@?[_[:alpha:]][_[:alnum:]]*\\b", - "name": "entity.name.variable.parameter.cs" - }, - "default-argument": { - "begin": "=", - "beginCaptures": { - "0": { - "name": "keyword.operator.assignment.cs" - } - }, - "end": "(?=,|\\))", - "patterns": [ - { - "include": "#expression" - } - ] - }, "type": { + "name": "meta.type.cs", "patterns": [ { "include": "#comment" @@ -4991,19 +4333,16 @@ }, { "include": "#type-nullable-suffix" - }, - { - "include": "#type-pointer-suffix" } ] }, "ref-modifier": { - "name": "storage.modifier.ref.cs", - "match": "\\bref\\b" + "name": "storage.modifier.cs", + "match": "\\b(ref)\\b" }, "readonly-modifier": { - "name": "storage.modifier.readonly.cs", - "match": "\\breadonly\\b" + "name": "storage.modifier.cs", + "match": "\\b(readonly)\\b" }, "tuple-type": { "begin": "\\(", @@ -5043,10 +4382,10 @@ } }, "type-builtin": { - "match": "\\b(bool|s?byte|u?short|n?u?int|u?long|float|double|decimal|char|string|object|void|dynamic)\\b", + "match": "\\b(bool|byte|char|decimal|double|float|int|long|object|sbyte|short|string|uint|ulong|ushort|void|dynamic)\\b", "captures": { "1": { - "name": "keyword.type.$1.cs" + "name": "keyword.type.cs" } } }, @@ -5105,6 +4444,9 @@ } }, "patterns": [ + { + "include": "#comment" + }, { "include": "#type" }, @@ -5127,9 +4469,6 @@ } }, "patterns": [ - { - "include": "#intrusive" - }, { "include": "#punctuation-comma" } @@ -5137,11 +4476,11 @@ }, "type-nullable-suffix": { "match": "\\?", - "name": "punctuation.separator.question-mark.cs" - }, - "type-pointer-suffix": { - "match": "\\*", - "name": "punctuation.separator.asterisk.cs" + "captures": { + "0": { + "name": "punctuation.separator.question-mark.cs" + } + } }, "operator-assignment": { "name": "keyword.operator.assignment.cs", @@ -5159,16 +4498,6 @@ "name": "punctuation.accessor.cs", "match": "\\." }, - "intrusive": { - "patterns": [ - { - "include": "#preprocessor" - }, - { - "include": "#comment" - } - ] - }, "preprocessor": { "name": "meta.preprocessor.cs", "begin": "^\\s*(\\#)\\s*", @@ -5474,47 +4803,38 @@ "comment": { "patterns": [ { - "name": "comment.block.documentation.cs", - "begin": "(^\\s+)?(///)(?!/)", - "while": "^(\\s*)(///)(?!/)", - "captures": { - "1": { - "name": "punctuation.whitespace.comment.leading.cs" - }, - "2": { + "name": "comment.block.cs", + "begin": "/\\*", + "beginCaptures": { + "0": { "name": "punctuation.definition.comment.cs" } }, - "patterns": [ - { - "include": "#xml-doc-comment" + "end": "\\*/", + "endCaptures": { + "0": { + "name": "punctuation.definition.comment.cs" } - ] + } }, { - "name": "comment.block.documentation.cs", - "begin": "(^\\s+)?(/\\*\\*)(?!/)", - "end": "(^\\s+)?(\\*/)", - "captures": { + "begin": "(^\\s+)?(?=//)", + "beginCaptures": { "1": { "name": "punctuation.whitespace.comment.leading.cs" - }, - "2": { - "name": "punctuation.definition.comment.cs" } }, + "end": "(?=$)", "patterns": [ { - "begin": "\\G(?=(?~\\*/)$)", - "while": "^(\\s*+)(\\*(?!/))?(?=(?~\\*/)$)", - "whileCaptures": { - "1": { - "name": "punctuation.whitespace.comment.leading.cs" - }, - "2": { + "name": "comment.block.documentation.cs", + "begin": "(? { + let disposables: Disposable[] = []; + setup(() => { + disposables = []; + }); + + teardown(async function () { + assertNoRpc(); + await closeAllEditors(); + disposeAll(disposables); + }); + + function getDeferredForRequest(): DeferredPromise { + disposables.push(interactive.registerInteractiveSessionProvider('provider', { + prepareSession: (_initialState: InteractiveSessionState | undefined, _token: CancellationToken): ProviderResult => { + return { + requester: { name: 'test' }, + responder: { name: 'test' }, + }; + }, + + provideResponseWithProgress: (_request: InteractiveRequest, _progress: Progress, _token: CancellationToken): ProviderResult => { + return null; + }, + + provideSlashCommands: (_session, _token) => { + return [{ command: 'hello', title: 'Hello', kind: CompletionItemKind.Text }]; + }, + + removeRequest: (_session: InteractiveSession, _requestId: string): void => { + throw new Error('Function not implemented.'); + } + })); + + const deferred = new DeferredPromise(); + const agent = chat.createChatAgent('agent', (request, _context, _progress, _token) => { + deferred.complete(request); + return null; + }); + agent.slashCommandProvider = { + provideSlashCommands: (_token) => { + return [{ name: 'hello', description: 'Hello' }]; + } + }; + disposables.push(agent); + return deferred; + } + + test('agent and slash command', async () => { + const deferred = getDeferredForRequest(); + interactive.sendInteractiveRequestToProvider('provider', { message: '@agent /hello friend' }); + const lastResult = await deferred.p; + assert.deepStrictEqual(lastResult.slashCommand, { name: 'hello', description: 'Hello' }); + assert.strictEqual(lastResult.prompt, 'friend'); + }); + + test('agent and variable', async () => { + disposables.push(chat.registerVariable('myVar', 'My variable', { + resolve(_name, _context, _token) { + return [{ level: ChatVariableLevel.Full, value: 'myValue' }]; + } + })); + + const deferred = getDeferredForRequest(); + interactive.sendInteractiveRequestToProvider('provider', { message: '@agent hi #myVar' }); + const lastResult = await deferred.p; + assert.strictEqual(lastResult.prompt, 'hi [#myVar](values:myVar)'); + assert.strictEqual(lastResult.variables['myVar'][0].value, 'myValue'); + }); +}); diff --git a/code/extensions/vscode-api-tests/src/singlefolder-tests/interactive.test.ts b/code/extensions/vscode-api-tests/src/singlefolder-tests/interactive.test.ts new file mode 100644 index 00000000000..b6b5623a7fb --- /dev/null +++ b/code/extensions/vscode-api-tests/src/singlefolder-tests/interactive.test.ts @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * 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 'mocha'; +import { CancellationToken, CompletionItemKind, Disposable, interactive, InteractiveProgress, InteractiveRequest, InteractiveResponseForProgress, InteractiveSession, InteractiveSessionState, Progress, ProviderResult } from 'vscode'; +import { assertNoRpc, closeAllEditors, DeferredPromise, disposeAll } from '../utils'; + +suite('InteractiveSessionProvider', () => { + let disposables: Disposable[] = []; + setup(async () => { + disposables = []; + }); + + teardown(async function () { + assertNoRpc(); + await closeAllEditors(); + disposeAll(disposables); + }); + + function getDeferredForRequest(): DeferredPromise { + const deferred = new DeferredPromise(); + disposables.push(interactive.registerInteractiveSessionProvider('provider', { + prepareSession: (_initialState: InteractiveSessionState | undefined, _token: CancellationToken): ProviderResult => { + return { + requester: { name: 'test' }, + responder: { name: 'test' }, + }; + }, + + provideResponseWithProgress: (request: InteractiveRequest, _progress: Progress, _token: CancellationToken): ProviderResult => { + deferred.complete(request); + return null; + }, + + provideSlashCommands: (_session, _token) => { + return [{ command: 'hello', title: 'Hello', kind: CompletionItemKind.Text }]; + }, + + removeRequest: (_session: InteractiveSession, _requestId: string): void => { + throw new Error('Function not implemented.'); + } + })); + return deferred; + } + + test('plain text query', async () => { + const deferred = getDeferredForRequest(); + interactive.sendInteractiveRequestToProvider('provider', { message: 'hello' }); + const lastResult = await deferred.p; + assert.strictEqual(lastResult.message, 'hello'); + }); + + test('slash command', async () => { + const deferred = getDeferredForRequest(); + interactive.sendInteractiveRequestToProvider('provider', { message: '/hello' }); + const lastResult = await deferred.p; + assert.strictEqual(lastResult.message, '/hello'); + }); +}); diff --git a/code/extensions/vscode-api-tests/src/singlefolder-tests/workspace.watcher.test.ts b/code/extensions/vscode-api-tests/src/singlefolder-tests/workspace.watcher.test.ts index 13041ce380b..7859ae40682 100644 --- a/code/extensions/vscode-api-tests/src/singlefolder-tests/workspace.watcher.test.ts +++ b/code/extensions/vscode-api-tests/src/singlefolder-tests/workspace.watcher.test.ts @@ -27,19 +27,28 @@ suite('vscode API - workspace-watcher', () => { } } - teardown(assertNoRpc); + let fs: WatcherTestFs; + let disposable: vscode.Disposable; - test('createFileSystemWatcher', async function () { - const fs = new WatcherTestFs('watcherTest', false); - vscode.workspace.registerFileSystemProvider('watcherTest', fs); + function onDidWatchPromise() { + const onDidWatchPromise = new Promise(resolve => { + fs.onDidWatch(request => resolve(request)); + }); - function onDidWatchPromise() { - const onDidWatchPromise = new Promise(resolve => { - fs.onDidWatch(request => resolve(request)); - }); + return onDidWatchPromise; + } - return onDidWatchPromise; - } + setup(() => { + fs = new WatcherTestFs('watcherTest', false); + disposable = vscode.workspace.registerFileSystemProvider('watcherTest', fs); + }); + + teardown(() => { + disposable.dispose(); + assertNoRpc(); + }); + + test('createFileSystemWatcher (old style)', async function () { // Non-recursive let watchUri = vscode.Uri.from({ scheme: 'watcherTest', path: '/somePath/folder' }); @@ -59,4 +68,29 @@ suite('vscode API - workspace-watcher', () => { assert.strictEqual(request.uri.toString(), watchUri.toString()); assert.strictEqual(request.options.recursive, true); }); + + test('createFileSystemWatcher (new style)', async function () { + + // Non-recursive + let watchUri = vscode.Uri.from({ scheme: 'watcherTest', path: '/somePath/folder' }); + const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(watchUri, '*.txt'), { excludes: ['testing'], ignoreChangeEvents: true }); + let request = await onDidWatchPromise(); + + assert.strictEqual(request.uri.toString(), watchUri.toString()); + assert.strictEqual(request.options.recursive, false); + assert.strictEqual(request.options.excludes.length, 1); + assert.strictEqual(request.options.excludes[0], 'testing'); + + watcher.dispose(); + + // Recursive + watchUri = vscode.Uri.from({ scheme: 'watcherTest', path: '/somePath/folder' }); + vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(watchUri, '**/*.txt'), { excludes: ['testing'], ignoreCreateEvents: true }); + request = await onDidWatchPromise(); + + assert.strictEqual(request.uri.toString(), watchUri.toString()); + assert.strictEqual(request.options.recursive, true); + assert.strictEqual(request.options.excludes.length, 1); + assert.strictEqual(request.options.excludes[0], 'testing'); + }); }); diff --git a/code/extensions/vscode-colorize-tests/test/colorize-fixtures/test.cs b/code/extensions/vscode-colorize-tests/test/colorize-fixtures/test.cs index c7098217742..9671574d338 100644 --- a/code/extensions/vscode-colorize-tests/test/colorize-fixtures/test.cs +++ b/code/extensions/vscode-colorize-tests/test/colorize-fixtures/test.cs @@ -5,6 +5,10 @@ class TestClass { static void Main(string[] args) { + if (currentTab < lastTab && setTabWithoutUpdate(currentTab + 1)) + { + System.Console.WriteLine("Tests #195098"); + } int[] radii = { 15, 32, 108, 74, 9 }; const double pi = 3.14159; foreach (int radius in radii) { diff --git a/code/extensions/vscode-colorize-tests/test/colorize-results/test_cs.json b/code/extensions/vscode-colorize-tests/test/colorize-results/test_cs.json index e846aa23b4e..bf42bc29b6d 100644 --- a/code/extensions/vscode-colorize-tests/test/colorize-results/test_cs.json +++ b/code/extensions/vscode-colorize-tests/test/colorize-results/test_cs.json @@ -1,16 +1,16 @@ [ { "c": "using", - "t": "source.cs keyword.other.directive.using.cs", + "t": "source.cs keyword.other.using.cs", "r": { - "dark_plus": "keyword.other.directive.using: #C586C0", - "light_plus": "keyword.other.directive.using: #AF00DB", + "dark_plus": "keyword.other.using: #C586C0", + "light_plus": "keyword.other.using: #AF00DB", "dark_vs": "keyword: #569CD6", "light_vs": "keyword: #0000FF", - "hc_black": "keyword.other.directive.using: #C586C0", - "dark_modern": "keyword.other.directive.using: #C586C0", - "hc_light": "keyword.other.directive.using: #B5200D", - "light_modern": "keyword.other.directive.using: #AF00DB" + "hc_black": "keyword.other.using: #C586C0", + "dark_modern": "keyword.other.using: #C586C0", + "hc_light": "keyword.other.using: #B5200D", + "light_modern": "keyword.other.using: #AF00DB" } }, { @@ -57,16 +57,506 @@ }, { "c": "namespace", - "t": "source.cs storage.type.namespace.cs", + "t": "source.cs keyword.other.namespace.cs", "r": { - "dark_plus": "storage.type: #569CD6", - "light_plus": "storage.type: #0000FF", - "dark_vs": "storage.type: #569CD6", - "light_vs": "storage.type: #0000FF", - "hc_black": "storage.type: #569CD6", - "dark_modern": "storage.type: #569CD6", - "hc_light": "storage.type: #0F4A85", - "light_modern": "storage.type: #0000FF" + "dark_plus": "keyword: #569CD6", + "light_plus": "keyword: #0000FF", + "dark_vs": "keyword: #569CD6", + "light_vs": "keyword: #0000FF", + "hc_black": "keyword: #569CD6", + "dark_modern": "keyword: #569CD6", + "hc_light": "keyword: #0F4A85", + "light_modern": "keyword: #0000FF" + } + }, + { + "c": " ", + "t": "source.cs", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "SampleNamespace", + "t": "source.cs entity.name.type.namespace.cs", + "r": { + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0", + "dark_modern": "entity.name.type: #4EC9B0", + "hc_light": "entity.name.type: #185E73", + "light_modern": "entity.name.type: #267F99" + } + }, + { + "c": "{", + "t": "source.cs punctuation.curlybrace.open.cs", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.cs", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "class", + "t": "source.cs keyword.other.class.cs", + "r": { + "dark_plus": "keyword: #569CD6", + "light_plus": "keyword: #0000FF", + "dark_vs": "keyword: #569CD6", + "light_vs": "keyword: #0000FF", + "hc_black": "keyword: #569CD6", + "dark_modern": "keyword: #569CD6", + "hc_light": "keyword: #0F4A85", + "light_modern": "keyword: #0000FF" + } + }, + { + "c": " ", + "t": "source.cs", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "TestClass", + "t": "source.cs entity.name.type.class.cs", + "r": { + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0", + "dark_modern": "entity.name.type: #4EC9B0", + "hc_light": "entity.name.type: #185E73", + "light_modern": "entity.name.type: #267F99" + } + }, + { + "c": " ", + "t": "source.cs", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "{", + "t": "source.cs punctuation.curlybrace.open.cs", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.cs", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "static", + "t": "source.cs storage.modifier.cs", + "r": { + "dark_plus": "storage.modifier: #569CD6", + "light_plus": "storage.modifier: #0000FF", + "dark_vs": "storage.modifier: #569CD6", + "light_vs": "storage.modifier: #0000FF", + "hc_black": "storage.modifier: #569CD6", + "dark_modern": "storage.modifier: #569CD6", + "hc_light": "storage.modifier: #0F4A85", + "light_modern": "storage.modifier: #0000FF" + } + }, + { + "c": " ", + "t": "source.cs", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "void", + "t": "source.cs keyword.type.cs", + "r": { + "dark_plus": "keyword: #569CD6", + "light_plus": "keyword: #0000FF", + "dark_vs": "keyword: #569CD6", + "light_vs": "keyword: #0000FF", + "hc_black": "keyword: #569CD6", + "dark_modern": "keyword: #569CD6", + "hc_light": "keyword: #0F4A85", + "light_modern": "keyword: #0000FF" + } + }, + { + "c": " ", + "t": "source.cs", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "Main", + "t": "source.cs entity.name.function.cs", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA", + "dark_modern": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_modern": "entity.name.function: #795E26" + } + }, + { + "c": "(", + "t": "source.cs punctuation.parenthesis.open.cs", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "string", + "t": "source.cs keyword.type.cs", + "r": { + "dark_plus": "keyword: #569CD6", + "light_plus": "keyword: #0000FF", + "dark_vs": "keyword: #569CD6", + "light_vs": "keyword: #0000FF", + "hc_black": "keyword: #569CD6", + "dark_modern": "keyword: #569CD6", + "hc_light": "keyword: #0F4A85", + "light_modern": "keyword: #0000FF" + } + }, + { + "c": "[", + "t": "source.cs punctuation.squarebracket.open.cs", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "]", + "t": "source.cs punctuation.squarebracket.close.cs", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.cs", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "args", + "t": "source.cs entity.name.variable.parameter.cs", + "r": { + "dark_plus": "entity.name.variable: #9CDCFE", + "light_plus": "entity.name.variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "entity.name.variable: #9CDCFE", + "hc_light": "entity.name.variable: #001080", + "light_modern": "entity.name.variable: #001080" + } + }, + { + "c": ")", + "t": "source.cs punctuation.parenthesis.close.cs", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.cs", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "{", + "t": "source.cs punctuation.curlybrace.open.cs", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "\t\t\t", + "t": "source.cs", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "if", + "t": "source.cs keyword.control.conditional.if.cs", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #C586C0", + "hc_light": "keyword.control: #B5200D", + "light_modern": "keyword.control: #AF00DB" + } + }, + { + "c": " ", + "t": "source.cs", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "(", + "t": "source.cs punctuation.parenthesis.open.cs", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "currentTab", + "t": "source.cs variable.other.readwrite.cs", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" + } + }, + { + "c": " ", + "t": "source.cs", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "<", + "t": "source.cs keyword.operator.relational.cs", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4", + "dark_modern": "keyword.operator: #D4D4D4", + "hc_light": "keyword.operator: #000000", + "light_modern": "keyword.operator: #000000" + } + }, + { + "c": " ", + "t": "source.cs", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "lastTab", + "t": "source.cs variable.other.readwrite.cs", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" + } + }, + { + "c": " ", + "t": "source.cs", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "&&", + "t": "source.cs keyword.operator.logical.cs", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4", + "dark_modern": "keyword.operator: #D4D4D4", + "hc_light": "keyword.operator: #000000", + "light_modern": "keyword.operator: #000000" } }, { @@ -84,22 +574,22 @@ } }, { - "c": "SampleNamespace", - "t": "source.cs entity.name.type.namespace.cs", + "c": "setTabWithoutUpdate", + "t": "source.cs entity.name.function.cs", "r": { - "dark_plus": "entity.name.type: #4EC9B0", - "light_plus": "entity.name.type: #267F99", + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "entity.name.type: #4EC9B0", - "dark_modern": "entity.name.type: #4EC9B0", - "hc_light": "entity.name.type: #185E73", - "light_modern": "entity.name.type: #267F99" + "hc_black": "entity.name.function: #DCDCAA", + "dark_modern": "entity.name.function: #DCDCAA", + "hc_light": "entity.name.function: #5E2CBC", + "light_modern": "entity.name.function: #795E26" } }, { - "c": "{", - "t": "source.cs punctuation.curlybrace.open.cs", + "c": "(", + "t": "source.cs punctuation.parenthesis.open.cs", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -112,7 +602,21 @@ } }, { - "c": " ", + "c": "currentTab", + "t": "source.cs variable.other.readwrite.cs", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" + } + }, + { + "c": " ", "t": "source.cs", "r": { "dark_plus": "default: #D4D4D4", @@ -126,17 +630,17 @@ } }, { - "c": "class", - "t": "source.cs storage.type.class.cs", + "c": "+", + "t": "source.cs keyword.operator.arithmetic.cs", "r": { - "dark_plus": "storage.type: #569CD6", - "light_plus": "storage.type: #0000FF", - "dark_vs": "storage.type: #569CD6", - "light_vs": "storage.type: #0000FF", - "hc_black": "storage.type: #569CD6", - "dark_modern": "storage.type: #569CD6", - "hc_light": "storage.type: #0F4A85", - "light_modern": "storage.type: #0000FF" + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4", + "dark_modern": "keyword.operator: #D4D4D4", + "hc_light": "keyword.operator: #000000", + "light_modern": "keyword.operator: #000000" } }, { @@ -154,21 +658,35 @@ } }, { - "c": "TestClass", - "t": "source.cs entity.name.type.class.cs", + "c": "1", + "t": "source.cs constant.numeric.decimal.cs", "r": { - "dark_plus": "entity.name.type: #4EC9B0", - "light_plus": "entity.name.type: #267F99", + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8", + "dark_modern": "constant.numeric: #B5CEA8", + "hc_light": "constant.numeric: #096D48", + "light_modern": "constant.numeric: #098658" + } + }, + { + "c": "))", + "t": "source.cs punctuation.parenthesis.close.cs", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "entity.name.type: #4EC9B0", - "dark_modern": "entity.name.type: #4EC9B0", - "hc_light": "entity.name.type: #185E73", - "light_modern": "entity.name.type: #267F99" + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" } }, { - "c": " ", + "c": " ", "t": "source.cs", "r": { "dark_plus": "default: #D4D4D4", @@ -196,7 +714,7 @@ } }, { - "c": " ", + "c": " ", "t": "source.cs", "r": { "dark_plus": "default: #D4D4D4", @@ -210,22 +728,22 @@ } }, { - "c": "static", - "t": "source.cs storage.modifier.static.cs", + "c": "System", + "t": "source.cs variable.other.object.cs", "r": { - "dark_plus": "storage.modifier: #569CD6", - "light_plus": "storage.modifier: #0000FF", - "dark_vs": "storage.modifier: #569CD6", - "light_vs": "storage.modifier: #0000FF", - "hc_black": "storage.modifier: #569CD6", - "dark_modern": "storage.modifier: #569CD6", - "hc_light": "storage.modifier: #0F4A85", - "light_modern": "storage.modifier: #0000FF" + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" } }, { - "c": " ", - "t": "source.cs", + "c": ".", + "t": "source.cs punctuation.accessor.cs", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -238,22 +756,22 @@ } }, { - "c": "void", - "t": "source.cs keyword.type.void.cs", + "c": "Console", + "t": "source.cs variable.other.object.property.cs", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6", - "dark_modern": "keyword: #569CD6", - "hc_light": "keyword: #0F4A85", - "light_modern": "keyword: #0000FF" + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE", + "dark_modern": "variable: #9CDCFE", + "hc_light": "variable: #001080", + "light_modern": "variable: #001080" } }, { - "c": " ", - "t": "source.cs", + "c": ".", + "t": "source.cs punctuation.accessor.cs", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -266,7 +784,7 @@ } }, { - "c": "Main", + "c": "WriteLine", "t": "source.cs entity.name.function.cs", "r": { "dark_plus": "entity.name.function: #DCDCAA", @@ -294,50 +812,50 @@ } }, { - "c": "string", - "t": "source.cs keyword.type.string.cs", + "c": "\"", + "t": "source.cs string.quoted.double.cs punctuation.definition.string.begin.cs", "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6", - "dark_modern": "keyword: #569CD6", - "hc_light": "keyword: #0F4A85", - "light_modern": "keyword: #0000FF" + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "dark_modern": "string: #CE9178", + "hc_light": "string: #0F4A85", + "light_modern": "string: #A31515" } }, { - "c": "[", - "t": "source.cs punctuation.squarebracket.open.cs", + "c": "Tests #195098", + "t": "source.cs string.quoted.double.cs", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "dark_modern": "string: #CE9178", + "hc_light": "string: #0F4A85", + "light_modern": "string: #A31515" } }, { - "c": "]", - "t": "source.cs punctuation.squarebracket.close.cs", + "c": "\"", + "t": "source.cs string.quoted.double.cs punctuation.definition.string.end.cs", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178", + "dark_modern": "string: #CE9178", + "hc_light": "string: #0F4A85", + "light_modern": "string: #A31515" } }, { - "c": " ", - "t": "source.cs", + "c": ")", + "t": "source.cs punctuation.parenthesis.close.cs", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -350,22 +868,8 @@ } }, { - "c": "args", - "t": "source.cs entity.name.variable.parameter.cs", - "r": { - "dark_plus": "entity.name.variable: #9CDCFE", - "light_plus": "entity.name.variable: #001080", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "entity.name.variable: #9CDCFE", - "hc_light": "entity.name.variable: #001080", - "light_modern": "entity.name.variable: #001080" - } - }, - { - "c": ")", - "t": "source.cs punctuation.parenthesis.close.cs", + "c": ";", + "t": "source.cs punctuation.terminator.statement.cs", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -378,7 +882,7 @@ } }, { - "c": " ", + "c": " ", "t": "source.cs", "r": { "dark_plus": "default: #D4D4D4", @@ -392,8 +896,8 @@ } }, { - "c": "{", - "t": "source.cs punctuation.curlybrace.open.cs", + "c": "}", + "t": "source.cs punctuation.curlybrace.close.cs", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -421,7 +925,7 @@ }, { "c": "int", - "t": "source.cs keyword.type.int.cs", + "t": "source.cs keyword.type.cs", "r": { "dark_plus": "keyword: #569CD6", "light_plus": "keyword: #0000FF", @@ -799,7 +1303,7 @@ }, { "c": "const", - "t": "source.cs storage.modifier.const.cs", + "t": "source.cs storage.modifier.cs", "r": { "dark_plus": "storage.modifier: #569CD6", "light_plus": "storage.modifier: #0000FF", @@ -827,7 +1331,7 @@ }, { "c": "double", - "t": "source.cs keyword.type.double.cs", + "t": "source.cs keyword.type.cs", "r": { "dark_plus": "keyword: #569CD6", "light_plus": "keyword: #0000FF", @@ -1023,7 +1527,7 @@ }, { "c": "int", - "t": "source.cs keyword.type.int.cs", + "t": "source.cs keyword.type.cs", "r": { "dark_plus": "keyword: #569CD6", "light_plus": "keyword: #0000FF", @@ -1177,7 +1681,7 @@ }, { "c": "double", - "t": "source.cs keyword.type.double.cs", + "t": "source.cs keyword.type.cs", "r": { "dark_plus": "keyword: #569CD6", "light_plus": "keyword: #0000FF", @@ -1429,16 +1933,16 @@ }, { "c": " ", - "t": "source.cs comment.line.double-slash.cs punctuation.whitespace.comment.leading.cs", + "t": "source.cs punctuation.whitespace.comment.leading.cs", "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "dark_modern": "comment: #6A9955", - "hc_light": "comment: #515151", - "light_modern": "comment: #008000" + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" } }, { @@ -1751,7 +2255,7 @@ }, { "c": "public", - "t": "source.cs storage.modifier.public.cs", + "t": "source.cs storage.modifier.cs", "r": { "dark_plus": "storage.modifier: #569CD6", "light_plus": "storage.modifier: #0000FF", @@ -1779,7 +2283,7 @@ }, { "c": "void", - "t": "source.cs keyword.type.void.cs", + "t": "source.cs keyword.type.cs", "r": { "dark_plus": "keyword: #569CD6", "light_plus": "keyword: #0000FF", @@ -1947,16 +2451,16 @@ }, { "c": "new", - "t": "source.cs keyword.operator.expression.new.cs", + "t": "source.cs keyword.other.new.cs", "r": { - "dark_plus": "keyword.operator.expression: #569CD6", - "light_plus": "keyword.operator.expression: #0000FF", - "dark_vs": "keyword.operator.expression: #569CD6", - "light_vs": "keyword.operator.expression: #0000FF", - "hc_black": "keyword.operator.expression: #569CD6", - "dark_modern": "keyword.operator.expression: #569CD6", - "hc_light": "keyword.operator.expression: #0F4A85", - "light_modern": "keyword.operator.expression: #0000FF" + "dark_plus": "keyword: #569CD6", + "light_plus": "keyword: #0000FF", + "dark_vs": "keyword: #569CD6", + "light_vs": "keyword: #0000FF", + "hc_black": "keyword: #569CD6", + "dark_modern": "keyword: #569CD6", + "hc_light": "keyword: #0F4A85", + "light_modern": "keyword: #0000FF" } }, { @@ -2003,7 +2507,7 @@ }, { "c": "int", - "t": "source.cs keyword.type.int.cs", + "t": "source.cs keyword.type.cs", "r": { "dark_plus": "keyword: #569CD6", "light_plus": "keyword: #0000FF", @@ -2115,7 +2619,7 @@ }, { "c": "int", - "t": "source.cs keyword.type.int.cs", + "t": "source.cs keyword.type.cs", "r": { "dark_plus": "keyword: #569CD6", "light_plus": "keyword: #0000FF", @@ -2255,16 +2759,16 @@ }, { "c": "new", - "t": "source.cs keyword.operator.expression.new.cs", + "t": "source.cs keyword.other.new.cs", "r": { - "dark_plus": "keyword.operator.expression: #569CD6", - "light_plus": "keyword.operator.expression: #0000FF", - "dark_vs": "keyword.operator.expression: #569CD6", - "light_vs": "keyword.operator.expression: #0000FF", - "hc_black": "keyword.operator.expression: #569CD6", - "dark_modern": "keyword.operator.expression: #569CD6", - "hc_light": "keyword.operator.expression: #0F4A85", - "light_modern": "keyword.operator.expression: #0000FF" + "dark_plus": "keyword: #569CD6", + "light_plus": "keyword: #0000FF", + "dark_vs": "keyword: #569CD6", + "light_vs": "keyword: #0000FF", + "hc_black": "keyword: #569CD6", + "dark_modern": "keyword: #569CD6", + "hc_light": "keyword: #0F4A85", + "light_modern": "keyword: #0000FF" } }, { @@ -2311,7 +2815,7 @@ }, { "c": "int", - "t": "source.cs keyword.type.int.cs", + "t": "source.cs keyword.type.cs", "r": { "dark_plus": "keyword: #569CD6", "light_plus": "keyword: #0000FF", @@ -2423,7 +2927,7 @@ }, { "c": "int", - "t": "source.cs keyword.type.int.cs", + "t": "source.cs keyword.type.cs", "r": { "dark_plus": "keyword: #569CD6", "light_plus": "keyword: #0000FF", @@ -2521,16 +3025,16 @@ }, { "c": "new", - "t": "source.cs keyword.operator.expression.new.cs", + "t": "source.cs keyword.other.new.cs", "r": { - "dark_plus": "keyword.operator.expression: #569CD6", - "light_plus": "keyword.operator.expression: #0000FF", - "dark_vs": "keyword.operator.expression: #569CD6", - "light_vs": "keyword.operator.expression: #0000FF", - "hc_black": "keyword.operator.expression: #569CD6", - "dark_modern": "keyword.operator.expression: #569CD6", - "hc_light": "keyword.operator.expression: #0F4A85", - "light_modern": "keyword.operator.expression: #0000FF" + "dark_plus": "keyword: #569CD6", + "light_plus": "keyword: #0000FF", + "dark_vs": "keyword: #569CD6", + "light_vs": "keyword: #0000FF", + "hc_black": "keyword: #569CD6", + "dark_modern": "keyword: #569CD6", + "hc_light": "keyword: #0F4A85", + "light_modern": "keyword: #0000FF" } }, { @@ -2577,7 +3081,7 @@ }, { "c": "int", - "t": "source.cs keyword.type.int.cs", + "t": "source.cs keyword.type.cs", "r": { "dark_plus": "keyword: #569CD6", "light_plus": "keyword: #0000FF", diff --git a/code/extensions/vscode-colorize-tests/test/colorize-results/test_cshtml.json b/code/extensions/vscode-colorize-tests/test/colorize-results/test_cshtml.json index 4307f03ed1e..5923b8411fe 100644 --- a/code/extensions/vscode-colorize-tests/test/colorize-results/test_cshtml.json +++ b/code/extensions/vscode-colorize-tests/test/colorize-results/test_cshtml.json @@ -43,16 +43,16 @@ }, { "c": "var", - "t": "text.html.cshtml meta.structure.razor.codeblock source.cs storage.type.var.cs", + "t": "text.html.cshtml meta.structure.razor.codeblock source.cs keyword.other.var.cs", "r": { - "dark_plus": "storage.type: #569CD6", - "light_plus": "storage.type: #0000FF", - "dark_vs": "storage.type: #569CD6", - "light_vs": "storage.type: #0000FF", - "hc_black": "storage.type: #569CD6", - "dark_modern": "storage.type: #569CD6", - "hc_light": "storage.type: #0F4A85", - "light_modern": "storage.type: #0000FF" + "dark_plus": "keyword: #569CD6", + "light_plus": "keyword: #0000FF", + "dark_vs": "keyword: #569CD6", + "light_vs": "keyword: #0000FF", + "hc_black": "keyword: #569CD6", + "dark_modern": "keyword: #569CD6", + "hc_light": "keyword: #0F4A85", + "light_modern": "keyword: #0000FF" } }, { @@ -169,16 +169,16 @@ }, { "c": "var", - "t": "text.html.cshtml meta.structure.razor.codeblock source.cs storage.type.var.cs", + "t": "text.html.cshtml meta.structure.razor.codeblock source.cs keyword.other.var.cs", "r": { - "dark_plus": "storage.type: #569CD6", - "light_plus": "storage.type: #0000FF", - "dark_vs": "storage.type: #569CD6", - "light_vs": "storage.type: #0000FF", - "hc_black": "storage.type: #569CD6", - "dark_modern": "storage.type: #569CD6", - "hc_light": "storage.type: #0F4A85", - "light_modern": "storage.type: #0000FF" + "dark_plus": "keyword: #569CD6", + "light_plus": "keyword: #0000FF", + "dark_vs": "keyword: #569CD6", + "light_vs": "keyword: #0000FF", + "hc_black": "keyword: #569CD6", + "dark_modern": "keyword: #569CD6", + "hc_light": "keyword: #0F4A85", + "light_modern": "keyword: #0000FF" } }, { @@ -505,16 +505,16 @@ }, { "c": " ", - "t": "text.html.cshtml meta.structure.razor.codeblock source.cs meta.statement.if.razor meta.structure.razor.csharp.codeblock comment.line.double-slash.cs punctuation.whitespace.comment.leading.cs", + "t": "text.html.cshtml meta.structure.razor.codeblock source.cs meta.statement.if.razor meta.structure.razor.csharp.codeblock punctuation.whitespace.comment.leading.cs", "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "dark_modern": "comment: #6A9955", - "hc_light": "comment: #515151", - "light_modern": "comment: #008000" + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" } }, { @@ -561,16 +561,16 @@ }, { "c": "var", - "t": "text.html.cshtml meta.structure.razor.codeblock source.cs meta.statement.if.razor meta.structure.razor.csharp.codeblock storage.type.var.cs", + "t": "text.html.cshtml meta.structure.razor.codeblock source.cs meta.statement.if.razor meta.structure.razor.csharp.codeblock keyword.other.var.cs", "r": { - "dark_plus": "storage.type: #569CD6", - "light_plus": "storage.type: #0000FF", - "dark_vs": "storage.type: #569CD6", - "light_vs": "storage.type: #0000FF", - "hc_black": "storage.type: #569CD6", - "dark_modern": "storage.type: #569CD6", - "hc_light": "storage.type: #0F4A85", - "light_modern": "storage.type: #0000FF" + "dark_plus": "keyword: #569CD6", + "light_plus": "keyword: #0000FF", + "dark_vs": "keyword: #569CD6", + "light_vs": "keyword: #0000FF", + "hc_black": "keyword: #569CD6", + "dark_modern": "keyword: #569CD6", + "hc_light": "keyword: #0F4A85", + "light_modern": "keyword: #0000FF" } }, { @@ -757,16 +757,16 @@ }, { "c": "var", - "t": "text.html.cshtml meta.structure.razor.codeblock source.cs meta.statement.if.razor meta.structure.razor.csharp.codeblock storage.type.var.cs", + "t": "text.html.cshtml meta.structure.razor.codeblock source.cs meta.statement.if.razor meta.structure.razor.csharp.codeblock keyword.other.var.cs", "r": { - "dark_plus": "storage.type: #569CD6", - "light_plus": "storage.type: #0000FF", - "dark_vs": "storage.type: #569CD6", - "light_vs": "storage.type: #0000FF", - "hc_black": "storage.type: #569CD6", - "dark_modern": "storage.type: #569CD6", - "hc_light": "storage.type: #0F4A85", - "light_modern": "storage.type: #0000FF" + "dark_plus": "keyword: #569CD6", + "light_plus": "keyword: #0000FF", + "dark_vs": "keyword: #569CD6", + "light_vs": "keyword: #0000FF", + "hc_black": "keyword: #569CD6", + "dark_modern": "keyword: #569CD6", + "hc_light": "keyword: #0F4A85", + "light_modern": "keyword: #0000FF" } }, { @@ -939,16 +939,16 @@ }, { "c": " ", - "t": "text.html.cshtml meta.structure.razor.codeblock source.cs meta.statement.if.razor meta.structure.razor.csharp.codeblock comment.line.double-slash.cs punctuation.whitespace.comment.leading.cs", + "t": "text.html.cshtml meta.structure.razor.codeblock source.cs meta.statement.if.razor meta.structure.razor.csharp.codeblock punctuation.whitespace.comment.leading.cs", "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "dark_modern": "comment: #6A9955", - "hc_light": "comment: #515151", - "light_modern": "comment: #008000" + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" } }, { @@ -4535,4 +4535,4 @@ "light_modern": "punctuation.definition.tag: #800000" } } -] \ No newline at end of file +] diff --git a/code/package.json b/code/package.json index 6334206c7bc..a2598b49bce 100644 --- a/code/package.json +++ b/code/package.json @@ -1,7 +1,7 @@ { "name": "che-code", "version": "1.84.0", - "distro": "7c11ebfa20a4556137f851c95af40923aae11a3d", + "distro": "f69d4735763562c6fd1d2a56596fe808865081ed", "author": { "name": "Microsoft Corporation" }, @@ -13,6 +13,7 @@ "test-browser": "npx playwright install && node test/unit/browser/index.js", "test-browser-no-install": "node test/unit/browser/index.js", "test-node": "mocha test/unit/node/index.js --delay --ui=tdd --timeout=5000 --exit", + "test-extension": "vscode-test", "preinstall": "node build/npm/preinstall.js", "postinstall": "node build/npm/postinstall.js", "compile": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js compile", @@ -70,8 +71,8 @@ "@parcel/watcher": "2.1.0", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/policy-watcher": "^1.1.4", - "@vscode/proxy-agent": "^0.17.4", - "@vscode/ripgrep": "^1.15.5", + "@vscode/proxy-agent": "^0.17.5", + "@vscode/ripgrep": "^1.15.6", "@vscode/spdlog": "^0.13.11", "@vscode/sqlite3": "5.1.6-vscode", "@vscode/sudo-prompt": "9.3.1", @@ -80,8 +81,8 @@ "@vscode/windows-process-tree": "^0.5.0", "@vscode/windows-registry": "^1.1.0", "graceful-fs": "4.2.11", - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^2.2.3", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", "jschardet": "3.0.0", "kerberos": "^2.0.1", "minimist": "^1.2.6", @@ -90,6 +91,7 @@ "native-watchdog": "^1.4.1", "node-pty": "1.1.0-beta5", "tas-client-umd": "0.1.8", + "util": "^0.12.4", "v8-inspect-profiler": "^0.1.0", "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", @@ -109,11 +111,10 @@ }, "devDependencies": { "@playwright/test": "^1.37.1", - "@swc/cli": "0.1.62", "@swc/core": "1.3.62", "@types/cookie": "^0.3.3", "@types/cssnano": "^4.0.0", - "@types/debug": "4.1.5", + "@types/debug": "^4.1.5", "@types/graceful-fs": "4.1.2", "@types/gulp-postcss": "^8.0.0", "@types/gulp-svgmin": "^1.2.1", @@ -137,15 +138,16 @@ "@typescript-eslint/parser": "^5.57.0", "@vscode/gulp-electron": "^1.36.0", "@vscode/l10n-dev": "0.0.18", - "@vscode/telemetry-extractor": "^1.9.9", - "@vscode/test-web": "^0.0.41", + "@vscode/telemetry-extractor": "^1.9.10", + "@vscode/test-cli": "^0.0.3", + "@vscode/test-electron": "^2.3.5", + "@vscode/test-web": "^0.0.42", "@vscode/vscode-perf": "^0.0.14", "ansi-colors": "^3.2.3", "asar": "^3.0.3", "chromium-pickle-js": "^0.2.0", "cookie": "^0.4.0", "copy-webpack-plugin": "^11.0.0", - "cson-parser": "^1.3.3", "css-loader": "^6.7.3", "cssnano": "^4.1.11", "debounce": "^1.0.0", @@ -157,7 +159,6 @@ "eslint-plugin-local": "^1.0.0", "event-stream": "3.3.4", "fancy-log": "^1.3.3", - "fast-plist": "0.1.3", "file-loader": "^6.2.0", "glob": "^5.0.13", "gulp": "^4.0.0", @@ -180,7 +181,6 @@ "gulp-untar": "^0.0.7", "husky": "^0.13.1", "innosetup": "6.0.5", - "is": "^3.1.0", "istanbul-lib-coverage": "^3.2.0", "istanbul-lib-instrument": "^5.2.0", "istanbul-lib-report": "^3.0.0", @@ -192,16 +192,14 @@ "minimatch": "^3.0.4", "minimist": "^1.2.6", "mkdirp": "^1.0.4", - "mocha": "^9.2.2", - "mocha-junit-reporter": "^2.0.0", + "mocha": "^10.2.0", + "mocha-junit-reporter": "^2.2.1", "mocha-multi-reporters": "^1.5.1", "npm-run-all": "^4.1.5", "opn": "^6.0.0", - "optimist": "0.3.5", "p-all": "^1.0.0", "path-browserify": "^1.0.1", "pump": "^1.0.1", - "queue": "3.0.6", "rcedit": "^1.1.0", "rimraf": "^2.2.8", "sinon": "^12.0.1", @@ -214,8 +212,6 @@ "tsec": "0.2.7", "typescript": "^5.3.0-dev.20231002", "typescript-formatter": "7.1.0", - "underscore": "^1.12.1", - "util": "^0.12.4", "vscode-nls-dev": "^3.3.1", "webpack": "^5.77.0", "webpack-cli": "^5.0.1", diff --git a/code/product.json b/code/product.json index 60fe5319f96..b0fbce7e2b0 100644 --- a/code/product.json +++ b/code/product.json @@ -52,8 +52,8 @@ }, { "name": "ms-vscode.js-debug", - "version": "1.83.0", - "sha256": "8e81c3ba8e3b643c54f4dccc0b9402ea605c2bee57758bdfdda61501ea8a23d9", + "version": "1.83.1", + "sha256": "1452fdbab8d0d83ca5765bb66170d50b005c97ca4dcd13e154c3401d842a92d4", "repo": "https://github.com/microsoft/vscode-js-debug", "metadata": { "id": "25629058-ddac-4e17-abba-74678e126c5d", diff --git a/code/remote/package.json b/code/remote/package.json index ddabd9329f9..8c6c63cf846 100644 --- a/code/remote/package.json +++ b/code/remote/package.json @@ -7,16 +7,16 @@ "@microsoft/1ds-post-js": "^3.2.13", "@parcel/watcher": "2.1.0", "@vscode/iconv-lite-umd": "0.7.0", - "@vscode/proxy-agent": "^0.17.4", - "@vscode/ripgrep": "^1.15.5", + "@vscode/proxy-agent": "^0.17.5", + "@vscode/ripgrep": "^1.15.6", "@vscode/spdlog": "^0.13.11", "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-process-tree": "^0.5.0", "@vscode/windows-registry": "^1.1.0", "cookie": "^0.4.0", "graceful-fs": "4.2.11", - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^2.2.3", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", "jschardet": "3.0.0", "kerberos": "^2.0.1", "minimist": "^1.2.6", diff --git a/code/remote/yarn.lock b/code/remote/yarn.lock index 619df868f5c..d7a82e835bf 100644 --- a/code/remote/yarn.lock +++ b/code/remote/yarn.lock @@ -58,26 +58,26 @@ resolved "https://registry.yarnpkg.com/@vscode/iconv-lite-umd/-/iconv-lite-umd-0.7.0.tgz#d2f1e0664ee6036408f9743fee264ea0699b0e48" integrity sha512-bRRFxLfg5dtAyl5XyiVWz/ZBPahpOpPrNYnnHpOpUZvam4tKH35wdhP4Kj6PbM0+KdliOsPzbGWpkxcdpNB/sg== -"@vscode/proxy-agent@^0.17.4": - version "0.17.4" - resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.17.4.tgz#e3ffb63357353a428436f15a69de3453a5061f0c" - integrity sha512-tX8eidofoJlZFRWzdiiW3wyu26hgIRk8HvM/RoP1wVSu3U/As36EgGIZYG6pPnqiythRqTcsddniVNA5M39g4w== +"@vscode/proxy-agent@^0.17.5": + version "0.17.5" + resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.17.5.tgz#a59f6087a39795425b2601c9ee95bcb0338154e6" + integrity sha512-plKfR1i9ce09aro1/yvK3Ckiu84Cj5ViuLqJ/7VRT6E9w5xP2YUPcgrCy+u7FGorKZmJb+wQ1L6f/cdJ7axulw== dependencies: "@tootallnate/once" "^3.0.0" agent-base "^7.0.1" debug "^4.3.4" http-proxy-agent "^7.0.0" - https-proxy-agent "^7.0.1" + https-proxy-agent "^7.0.2" socks-proxy-agent "^8.0.1" optionalDependencies: "@vscode/windows-ca-certs" "^0.3.1" -"@vscode/ripgrep@^1.15.5": - version "1.15.5" - resolved "https://registry.yarnpkg.com/@vscode/ripgrep/-/ripgrep-1.15.5.tgz#26025884bbc3a8b40dfc29f5bda4b87b47bd7356" - integrity sha512-PVvKNEmtnlek3i4MJMaB910dz46CKQqcIY2gKR3PSlfz/ZPlSYuSuyQMS7iK20KL4hGUdSbWt964B5S5EIojqw== +"@vscode/ripgrep@^1.15.6": + version "1.15.6" + resolved "https://registry.yarnpkg.com/@vscode/ripgrep/-/ripgrep-1.15.6.tgz#17bdffc1fd0c4a034dc3e1e8203b8d07add96c0d" + integrity sha512-mCtfHqZ/g+75qDDeIPB9ST1xyJDaJornaSujuRKkB0SMZ6FMVtuKUdvvvOITR+DcKo5KOwUVuOUUpt75jOY+Yw== dependencies: - https-proxy-agent "^5.0.0" + https-proxy-agent "^7.0.2" proxy-from-env "^1.1.0" "@vscode/spdlog@^0.13.11": @@ -113,27 +113,6 @@ resolved "https://registry.yarnpkg.com/@vscode/windows-registry/-/windows-registry-1.1.0.tgz#03dace7c29c46f658588b9885b9580e453ad21f9" integrity sha512-5AZzuWJpGscyiMOed0IuyEwt6iKmV5Us7zuwCDCFYMIq7tsvooO9BUiciywsvuthGz6UG4LSpeDeCxvgMVhnIw== -agent-base@4: - version "4.2.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.0.tgz#9838b5c3392b962bad031e6a4c5e1024abec45ce" - integrity sha512-c+R/U5X+2zz2+UCrCFv6odQzJdoqI+YecuhnAJLa1zYaMc13zPfwMwZrr91Pd1DYNo/yPRbiM4WVf9whgwFsIg== - dependencies: - es6-promisify "^5.0.0" - -agent-base@6: - version "6.0.2" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" - integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== - dependencies: - debug "4" - -agent-base@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" - integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== - dependencies: - es6-promisify "^5.0.0" - agent-base@^7.0.1, agent-base@^7.0.2, agent-base@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.0.tgz#536802b76bc0b34aa50195eb2442276d613e3434" @@ -197,21 +176,7 @@ cookie@^0.4.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== -debug@3.1.0, debug@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== - dependencies: - ms "2.0.0" - -debug@4: - version "4.1.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" - integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== - dependencies: - ms "^2.1.1" - -debug@^4.3.4: +debug@4, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -242,18 +207,6 @@ end-of-stream@^1.1.0, end-of-stream@^1.4.1: dependencies: once "^1.4.0" -es6-promise@^4.0.3: - version "4.2.4" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.4.tgz#dc4221c2b16518760bd8c39a52d8f356fc00ed29" - integrity sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ== - -es6-promisify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" - integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= - dependencies: - es6-promise "^4.0.3" - expand-template@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" @@ -293,14 +246,6 @@ graceful-fs@4.2.11: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== -http-proxy-agent@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" - integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg== - dependencies: - agent-base "4" - debug "3.1.0" - http-proxy-agent@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz#e9096c5afd071a3fce56e6252bb321583c124673" @@ -309,26 +254,10 @@ http-proxy-agent@^7.0.0: agent-base "^7.1.0" debug "^4.3.4" -https-proxy-agent@^2.2.3: - version "2.2.4" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" - integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== - dependencies: - agent-base "^4.3.0" - debug "^3.1.0" - -https-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" - integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== - dependencies: - agent-base "6" - debug "4" - -https-proxy-agent@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.1.tgz#0277e28f13a07d45c663633841e20a40aaafe0ab" - integrity sha512-Eun8zV0kcYS1g19r78osiQLEFIRspRUDd9tIfBCTBPBeMieF/EsJNL8VI3xOIdYRDEkjQnqOYPsZ2DsWsVsFwQ== +https-proxy-agent@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz#e2645b846b90e96c6e6f347fb5b2e41f1590b09b" + integrity sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA== dependencies: agent-base "^7.0.2" debug "4" @@ -428,12 +357,7 @@ mkdirp@^0.5.5: dependencies: minimist "^1.2.6" -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - -ms@2.1.2, ms@^2.1.1: +ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== diff --git a/code/scripts/test-integration.bat b/code/scripts/test-integration.bat index fb9498937f6..1834f26162d 100644 --- a/code/scripts/test-integration.bat +++ b/code/scripts/test-integration.bat @@ -59,7 +59,7 @@ if %errorlevel% neq 0 exit /b %errorlevel% echo. echo ### Markdown tests -call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\markdown-language-features\test-workspace --extensionDevelopmentPath=%~dp0\..\extensions\markdown-language-features --extensionTestsPath=%~dp0\..\extensions\markdown-language-features\out\test %API_TESTS_EXTRA_ARGS% +call yarn test-extension -l markdown-language-features if %errorlevel% neq 0 exit /b %errorlevel% echo. @@ -77,16 +77,12 @@ if %errorlevel% neq 0 exit /b %errorlevel% echo. echo ### Ipynb tests -set IPYNBWORKSPACE=%TEMPDIR%\ipynb-%RANDOM% -mkdir %IPYNBWORKSPACE% -call "%INTEGRATION_TEST_ELECTRON_PATH%" %IPYNBWORKSPACE% --extensionDevelopmentPath=%~dp0\..\extensions\ipynb --extensionTestsPath=%~dp0\..\extensions\ipynb\out\test %API_TESTS_EXTRA_ARGS% +call yarn test-extension -l ipynb if %errorlevel% neq 0 exit /b %errorlevel% echo. echo ### Notebook Output tests -set NBOUTWORKSPACE=%TEMPDIR%\nbout-%RANDOM% -mkdir %NBOUTWORKSPACE% -call "%INTEGRATION_TEST_ELECTRON_PATH%" %NBOUTWORKSPACE% --extensionDevelopmentPath=%~dp0\..\extensions\notebook-renderers --extensionTestsPath=%~dp0\..\extensions\notebook-renderers\out\test %API_TESTS_EXTRA_ARGS% +call yarn test-extension -l notebook-renderers if %errorlevel% neq 0 exit /b %errorlevel% echo. diff --git a/code/scripts/test-integration.sh b/code/scripts/test-integration.sh index 85e4f80dea6..6a7a1fe4a75 100755 --- a/code/scripts/test-integration.sh +++ b/code/scripts/test-integration.sh @@ -79,7 +79,7 @@ kill_app echo echo "### Markdown tests" echo -"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/markdown-language-features/test-workspace --extensionDevelopmentPath=$ROOT/extensions/markdown-language-features --extensionTestsPath=$ROOT/extensions/markdown-language-features/out/test $API_TESTS_EXTRA_ARGS +yarn test-extension -l markdown-language-features kill_app echo @@ -97,13 +97,13 @@ kill_app echo echo "### Ipynb tests" echo -"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $(mktemp -d 2>/dev/null) --extensionDevelopmentPath=$ROOT/extensions/ipynb --extensionTestsPath=$ROOT/extensions/ipynb/out/test $API_TESTS_EXTRA_ARGS +yarn test-extension -l ipynb kill_app echo echo "### Notebook Output tests" echo -"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $(mktemp -d 2>/dev/null) --extensionDevelopmentPath=$ROOT/extensions/notebook-renderers --extensionTestsPath=$ROOT/extensions/notebook-renderers/out/test $API_TESTS_EXTRA_ARGS +yarn test-extension -l notebook-renderers kill_app echo diff --git a/code/src/main.js b/code/src/main.js index 3c0a6530e7c..887623540eb 100644 --- a/code/src/main.js +++ b/code/src/main.js @@ -201,16 +201,15 @@ function configureCommandlineSwitchesSync(cliArgs) { 'disable-hardware-acceleration', // override for the color profile to use - 'force-color-profile', - - // override which password-store is used - 'password-store' + 'force-color-profile' ]; if (process.platform === 'linux') { - // Force enable screen readers on Linux via this flag SUPPORTED_ELECTRON_SWITCHES.push('force-renderer-accessibility'); + + // override which password-store is used on Linux + SUPPORTED_ELECTRON_SWITCHES.push('password-store'); } const SUPPORTED_MAIN_PROCESS_SWITCHES = [ @@ -219,7 +218,10 @@ function configureCommandlineSwitchesSync(cliArgs) { 'enable-proposed-api', // Log level to use. Default is 'info'. Allowed values are 'error', 'warn', 'info', 'debug', 'trace', 'off'. - 'log-level' + 'log-level', + + // Use an in-memory storage for secrets + 'use-inmemory-secretstorage' ]; // Read argv config @@ -272,6 +274,12 @@ function configureCommandlineSwitchesSync(cliArgs) { } } break; + + case 'use-inmemory-secretstorage': + if (argvValue) { + process.argv.push('--use-inmemory-secretstorage'); + } + break; } } }); diff --git a/code/src/tsconfig.vscode-dts.json b/code/src/tsconfig.vscode-dts.json index b8607658396..3df2c2292ef 100644 --- a/code/src/tsconfig.vscode-dts.json +++ b/code/src/tsconfig.vscode-dts.json @@ -13,8 +13,7 @@ "forceConsistentCasingInFileNames": true, "types": [], "lib": [ - "es5", - "ES2015.Iterable" + "ES2022" ], }, "include": [ diff --git a/code/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts b/code/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts index 20837817eb1..f61963e182b 100644 --- a/code/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts +++ b/code/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts @@ -207,6 +207,10 @@ export class ActionWithDropdownActionViewItem extends ActionViewItem { this.dropdownMenuActionViewItem.render(this.element); this._register(addDisposableListener(this.element, EventType.KEY_DOWN, e => { + // If we don't have any actions then the dropdown is hidden so don't try to focus it #164050 + if (menuActionsProvider.getActions().length === 0) { + return; + } const event = new StandardKeyboardEvent(e); let handled: boolean = false; if (this.dropdownMenuActionViewItem?.isFocused() && event.equals(KeyCode.LeftArrow)) { diff --git a/code/src/vs/base/browser/ui/icons/iconSelectBox.css b/code/src/vs/base/browser/ui/icons/iconSelectBox.css index 399d5779423..90d1c972a41 100644 --- a/code/src/vs/base/browser/ui/icons/iconSelectBox.css +++ b/code/src/vs/base/browser/ui/icons/iconSelectBox.css @@ -22,8 +22,10 @@ } .icon-select-box .icon-select-icons-container > .icon-container.focused { - outline: 1px dashed var(--vscode-toolbar-hoverOutline); - outline-offset: -1px; + background-color: var(--vscode-quickInputList-focusBackground); +} + +.icon-select-box .icon-select-icons-container > .icon-container:hover { background-color: var(--vscode-toolbar-hoverBackground); } diff --git a/code/src/vs/base/browser/ui/icons/iconSelectBox.ts b/code/src/vs/base/browser/ui/icons/iconSelectBox.ts index 6b3fe557bf9..465c1dc1181 100644 --- a/code/src/vs/base/browser/ui/icons/iconSelectBox.ts +++ b/code/src/vs/base/browser/ui/icons/iconSelectBox.ts @@ -97,8 +97,10 @@ export class IconSelectBox extends Disposable { matches.push(match); } } - iconsDisposables.value = this.renderIcons(icons, matches, iconsContainer); - this.scrollableElement?.scanDomNode(); + if (icons.length) { + iconsDisposables.value = this.renderIcons(icons, matches, iconsContainer); + this.scrollableElement?.scanDomNode(); + } })); this.inputBox.inputElement.role = 'combobox'; @@ -134,10 +136,6 @@ export class IconSelectBox extends Disposable { this.setSelection(index); })); - disposables.add(dom.addDisposableListener(iconContainer, dom.EventType.MOUSE_OVER, (e: MouseEvent) => { - this.focusIcon(index); - })); - if (icon === focusedIcon) { focusedIconIndex = index; } diff --git a/code/src/vs/base/common/async.ts b/code/src/vs/base/common/async.ts index 98b6aff2b4e..192c222315b 100644 --- a/code/src/vs/base/common/async.ts +++ b/code/src/vs/base/common/async.ts @@ -1887,4 +1887,61 @@ export function createCancelableAsyncIterable(callback: (token: CancellationT }); } +export class DeferredAsyncIterableObject { + + private readonly _deferred = new DeferredPromise(); + private readonly _asyncIterable: AsyncIterableObject; + + private _errorFn: (error: Error) => void; + private _emitFn: (item: T) => void; + + constructor() { + this._asyncIterable = new AsyncIterableObject(emitter => { + + if (earlyError) { + emitter.reject(earlyError); + return; + } + if (earlyItems) { + emitter.emitMany(earlyItems); + } + this._errorFn = (error: Error) => emitter.reject(error); + this._emitFn = (item: T) => emitter.emitOne(item); + return this._deferred.p; + }); + + let earlyError: Error | undefined; + let earlyItems: T[] | undefined; + + this._emitFn = (item: T) => { + if (!earlyItems) { + earlyItems = []; + } + earlyItems.push(item); + }; + this._errorFn = (error: Error) => { + if (!earlyError) { + earlyError = error; + } + }; + } + + get asyncIterable(): AsyncIterableObject { + return this._asyncIterable; + } + + complete(): void { + this._deferred.complete(); + } + + error(error: Error): void { + this._errorFn(error); + this._deferred.complete(); + } + + emit(item: T): void { + this._emitFn(item); + } +} + //#endregion diff --git a/code/src/vs/base/common/htmlContent.ts b/code/src/vs/base/common/htmlContent.ts index b62f09f07ca..d16621579af 100644 --- a/code/src/vs/base/common/htmlContent.ts +++ b/code/src/vs/base/common/htmlContent.ts @@ -57,7 +57,7 @@ export class MarkdownString implements IMarkdownString { } appendText(value: string, newlineStyle: MarkdownStringTextNewlineStyle = MarkdownStringTextNewlineStyle.Paragraph): MarkdownString { - this.value += escapeMarkdownSyntaxTokens(this.supportThemeIcons ? escapeIcons(value) : value) + this.value += escapeMarkdownSyntaxTokens(this.supportThemeIcons ? escapeIcons(value) : value) // CodeQL [SM02383] The Markdown is fully sanitized after being rendered. .replace(/([ \t]+)/g, (_match, g1) => ' '.repeat(g1.length)) // CodeQL [SM02383] The Markdown is fully sanitized after being rendered. .replace(/\>/gm, '\\>') // CodeQL [SM02383] The Markdown is fully sanitized after being rendered. .replace(/\n/g, newlineStyle === MarkdownStringTextNewlineStyle.Break ? '\\\n' : '\n\n'); // CodeQL [SM02383] The Markdown is fully sanitized after being rendered. diff --git a/code/src/vs/base/node/id.ts b/code/src/vs/base/node/id.ts index a5ea6a2bb0d..42a7f771358 100644 --- a/code/src/vs/base/node/id.ts +++ b/code/src/vs/base/node/id.ts @@ -7,6 +7,7 @@ import { networkInterfaces } from 'os'; import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; import * as uuid from 'vs/base/common/uuid'; import { getMac } from 'vs/base/node/macAddress'; +import { isWindows } from 'vs/base/common/platform'; // http://www.techrepublic.com/blog/data-center/mac-address-scorecard-for-common-virtual-machine-platforms/ // VMware ESX 3, Server, Workstation, Player 00-50-56, 00-0C-29, 00-05-69 @@ -99,3 +100,17 @@ async function getMacMachineId(errorLogger: (error: any) => void): Promise void): Promise { + if (isWindows) { + const Registry = await import('@vscode/windows-registry'); + try { + return Registry.GetStringRegKey('HKEY_LOCAL_MACHINE', SQM_KEY, 'MachineId') || ''; + } catch (err) { + errorLogger(err); + return ''; + } + } + return ''; +} diff --git a/code/src/vs/base/parts/sandbox/electron-sandbox/globals.ts b/code/src/vs/base/parts/sandbox/electron-sandbox/globals.ts index 614c1385b2a..d0ae5a5ff0e 100644 --- a/code/src/vs/base/parts/sandbox/electron-sandbox/globals.ts +++ b/code/src/vs/base/parts/sandbox/electron-sandbox/globals.ts @@ -120,3 +120,32 @@ export const ipcMessagePort: IpcMessagePort = globals.vscode.ipcMessagePort; export const webFrame: WebFrame = globals.vscode.webFrame; export const process: ISandboxNodeProcess = globals.vscode.process; export const context: ISandboxContext = globals.vscode.context; + +export interface IGlobalsSlim { + readonly ipcRenderer: Pick; + readonly webFrame: import('vs/base/parts/sandbox/electron-sandbox/electronTypes').WebFrame; +} + +/** + * Get the globals that are available in the given window. Since + * this method supports auxiliary windows, only a subset of globals + * is returned. + */ +export function getGlobals(win: Window): IGlobalsSlim | undefined { + if (win === window) { + return { ipcRenderer, webFrame }; + } + + const auxiliaryWindowCandidate = win as unknown as { + vscode: { + ipcRenderer: Pick; + webFrame: import('vs/base/parts/sandbox/electron-sandbox/electronTypes').WebFrame; + }; + }; + + if (auxiliaryWindowCandidate?.vscode?.ipcRenderer && auxiliaryWindowCandidate?.vscode?.webFrame) { + return auxiliaryWindowCandidate.vscode; + } + + return undefined; +} diff --git a/code/src/vs/base/parts/sandbox/electron-sandbox/preload-slim.js b/code/src/vs/base/parts/sandbox/electron-sandbox/preload-slim.js index 05d390e37b7..7b6256756ac 100644 --- a/code/src/vs/base/parts/sandbox/electron-sandbox/preload-slim.js +++ b/code/src/vs/base/parts/sandbox/electron-sandbox/preload-slim.js @@ -7,7 +7,7 @@ (function () { 'use strict'; - const { ipcRenderer, contextBridge } = require('electron'); + const { ipcRenderer, webFrame, contextBridge } = require('electron'); /** * @param {string} channel @@ -27,11 +27,10 @@ * A minimal set of methods exposed from Electron's `ipcRenderer` * to support communication to main process. * - * @typedef {Pick} IpcRenderer + * @typedef {Pick} IpcRenderer * * @type {IpcRenderer} */ - ipcRenderer: { /** @@ -42,6 +41,34 @@ if (validateIPC(channel)) { ipcRenderer.send(channel, ...args); } + }, + + /** + * @param {string} channel + * @param {any[]} args + * @returns {Promise | never} + */ + invoke(channel, ...args) { + if (validateIPC(channel)) { + return ipcRenderer.invoke(channel, ...args); + } + } + }, + + /** + * Support for subset of methods of Electron's `webFrame` type. + * + * @type {import('./electronTypes').WebFrame} + */ + webFrame: { + + /** + * @param {number} level + */ + setZoomLevel(level) { + if (typeof level === 'number') { + webFrame.setZoomLevel(level); + } } } }; diff --git a/code/src/vs/base/test/browser/dom.test.ts b/code/src/vs/base/test/browser/dom.test.ts index 50ed9cf5e73..41529f4868c 100644 --- a/code/src/vs/base/test/browser/dom.test.ts +++ b/code/src/vs/base/test/browser/dom.test.ts @@ -88,7 +88,7 @@ suite('dom', () => { assert(!div.firstChild); }); - test('should buld nodes with id', () => { + test('should build nodes with id', () => { const div = $('div#foo'); assert(div); assert(div instanceof HTMLElement); @@ -96,7 +96,7 @@ suite('dom', () => { assert.strictEqual(div.id, 'foo'); }); - test('should buld nodes with class-name', () => { + test('should build nodes with class-name', () => { const div = $('div.foo'); assert(div); assert(div instanceof HTMLElement); diff --git a/code/src/vs/base/test/node/id.test.ts b/code/src/vs/base/test/node/id.test.ts index ed4b0d0cb2f..bdec456edde 100644 --- a/code/src/vs/base/test/node/id.test.ts +++ b/code/src/vs/base/test/node/id.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { getMachineId } from 'vs/base/node/id'; +import { getMachineId, getSqmMachineId } from 'vs/base/node/id'; import { getMac } from 'vs/base/node/macAddress'; import { flakySuite } from 'vs/base/test/node/testUtils'; @@ -17,6 +17,13 @@ flakySuite('ID', () => { assert.strictEqual(errors.length, 0); }); + test('getSqmId', async function () { + const errors = []; + const id = await getSqmMachineId(err => errors.push(err)); + assert.ok(typeof id === 'string'); + assert.strictEqual(errors.length, 0); + }); + test('getMac', async () => { const macAddress = getMac(); assert.ok(/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/.test(macAddress), `Expected a MAC address, got: ${macAddress}`); diff --git a/code/src/vs/code/electron-main/app.ts b/code/src/vs/code/electron-main/app.ts index 642abb829c2..fb758311a1a 100644 --- a/code/src/vs/code/electron-main/app.ts +++ b/code/src/vs/code/electron-main/app.ts @@ -105,7 +105,7 @@ import { ExtensionsScannerService } from 'vs/platform/extensionManagement/node/e import { UserDataProfilesHandler } from 'vs/platform/userDataProfile/electron-main/userDataProfilesHandler'; import { ProfileStorageChangesListenerChannel } from 'vs/platform/userDataProfile/electron-main/userDataProfileStorageIpc'; import { Promises, RunOnceScheduler, runWhenIdle } from 'vs/base/common/async'; -import { resolveMachineId } from 'vs/platform/telemetry/electron-main/telemetryUtils'; +import { resolveMachineId, resolveSqmId } from 'vs/platform/telemetry/electron-main/telemetryUtils'; import { ExtensionsProfileScannerService } from 'vs/platform/extensionManagement/node/extensionsProfileScannerService'; import { LoggerChannel } from 'vs/platform/log/electron-main/logIpc'; import { ILoggerMainService } from 'vs/platform/log/electron-main/loggerService'; @@ -118,7 +118,8 @@ import { ElectronPtyHostStarter } from 'vs/platform/terminal/electron-main/elect import { PtyHostService } from 'vs/platform/terminal/node/ptyHostService'; import { NODE_REMOTE_RESOURCE_CHANNEL_NAME, NODE_REMOTE_RESOURCE_IPC_METHOD_NAME, NodeRemoteResourceResponse, NodeRemoteResourceRouter } from 'vs/platform/remote/common/electronRemoteResources'; import { Lazy } from 'vs/base/common/lazy'; -import { AuxiliaryWindow } from 'vs/platform/windows/electron-main/auxiliaryWindow'; +import { IAuxiliaryWindowsMainService } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows'; +import { AuxiliaryWindowsMainService } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService'; /** * The main VS Code application. There will only ever be one instance, @@ -132,6 +133,7 @@ export class CodeApplication extends Disposable { }; private windowsMainService: IWindowsMainService | undefined; + private auxiliaryWindowsMainService: IAuxiliaryWindowsMainService | undefined; private nativeHostMainService: INativeHostMainService | undefined; constructor( @@ -386,7 +388,7 @@ export class CodeApplication extends Disposable { // Child Window: delegate to `AuxiliaryWindow` class const isChildWindow = contents?.opener?.url.startsWith(`${Schemas.vscodeFileResource}://${VSCODE_AUTHORITY}/`); if (isChildWindow) { - this.mainInstantiationService.createInstance(AuxiliaryWindow, contents); + this.auxiliaryWindowsMainService?.registerWindow(contents); } // Block any in-page navigation @@ -406,7 +408,7 @@ export class CodeApplication extends Disposable { return { action: 'allow', - overrideBrowserWindowOptions: AuxiliaryWindow.open(this.mainInstantiationService) + overrideBrowserWindowOptions: this.auxiliaryWindowsMainService?.createWindow() }; } @@ -459,6 +461,8 @@ export class CodeApplication extends Disposable { //#region Bootstrap IPC Handlers + validatedIpcMain.handle('vscode:getWindowId', event => Promise.resolve(event.sender.id)); + validatedIpcMain.handle('vscode:fetchShellEnv', event => { // Prefer to use the args and env from the target window @@ -592,14 +596,17 @@ export class CodeApplication extends Disposable { // Resolve unique machine ID this.logService.trace('Resolving machine identifier...'); - const machineId = await resolveMachineId(this.stateService, this.logService); + const [machineId, sqmId] = await Promise.all([ + resolveMachineId(this.stateService, this.logService), + resolveSqmId(this.stateService, this.logService) + ]); this.logService.trace(`Resolved machine identifier: ${machineId}`); // Shared process - const { sharedProcessReady, sharedProcessClient } = this.setupSharedProcess(machineId); + const { sharedProcessReady, sharedProcessClient } = this.setupSharedProcess(machineId, sqmId); // Services - const appInstantiationService = await this.initServices(machineId, sharedProcessReady); + const appInstantiationService = await this.initServices(machineId, sqmId, sharedProcessReady); // Auth Handler this._register(appInstantiationService.createInstance(ProxyAuthHandler)); @@ -793,7 +800,13 @@ export class CodeApplication extends Disposable { } if (checkboxChecked) { - windowsMainService.sendToOpeningWindow('vscode:disablePromptForProtocolHandling', uri.authority === Schemas.file ? 'local' : 'remote'); + // Due to https://github.com/microsoft/vscode/issues/195436, we can only + // update settings from within a window. But we do not know if a window + // is about to open or can already handle the request, so we have to send + // to any current window and any newly opening window. + const request = { channel: 'vscode:disablePromptForProtocolHandling', args: uri.authority === Schemas.file ? 'local' : 'remote' }; + windowsMainService.sendToFocused(request.channel, request.args); + windowsMainService.sendToOpeningWindow(request.channel, request.args); } return false; // not blocked by user choice @@ -946,8 +959,8 @@ export class CodeApplication extends Disposable { return false; } - private setupSharedProcess(machineId: string): { sharedProcessReady: Promise; sharedProcessClient: Promise } { - const sharedProcess = this._register(this.mainInstantiationService.createInstance(SharedProcess, machineId)); + private setupSharedProcess(machineId: string, sqmId: string): { sharedProcessReady: Promise; sharedProcessClient: Promise } { + const sharedProcess = this._register(this.mainInstantiationService.createInstance(SharedProcess, machineId, sqmId)); const sharedProcessClient = (async () => { this.logService.trace('Main->SharedProcess#connect'); @@ -968,7 +981,7 @@ export class CodeApplication extends Disposable { return { sharedProcessReady, sharedProcessClient }; } - private async initServices(machineId: string, sharedProcessReady: Promise): Promise { + private async initServices(machineId: string, sqmId: string, sharedProcessReady: Promise): Promise { const services = new ServiceCollection(); // Update @@ -991,7 +1004,8 @@ export class CodeApplication extends Disposable { } // Windows - services.set(IWindowsMainService, new SyncDescriptor(WindowsMainService, [machineId, this.userEnv], false)); + services.set(IWindowsMainService, new SyncDescriptor(WindowsMainService, [machineId, sqmId, this.userEnv], false)); + services.set(IAuxiliaryWindowsMainService, new SyncDescriptor(AuxiliaryWindowsMainService, undefined, false)); // Dialogs const dialogMainService = new DialogMainService(this.logService, this.productService); @@ -1070,7 +1084,7 @@ export class CodeApplication extends Disposable { const isInternal = isInternalTelemetry(this.productService, this.configurationService); const channel = getDelayedChannel(sharedProcessReady.then(client => client.getChannel('telemetryAppender'))); const appender = new TelemetryAppenderClient(channel); - const commonProperties = resolveCommonProperties(release(), hostname(), process.arch, this.productService.commit, this.productService.version, machineId, isInternal); + const commonProperties = resolveCommonProperties(release(), hostname(), process.arch, this.productService.commit, this.productService.version, machineId, sqmId, isInternal); const piiPaths = getPiiPathsFromEnvironment(this.environmentMainService); const config: ITelemetryServiceConfig = { appenders: [appender], commonProperties, piiPaths, sendErrorTelemetry: true }; @@ -1210,6 +1224,7 @@ export class CodeApplication extends Disposable { private async openFirstWindow(accessor: ServicesAccessor, initialProtocolUrls: IInitialProtocolUrls | undefined): Promise { const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); + this.auxiliaryWindowsMainService = accessor.get(IAuxiliaryWindowsMainService); const context = isLaunchedFromCli(process.env) ? OpenContext.CLI : OpenContext.DESKTOP; const args = this.environmentMainService.args; diff --git a/code/src/vs/code/node/cliProcessMain.ts b/code/src/vs/code/node/cliProcessMain.ts index 85daa51a6e1..b2861976c11 100644 --- a/code/src/vs/code/node/cliProcessMain.ts +++ b/code/src/vs/code/node/cliProcessMain.ts @@ -57,7 +57,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { UserDataProfilesReadonlyService } from 'vs/platform/userDataProfile/node/userDataProfile'; -import { resolveMachineId } from 'vs/platform/telemetry/node/telemetryUtils'; +import { resolveMachineId, resolveSqmId } from 'vs/platform/telemetry/node/telemetryUtils'; import { ExtensionsProfileScannerService } from 'vs/platform/extensionManagement/node/extensionsProfileScannerService'; import { LogService } from 'vs/platform/log/common/logService'; import { LoggerService } from 'vs/platform/log/node/loggerService'; @@ -184,6 +184,7 @@ class CliMain extends Disposable { logService.error(error); } } + const sqmId = await resolveSqmId(stateService, logService); // Initialize user data profiles after initializing the state userDataProfilesService.init(); @@ -219,7 +220,7 @@ class CliMain extends Disposable { const config: ITelemetryServiceConfig = { appenders, sendErrorTelemetry: false, - commonProperties: resolveCommonProperties(release(), hostname(), process.arch, productService.commit, productService.version, machineId, isInternal), + commonProperties: resolveCommonProperties(release(), hostname(), process.arch, productService.commit, productService.version, machineId, sqmId, isInternal), piiPaths: getPiiPathsFromEnvironment(environmentService) }; diff --git a/code/src/vs/code/node/sharedProcess/sharedProcessMain.ts b/code/src/vs/code/node/sharedProcess/sharedProcessMain.ts index 6a26fb9ad17..557f8b6a6fe 100644 --- a/code/src/vs/code/node/sharedProcess/sharedProcessMain.ts +++ b/code/src/vs/code/node/sharedProcess/sharedProcessMain.ts @@ -303,7 +303,7 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter { telemetryService = new TelemetryService({ appenders, - commonProperties: resolveCommonProperties(release(), hostname(), process.arch, productService.commit, productService.version, this.configuration.machineId, internalTelemetry), + commonProperties: resolveCommonProperties(release(), hostname(), process.arch, productService.commit, productService.version, this.configuration.machineId, this.configuration.sqmId, internalTelemetry), sendErrorTelemetry: true, piiPaths: getPiiPathsFromEnvironment(environmentService), }, configurationService, productService); diff --git a/code/src/vs/editor/common/config/editorOptions.ts b/code/src/vs/editor/common/config/editorOptions.ts index d5938f90014..e9fc22f531c 100644 --- a/code/src/vs/editor/common/config/editorOptions.ts +++ b/code/src/vs/editor/common/config/editorOptions.ts @@ -1305,9 +1305,9 @@ class EditorAccessibilitySupport extends BaseEditorOption { - const originalText = current.original.toOffsetRange().slice(originalLines).map(l => l.trim()).join('\n'); - return originalText.length >= 10; + const lines = current.original.toOffsetRange().slice(originalLines).map(l => l.trim()); + const originalText = lines.join('\n'); + return originalText.length >= 15 && countWhere(lines, l => l.length >= 2) >= 2; }); moves = removeMovesInSameDiff(changes, moves); return moves; } +function countWhere(arr: T[], predicate: (t: T) => boolean): number { + let count = 0; + for (const t of arr) { + if (predicate(t)) { + count++; + } + } + return count; +} + function computeMovesFromSimpleDeletionsToSimpleInsertions( changes: DetailedLineRangeMapping[], originalLines: string[], @@ -307,9 +318,9 @@ function joinCloseConsecutiveMoves(moves: LineRangeMapping[]): LineRangeMapping[ function removeMovesInSameDiff(changes: DetailedLineRangeMapping[], moves: LineRangeMapping[]) { const changesMonotonous = new MonotonousArray(changes); moves = moves.filter(m => { - const diffBeforeEndOfMoveOriginal = changesMonotonous.findLastMonotonous(c => c.original.endLineNumberExclusive < m.original.endLineNumberExclusive) + const diffBeforeEndOfMoveOriginal = changesMonotonous.findLastMonotonous(c => c.original.startLineNumber < m.original.endLineNumberExclusive) || new LineRangeMapping(new LineRange(1, 1), new LineRange(1, 1)); - const diffBeforeEndOfMoveModified = findLastMonotonous(changes, c => c.modified.endLineNumberExclusive < m.modified.endLineNumberExclusive); + const diffBeforeEndOfMoveModified = findLastMonotonous(changes, c => c.modified.startLineNumber < m.modified.endLineNumberExclusive); const differentDiffs = diffBeforeEndOfMoveOriginal !== diffBeforeEndOfMoveModified; return differentDiffs; diff --git a/code/src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts b/code/src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts index 64b3b75d317..da23e59f332 100644 --- a/code/src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts +++ b/code/src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts @@ -12,6 +12,9 @@ import { LinesSliceCharSequence } from 'vs/editor/common/diff/defaultLinesDiffCo export function optimizeSequenceDiffs(sequence1: ISequence, sequence2: ISequence, sequenceDiffs: SequenceDiff[]): SequenceDiff[] { let result = sequenceDiffs; result = joinSequenceDiffsByShifting(sequence1, sequence2, result); + // Sometimes, calling this function twice improves the result. + // Uncomment the second invocation and run the tests to see the difference. + result = joinSequenceDiffsByShifting(sequence1, sequence2, result); result = shiftSequenceDiffs(sequence1, sequence2, result); return result; } diff --git a/code/src/vs/editor/common/diff/defaultLinesDiffComputer/linesSliceCharSequence.ts b/code/src/vs/editor/common/diff/defaultLinesDiffComputer/linesSliceCharSequence.ts index 822a6310bf0..fe408e95581 100644 --- a/code/src/vs/editor/common/diff/defaultLinesDiffComputer/linesSliceCharSequence.ts +++ b/code/src/vs/editor/common/diff/defaultLinesDiffComputer/linesSliceCharSequence.ts @@ -175,6 +175,7 @@ const enum CharBoundaryCategory { WordNumber, End, Other, + Separator, Space, LineBreakCR, LineBreakLF, @@ -186,6 +187,7 @@ const score: Record = { [CharBoundaryCategory.WordNumber]: 0, [CharBoundaryCategory.End]: 10, [CharBoundaryCategory.Other]: 2, + [CharBoundaryCategory.Separator]: 3, [CharBoundaryCategory.Space]: 3, [CharBoundaryCategory.LineBreakCR]: 10, [CharBoundaryCategory.LineBreakLF]: 10, @@ -210,6 +212,8 @@ function getCategory(charCode: number): CharBoundaryCategory { return CharBoundaryCategory.WordNumber; } else if (charCode === -1) { return CharBoundaryCategory.End; + } else if (charCode === CharCode.Comma || charCode === CharCode.Semicolon) { + return CharBoundaryCategory.Separator; } else { return CharBoundaryCategory.Other; } diff --git a/code/src/vs/editor/common/languages.ts b/code/src/vs/editor/common/languages.ts index 685a470705b..7c9a7a5a308 100644 --- a/code/src/vs/editor/common/languages.ts +++ b/code/src/vs/editor/common/languages.ts @@ -1792,6 +1792,7 @@ export interface PendingCommentThread { range: IRange; uri: URI; owner: string; + isReply: boolean; } /** diff --git a/code/src/vs/editor/common/services/getIconClasses.ts b/code/src/vs/editor/common/services/getIconClasses.ts index 464555c63fb..bc3da1e5e6c 100644 --- a/code/src/vs/editor/common/services/getIconClasses.ts +++ b/code/src/vs/editor/common/services/getIconClasses.ts @@ -37,8 +37,13 @@ export function getIconClasses(modelService: IModelService, languageService: ILa } } + // Root Folders + if (fileKind === FileKind.ROOT_FOLDER) { + classes.push(`${name}-root-name-folder-icon`); + } + // Folders - if (fileKind === FileKind.FOLDER) { + else if (fileKind === FileKind.FOLDER) { classes.push(`${name}-name-folder-icon`); } diff --git a/code/src/vs/editor/contrib/codeAction/browser/codeAction.ts b/code/src/vs/editor/contrib/codeAction/browser/codeAction.ts index 1765a6cf79b..a3c6c534d64 100644 --- a/code/src/vs/editor/contrib/codeAction/browser/codeAction.ts +++ b/code/src/vs/editor/contrib/codeAction/browser/codeAction.ts @@ -52,6 +52,11 @@ class ManagedCodeActionSet extends Disposable implements CodeActionSet { } private static codeActionsComparator({ action: a }: CodeActionItem, { action: b }: CodeActionItem): number { + if (a.isAI && !b.isAI) { + return 1; + } else if (!a.isAI && b.isAI) { + return -1; + } if (isNonEmptyArray(a.diagnostics)) { return isNonEmptyArray(b.diagnostics) ? ManagedCodeActionSet.codeActionsPreferredComparator(a, b) : -1; } else if (isNonEmptyArray(b.diagnostics)) { diff --git a/code/src/vs/editor/test/node/diffing/fixtures.test.ts b/code/src/vs/editor/test/node/diffing/fixtures.test.ts index 18a949c6538..e200d808c22 100644 --- a/code/src/vs/editor/test/node/diffing/fixtures.test.ts +++ b/code/src/vs/editor/test/node/diffing/fixtures.test.ts @@ -42,7 +42,7 @@ suite('diffing fixtures', () => { const diffingAlgo = diffingAlgoName === 'legacy' ? new LegacyLinesDiffComputer() : new DefaultLinesDiffComputer(); const ignoreTrimWhitespace = folder.indexOf('trimws') >= 0; - const diff = diffingAlgo.computeDiff(firstContentLines, secondContentLines, { ignoreTrimWhitespace, maxComputationTimeMs: Number.MAX_SAFE_INTEGER, computeMoves: false }); + const diff = diffingAlgo.computeDiff(firstContentLines, secondContentLines, { ignoreTrimWhitespace, maxComputationTimeMs: Number.MAX_SAFE_INTEGER, computeMoves: true }); function getDiffs(changes: readonly DetailedLineRangeMapping[]): IDetailedDiff[] { return changes.map(c => ({ @@ -120,7 +120,7 @@ suite('diffing fixtures', () => { } test(`test`, () => { - runTest('invalid-diff-trimws', 'advanced'); + runTest('shifting-twice', 'advanced'); }); for (const folder of folders) { @@ -155,5 +155,5 @@ interface IMoveInfo { originalRange: string; // [startLineNumber, endLineNumberExclusive) modifiedRange: string; // [startLineNumber, endLineNumberExclusive) - changes?: IDetailedDiff[]; + changes: IDetailedDiff[]; } diff --git a/code/src/vs/editor/test/node/diffing/fixtures/difficult-move/advanced.expected.diff.json b/code/src/vs/editor/test/node/diffing/fixtures/difficult-move/advanced.expected.diff.json index 7686d146b16..372deb16284 100644 --- a/code/src/vs/editor/test/node/diffing/fixtures/difficult-move/advanced.expected.diff.json +++ b/code/src/vs/editor/test/node/diffing/fixtures/difficult-move/advanced.expected.diff.json @@ -48,5 +48,31 @@ } ] } + ], + "moves": [ + { + "originalRange": "[223,226)", + "modifiedRange": "[158,161)", + "changes": [ + { + "originalRange": "[223,226)", + "modifiedRange": "[158,161)", + "innerChanges": [ + { + "originalRange": "[223,1 -> 223,3]", + "modifiedRange": "[158,1 -> 158,1]" + }, + { + "originalRange": "[224,1 -> 224,3]", + "modifiedRange": "[159,1 -> 159,1]" + }, + { + "originalRange": "[225,1 -> 225,3]", + "modifiedRange": "[160,1 -> 160,1]" + } + ] + } + ] + } ] } \ No newline at end of file diff --git a/code/src/vs/editor/test/node/diffing/fixtures/false-positive-move/1.tst b/code/src/vs/editor/test/node/diffing/fixtures/false-positive-move/1.tst new file mode 100644 index 00000000000..e2bcbba1fb6 --- /dev/null +++ b/code/src/vs/editor/test/node/diffing/fixtures/false-positive-move/1.tst @@ -0,0 +1,29 @@ +includeIssuesWithoutMilestone: boolean = false, +): Promise> { + const milestones: ItemsResponseResult = await this.fetchPagedData( + options, + 'milestoneIssuesKey', + PagedDataType.Milestones, + PRType.All + ); + if (includeIssuesWithoutMilestone) { + const additionalIssues: ItemsResponseResult = await this.fetchPagedData( + options, + 'noMilestoneIssuesKey', + PagedDataType.IssuesWithoutMilestone, + PRType.All + ); + milestones.items.push({ + milestone: { + createdAt: new Date(0).toDateString(), + id: '', + title: NO_MILESTONE, + }, + issues: await Promise.all(additionalIssues.items.map(async (issue) => { + const githubRepository = await this.getRepoForIssue(issue); + return new IssueModel(githubRepository, githubRepository.remote, issue); + })), + }); + } + return milestones; +} \ No newline at end of file diff --git a/code/src/vs/editor/test/node/diffing/fixtures/false-positive-move/2.tst b/code/src/vs/editor/test/node/diffing/fixtures/false-positive-move/2.tst new file mode 100644 index 00000000000..709085b12d8 --- /dev/null +++ b/code/src/vs/editor/test/node/diffing/fixtures/false-positive-move/2.tst @@ -0,0 +1,34 @@ +includeIssuesWithoutMilestone: boolean = false, +): Promise> { + try { + const milestones: ItemsResponseResult = await this.fetchPagedData( + options, + 'milestoneIssuesKey', + PagedDataType.Milestones, + PRType.All + ); + if (includeIssuesWithoutMilestone) { + const additionalIssues: ItemsResponseResult = await this.fetchPagedData( + options, + 'noMilestoneIssuesKey', + PagedDataType.IssuesWithoutMilestone, + PRType.All + ); + milestones.items.push({ + milestone: { + createdAt: new Date(0).toDateString(), + id: '', + title: NO_MILESTONE, + }, + issues: await Promise.all(additionalIssues.items.map(async (issue) => { + const githubRepository = await this.getRepoForIssue(issue); + return new IssueModel(githubRepository, githubRepository.remote, issue); + })), + }); + } + return milestones; + } catch (e) { + Logger.error(`Error fetching milestone issues: ${e instanceof Error ? e.message : e}`, FolderRepositoryManager.ID); + return { hasMorePages: false, hasUnsearchedRepositories: false, items: [] }; + } +} diff --git a/code/src/vs/editor/test/node/diffing/fixtures/false-positive-move/advanced.expected.diff.json b/code/src/vs/editor/test/node/diffing/fixtures/false-positive-move/advanced.expected.diff.json new file mode 100644 index 00000000000..4aa43305dea --- /dev/null +++ b/code/src/vs/editor/test/node/diffing/fixtures/false-positive-move/advanced.expected.diff.json @@ -0,0 +1,140 @@ +{ + "original": { + "content": "includeIssuesWithoutMilestone: boolean = false,\n): Promise> {\n\tconst milestones: ItemsResponseResult = await this.fetchPagedData(\n\t\toptions,\n\t\t'milestoneIssuesKey',\n\t\tPagedDataType.Milestones,\n\t\tPRType.All\n\t);\n\tif (includeIssuesWithoutMilestone) {\n\t\tconst additionalIssues: ItemsResponseResult = await this.fetchPagedData(\n\t\t\toptions,\n\t\t\t'noMilestoneIssuesKey',\n\t\t\tPagedDataType.IssuesWithoutMilestone,\n\t\t\tPRType.All\n\t\t);\n\t\tmilestones.items.push({\n\t\t\tmilestone: {\n\t\t\t\tcreatedAt: new Date(0).toDateString(),\n\t\t\t\tid: '',\n\t\t\t\ttitle: NO_MILESTONE,\n\t\t\t},\n\t\t\tissues: await Promise.all(additionalIssues.items.map(async (issue) => {\n\t\t\t\tconst githubRepository = await this.getRepoForIssue(issue);\n\t\t\t\treturn new IssueModel(githubRepository, githubRepository.remote, issue);\n\t\t\t})),\n\t\t});\n\t}\n\treturn milestones;\n}", + "fileName": "./1.tst" + }, + "modified": { + "content": "includeIssuesWithoutMilestone: boolean = false,\n): Promise> {\n\ttry {\n\t\tconst milestones: ItemsResponseResult = await this.fetchPagedData(\n\t\t\toptions,\n\t\t\t'milestoneIssuesKey',\n\t\t\tPagedDataType.Milestones,\n\t\t\tPRType.All\n\t\t);\n\t\tif (includeIssuesWithoutMilestone) {\n\t\t\tconst additionalIssues: ItemsResponseResult = await this.fetchPagedData(\n\t\t\t\toptions,\n\t\t\t\t'noMilestoneIssuesKey',\n\t\t\t\tPagedDataType.IssuesWithoutMilestone,\n\t\t\t\tPRType.All\n\t\t\t);\n\t\t\tmilestones.items.push({\n\t\t\t\tmilestone: {\n\t\t\t\t\tcreatedAt: new Date(0).toDateString(),\n\t\t\t\t\tid: '',\n\t\t\t\t\ttitle: NO_MILESTONE,\n\t\t\t\t},\n\t\t\t\tissues: await Promise.all(additionalIssues.items.map(async (issue) => {\n\t\t\t\t\tconst githubRepository = await this.getRepoForIssue(issue);\n\t\t\t\t\treturn new IssueModel(githubRepository, githubRepository.remote, issue);\n\t\t\t\t})),\n\t\t\t});\n\t\t}\n\t\treturn milestones;\n\t} catch (e) {\n\t\tLogger.error(`Error fetching milestone issues: ${e instanceof Error ? e.message : e}`, FolderRepositoryManager.ID);\n\t\treturn { hasMorePages: false, hasUnsearchedRepositories: false, items: [] };\n\t}\n}\n", + "fileName": "./2.tst" + }, + "diffs": [ + { + "originalRange": "[3,29)", + "modifiedRange": "[3,34)", + "innerChanges": [ + { + "originalRange": "[3,1 -> 3,1]", + "modifiedRange": "[3,1 -> 4,1]" + }, + { + "originalRange": "[3,1 -> 3,1]", + "modifiedRange": "[4,1 -> 4,2]" + }, + { + "originalRange": "[4,1 -> 4,1]", + "modifiedRange": "[5,1 -> 5,2]" + }, + { + "originalRange": "[5,1 -> 5,1]", + "modifiedRange": "[6,1 -> 6,2]" + }, + { + "originalRange": "[6,1 -> 6,1]", + "modifiedRange": "[7,1 -> 7,2]" + }, + { + "originalRange": "[7,1 -> 7,1]", + "modifiedRange": "[8,1 -> 8,2]" + }, + { + "originalRange": "[8,1 -> 8,1]", + "modifiedRange": "[9,1 -> 9,2]" + }, + { + "originalRange": "[9,1 -> 9,1]", + "modifiedRange": "[10,1 -> 10,2]" + }, + { + "originalRange": "[10,1 -> 10,1]", + "modifiedRange": "[11,1 -> 11,2]" + }, + { + "originalRange": "[11,1 -> 11,1]", + "modifiedRange": "[12,1 -> 12,2]" + }, + { + "originalRange": "[12,1 -> 12,1]", + "modifiedRange": "[13,1 -> 13,2]" + }, + { + "originalRange": "[13,1 -> 13,1]", + "modifiedRange": "[14,1 -> 14,2]" + }, + { + "originalRange": "[14,1 -> 14,1]", + "modifiedRange": "[15,1 -> 15,2]" + }, + { + "originalRange": "[15,1 -> 15,1]", + "modifiedRange": "[16,1 -> 16,2]" + }, + { + "originalRange": "[16,1 -> 16,1]", + "modifiedRange": "[17,1 -> 17,2]" + }, + { + "originalRange": "[17,1 -> 17,1]", + "modifiedRange": "[18,1 -> 18,2]" + }, + { + "originalRange": "[18,1 -> 18,1]", + "modifiedRange": "[19,1 -> 19,2]" + }, + { + "originalRange": "[19,1 -> 19,1]", + "modifiedRange": "[20,1 -> 20,2]" + }, + { + "originalRange": "[20,1 -> 20,1]", + "modifiedRange": "[21,1 -> 21,2]" + }, + { + "originalRange": "[21,1 -> 21,1]", + "modifiedRange": "[22,1 -> 22,2]" + }, + { + "originalRange": "[22,1 -> 22,1]", + "modifiedRange": "[23,1 -> 23,2]" + }, + { + "originalRange": "[23,1 -> 23,1]", + "modifiedRange": "[24,1 -> 24,2]" + }, + { + "originalRange": "[24,1 -> 24,1]", + "modifiedRange": "[25,1 -> 25,2]" + }, + { + "originalRange": "[25,1 -> 25,1]", + "modifiedRange": "[26,1 -> 26,2]" + }, + { + "originalRange": "[26,1 -> 26,1]", + "modifiedRange": "[27,1 -> 27,2]" + }, + { + "originalRange": "[27,1 -> 27,1]", + "modifiedRange": "[28,1 -> 28,2]" + }, + { + "originalRange": "[28,1 -> 28,1]", + "modifiedRange": "[29,1 -> 29,2]" + }, + { + "originalRange": "[29,1 -> 29,1]", + "modifiedRange": "[30,1 -> 34,1]" + } + ] + }, + { + "originalRange": "[30,30)", + "modifiedRange": "[35,36)", + "innerChanges": [ + { + "originalRange": "[29,2 -> 29,2 EOL]", + "modifiedRange": "[34,2 -> 35,1 EOL]" + } + ] + } + ] +} \ No newline at end of file diff --git a/code/src/vs/editor/test/node/diffing/fixtures/false-positive-move/legacy.expected.diff.json b/code/src/vs/editor/test/node/diffing/fixtures/false-positive-move/legacy.expected.diff.json new file mode 100644 index 00000000000..ad6a738346b --- /dev/null +++ b/code/src/vs/editor/test/node/diffing/fixtures/false-positive-move/legacy.expected.diff.json @@ -0,0 +1,17 @@ +{ + "original": { + "content": "includeIssuesWithoutMilestone: boolean = false,\n): Promise> {\n\tconst milestones: ItemsResponseResult = await this.fetchPagedData(\n\t\toptions,\n\t\t'milestoneIssuesKey',\n\t\tPagedDataType.Milestones,\n\t\tPRType.All\n\t);\n\tif (includeIssuesWithoutMilestone) {\n\t\tconst additionalIssues: ItemsResponseResult = await this.fetchPagedData(\n\t\t\toptions,\n\t\t\t'noMilestoneIssuesKey',\n\t\t\tPagedDataType.IssuesWithoutMilestone,\n\t\t\tPRType.All\n\t\t);\n\t\tmilestones.items.push({\n\t\t\tmilestone: {\n\t\t\t\tcreatedAt: new Date(0).toDateString(),\n\t\t\t\tid: '',\n\t\t\t\ttitle: NO_MILESTONE,\n\t\t\t},\n\t\t\tissues: await Promise.all(additionalIssues.items.map(async (issue) => {\n\t\t\t\tconst githubRepository = await this.getRepoForIssue(issue);\n\t\t\t\treturn new IssueModel(githubRepository, githubRepository.remote, issue);\n\t\t\t})),\n\t\t});\n\t}\n\treturn milestones;\n}", + "fileName": "./1.tst" + }, + "modified": { + "content": "includeIssuesWithoutMilestone: boolean = false,\n): Promise> {\n\ttry {\n\t\tconst milestones: ItemsResponseResult = await this.fetchPagedData(\n\t\t\toptions,\n\t\t\t'milestoneIssuesKey',\n\t\t\tPagedDataType.Milestones,\n\t\t\tPRType.All\n\t\t);\n\t\tif (includeIssuesWithoutMilestone) {\n\t\t\tconst additionalIssues: ItemsResponseResult = await this.fetchPagedData(\n\t\t\t\toptions,\n\t\t\t\t'noMilestoneIssuesKey',\n\t\t\t\tPagedDataType.IssuesWithoutMilestone,\n\t\t\t\tPRType.All\n\t\t\t);\n\t\t\tmilestones.items.push({\n\t\t\t\tmilestone: {\n\t\t\t\t\tcreatedAt: new Date(0).toDateString(),\n\t\t\t\t\tid: '',\n\t\t\t\t\ttitle: NO_MILESTONE,\n\t\t\t\t},\n\t\t\t\tissues: await Promise.all(additionalIssues.items.map(async (issue) => {\n\t\t\t\t\tconst githubRepository = await this.getRepoForIssue(issue);\n\t\t\t\t\treturn new IssueModel(githubRepository, githubRepository.remote, issue);\n\t\t\t\t})),\n\t\t\t});\n\t\t}\n\t\treturn milestones;\n\t} catch (e) {\n\t\tLogger.error(`Error fetching milestone issues: ${e instanceof Error ? e.message : e}`, FolderRepositoryManager.ID);\n\t\treturn { hasMorePages: false, hasUnsearchedRepositories: false, items: [] };\n\t}\n}\n", + "fileName": "./2.tst" + }, + "diffs": [ + { + "originalRange": "[3,30)", + "modifiedRange": "[3,36)", + "innerChanges": null + } + ] +} \ No newline at end of file diff --git a/code/src/vs/editor/test/node/diffing/fixtures/method-splitting/advanced.expected.diff.json b/code/src/vs/editor/test/node/diffing/fixtures/method-splitting/advanced.expected.diff.json index b1de25a5907..ca938c66689 100644 --- a/code/src/vs/editor/test/node/diffing/fixtures/method-splitting/advanced.expected.diff.json +++ b/code/src/vs/editor/test/node/diffing/fixtures/method-splitting/advanced.expected.diff.json @@ -21,12 +21,8 @@ "modifiedRange": "[7,76 -> 7,80]" }, { - "originalRange": "[12,153 -> 12,153]", - "modifiedRange": "[7,157 -> 7,181]" - }, - { - "originalRange": "[13,1 -> 13,1]", - "modifiedRange": "[8,1 -> 9,1]" + "originalRange": "[12,153 -> 12,155 EOL]", + "modifiedRange": "[7,157 -> 8,1 EOL]" }, { "originalRange": "[13,31 -> 13,31]", diff --git a/code/src/vs/editor/test/node/diffing/fixtures/move-1/advanced.expected.diff.json b/code/src/vs/editor/test/node/diffing/fixtures/move-1/advanced.expected.diff.json index ad809ac7fe0..e2dac9ce019 100644 --- a/code/src/vs/editor/test/node/diffing/fixtures/move-1/advanced.expected.diff.json +++ b/code/src/vs/editor/test/node/diffing/fixtures/move-1/advanced.expected.diff.json @@ -28,5 +28,23 @@ } ] } + ], + "moves": [ + { + "originalRange": "[24,28)", + "modifiedRange": "[70,74)", + "changes": [ + { + "originalRange": "[26,27)", + "modifiedRange": "[72,73)", + "innerChanges": [ + { + "originalRange": "[26,36 -> 26,40 EOL]", + "modifiedRange": "[72,36 -> 72,40 EOL]" + } + ] + } + ] + } ] } \ No newline at end of file diff --git a/code/src/vs/editor/test/node/diffing/fixtures/noise-1/advanced.expected.diff.json b/code/src/vs/editor/test/node/diffing/fixtures/noise-1/advanced.expected.diff.json index 36bbcbebac9..06849d3af26 100644 --- a/code/src/vs/editor/test/node/diffing/fixtures/noise-1/advanced.expected.diff.json +++ b/code/src/vs/editor/test/node/diffing/fixtures/noise-1/advanced.expected.diff.json @@ -14,11 +14,7 @@ "innerChanges": [ { "originalRange": "[52,7 -> 52,20]", - "modifiedRange": "[52,7 -> 53,2]" - }, - { - "originalRange": "[52,24 -> 52,24]", - "modifiedRange": "[53,6 -> 53,45]" + "modifiedRange": "[52,7 -> 53,41]" }, { "originalRange": "[52,77 -> 56,1 EOL]", diff --git a/code/src/vs/editor/test/node/diffing/fixtures/noise-2/advanced.expected.diff.json b/code/src/vs/editor/test/node/diffing/fixtures/noise-2/advanced.expected.diff.json index a30c65cb9ee..eb4153d4d82 100644 --- a/code/src/vs/editor/test/node/diffing/fixtures/noise-2/advanced.expected.diff.json +++ b/code/src/vs/editor/test/node/diffing/fixtures/noise-2/advanced.expected.diff.json @@ -17,8 +17,8 @@ "modifiedRange": "[50,1 -> 51,1]" }, { - "originalRange": "[50,19 -> 50,27]", - "modifiedRange": "[51,19 -> 68,24]" + "originalRange": "[50,19 -> 50,21]", + "modifiedRange": "[51,19 -> 68,18]" }, { "originalRange": "[51,1 -> 51,1]", diff --git a/code/src/vs/editor/test/node/diffing/fixtures/noisy-move1/1.tst b/code/src/vs/editor/test/node/diffing/fixtures/noisy-move1/1.tst new file mode 100644 index 00000000000..28c9da392b1 --- /dev/null +++ b/code/src/vs/editor/test/node/diffing/fixtures/noisy-move1/1.tst @@ -0,0 +1,544 @@ + contextKeyService.onDidChangeContext(this.onDidChangeContext, this, this.disposables); + this.disposables.add(Event.filter(viewsRegistry.onDidChangeViewWelcomeContent, id => id === this.id)(this.onDidChangeViewWelcomeContent, this, this.disposables)); + this.onDidChangeViewWelcomeContent(); + } + + private onDidChangeViewWelcomeContent(): void { + const descriptors = viewsRegistry.getViewWelcomeContent(this.id); + + this.items = []; + + for (const descriptor of descriptors) { + if (descriptor.when === 'default') { + this.defaultItem = { descriptor, visible: true }; + } else { + const visible = descriptor.when ? this.contextKeyService.contextMatchesRules(descriptor.when) : true; + this.items.push({ descriptor, visible }); + } + } + + this._onDidChange.fire(); + } + + private onDidChangeContext(): void { + let didChange = false; + + for (const item of this.items) { + if (!item.descriptor.when || item.descriptor.when === 'default') { + continue; + } + + const visible = this.contextKeyService.contextMatchesRules(item.descriptor.when); + + if (item.visible === visible) { + continue; + } + + item.visible = visible; + didChange = true; + } + + if (didChange) { + this._onDidChange.fire(); + } + } + + dispose(): void { + this.disposables.dispose(); + } +} + +export abstract class ViewPane extends Pane implements IView { + + private static readonly AlwaysShowActionsConfig = 'workbench.view.alwaysShowHeaderActions'; + + private _onDidFocus = this._register(new Emitter()); + readonly onDidFocus: Event = this._onDidFocus.event; + + private _onDidBlur = this._register(new Emitter()); + readonly onDidBlur: Event = this._onDidBlur.event; + + private _onDidChangeBodyVisibility = this._register(new Emitter()); + readonly onDidChangeBodyVisibility: Event = this._onDidChangeBodyVisibility.event; + + protected _onDidChangeTitleArea = this._register(new Emitter()); + readonly onDidChangeTitleArea: Event = this._onDidChangeTitleArea.event; + + protected _onDidChangeViewWelcomeState = this._register(new Emitter()); + readonly onDidChangeViewWelcomeState: Event = this._onDidChangeViewWelcomeState.event; + + private _isVisible: boolean = false; + readonly id: string; + + private _title: string; + public get title(): string { + return this._title; + } + + private _titleDescription: string | undefined; + public get titleDescription(): string | undefined { + return this._titleDescription; + } + + readonly menuActions: CompositeMenuActions; + + private progressBar!: ProgressBar; + private progressIndicator!: IProgressIndicator; + + private toolbar?: WorkbenchToolBar; + private readonly showActions: ViewPaneShowActions; + private headerContainer?: HTMLElement; + private titleContainer?: HTMLElement; + private titleDescriptionContainer?: HTMLElement; + private iconContainer?: HTMLElement; + protected twistiesContainer?: HTMLElement; + + private bodyContainer!: HTMLElement; + private viewWelcomeContainer!: HTMLElement; + private viewWelcomeDisposable: IDisposable = Disposable.None; + private viewWelcomeController: ViewWelcomeController; + + protected readonly scopedContextKeyService: IContextKeyService; + + constructor( + options: IViewPaneOptions, + @IKeybindingService protected keybindingService: IKeybindingService, + @IContextMenuService protected contextMenuService: IContextMenuService, + @IConfigurationService protected readonly configurationService: IConfigurationService, + @IContextKeyService protected contextKeyService: IContextKeyService, + @IViewDescriptorService protected viewDescriptorService: IViewDescriptorService, + @IInstantiationService protected instantiationService: IInstantiationService, + @IOpenerService protected openerService: IOpenerService, + @IThemeService protected themeService: IThemeService, + @ITelemetryService protected telemetryService: ITelemetryService, + ) { + super({ ...options, ...{ orientation: viewDescriptorService.getViewLocationById(options.id) === ViewContainerLocation.Panel ? Orientation.HORIZONTAL : Orientation.VERTICAL } }); + + this.id = options.id; + this._title = options.title; + this._titleDescription = options.titleDescription; + this.showActions = options.showActions ?? ViewPaneShowActions.Default; + + this.scopedContextKeyService = this._register(contextKeyService.createScoped(this.element)); + this.scopedContextKeyService.createKey('view', this.id); + const viewLocationKey = this.scopedContextKeyService.createKey('viewLocation', ViewContainerLocationToString(viewDescriptorService.getViewLocationById(this.id)!)); + this._register(Event.filter(viewDescriptorService.onDidChangeLocation, e => e.views.some(view => view.id === this.id))(() => viewLocationKey.set(ViewContainerLocationToString(viewDescriptorService.getViewLocationById(this.id)!)))); + + this.menuActions = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService])).createInstance(CompositeMenuActions, options.titleMenuId ?? MenuId.ViewTitle, MenuId.ViewTitleContext, { shouldForwardArgs: !options.donotForwardArgs })); + this._register(this.menuActions.onDidChange(() => this.updateActions())); + + this.viewWelcomeController = this._register(new ViewWelcomeController(this.id, contextKeyService)); + } + + override get headerVisible(): boolean { + return super.headerVisible; + } + + override set headerVisible(visible: boolean) { + super.headerVisible = visible; + this.element.classList.toggle('merged-header', !visible); + } + + setVisible(visible: boolean): void { + if (this._isVisible !== visible) { + this._isVisible = visible; + + if (this.isExpanded()) { + this._onDidChangeBodyVisibility.fire(visible); + } + } + } + + isVisible(): boolean { + return this._isVisible; + } + + isBodyVisible(): boolean { + return this._isVisible && this.isExpanded(); + } + + override setExpanded(expanded: boolean): boolean { + const changed = super.setExpanded(expanded); + if (changed) { + this._onDidChangeBodyVisibility.fire(expanded); + } + if (this.twistiesContainer) { + this.twistiesContainer.classList.remove(...ThemeIcon.asClassNameArray(this.getTwistyIcon(!expanded))); + this.twistiesContainer.classList.add(...ThemeIcon.asClassNameArray(this.getTwistyIcon(expanded))); + } + return changed; + } + + override render(): void { + super.render(); + + const focusTracker = trackFocus(this.element); + this._register(focusTracker); + this._register(focusTracker.onDidFocus(() => this._onDidFocus.fire())); + this._register(focusTracker.onDidBlur(() => this._onDidBlur.fire())); + } + + protected renderHeader(container: HTMLElement): void { + this.headerContainer = container; + + this.twistiesContainer = append(container, $(ThemeIcon.asCSSSelector(this.getTwistyIcon(this.isExpanded())))); + + this.renderHeaderTitle(container, this.title); + + const actions = append(container, $('.actions')); + actions.classList.toggle('show-always', this.showActions === ViewPaneShowActions.Always); + actions.classList.toggle('show-expanded', this.showActions === ViewPaneShowActions.WhenExpanded); + this.toolbar = this.instantiationService.createInstance(WorkbenchToolBar, actions, { + orientation: ActionsOrientation.HORIZONTAL, + actionViewItemProvider: action => this.getActionViewItem(action), + ariaLabel: nls.localize('viewToolbarAriaLabel', "{0} actions", this.title), + getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id), + renderDropdownAsChildElement: true, + actionRunner: this.getActionRunner(), + resetMenu: this.menuActions.menuId + }); + + this._register(this.toolbar); + this.setActions(); + + this._register(addDisposableListener(actions, EventType.CLICK, e => e.preventDefault())); + + const viewContainerModel = this.viewDescriptorService.getViewContainerByViewId(this.id); + if (viewContainerModel) { + this._register(this.viewDescriptorService.getViewContainerModel(viewContainerModel).onDidChangeContainerInfo(({ title }) => this.updateTitle(this.title))); + } else { + console.error(`View container model not found for view ${this.id}`); + } + + const onDidRelevantConfigurationChange = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(ViewPane.AlwaysShowActionsConfig)); + this._register(onDidRelevantConfigurationChange(this.updateActionsVisibility, this)); + this.updateActionsVisibility(); + } + + protected getTwistyIcon(expanded: boolean): ThemeIcon { + return expanded ? viewPaneContainerExpandedIcon : viewPaneContainerCollapsedIcon; + } + + override style(styles: IPaneStyles): void { + super.style(styles); + + const icon = this.getIcon(); + if (this.iconContainer) { + const fgColor = asCssValueWithDefault(styles.headerForeground, asCssVariable(foreground)); + if (URI.isUri(icon)) { + // Apply background color to activity bar item provided with iconUrls + this.iconContainer.style.backgroundColor = fgColor; + this.iconContainer.style.color = ''; + } else { + // Apply foreground color to activity bar items provided with codicons + this.iconContainer.style.color = fgColor; + this.iconContainer.style.backgroundColor = ''; + } + } + } + + private getIcon(): ThemeIcon | URI { + return this.viewDescriptorService.getViewDescriptorById(this.id)?.containerIcon || defaultViewIcon; + } + + protected renderHeaderTitle(container: HTMLElement, title: string): void { + this.iconContainer = append(container, $('.icon', undefined)); + const icon = this.getIcon(); + + let cssClass: string | undefined = undefined; + if (URI.isUri(icon)) { + cssClass = `view-${this.id.replace(/[\.\:]/g, '-')}`; + const iconClass = `.pane-header .icon.${cssClass}`; + + createCSSRule(iconClass, ` + mask: ${asCSSUrl(icon)} no-repeat 50% 50%; + mask-size: 24px; + -webkit-mask: ${asCSSUrl(icon)} no-repeat 50% 50%; + -webkit-mask-size: 16px; + `); + } else if (ThemeIcon.isThemeIcon(icon)) { + cssClass = ThemeIcon.asClassName(icon); + } + + if (cssClass) { + this.iconContainer.classList.add(...cssClass.split(' ')); + } + + const calculatedTitle = this.calculateTitle(title); + this.titleContainer = append(container, $('h3.title', { title: calculatedTitle }, calculatedTitle)); + + if (this._titleDescription) { + this.setTitleDescription(this._titleDescription); + } + + this.iconContainer.title = calculatedTitle; + this.iconContainer.setAttribute('aria-label', calculatedTitle); + } + + protected updateTitle(title: string): void { + const calculatedTitle = this.calculateTitle(title); + if (this.titleContainer) { + this.titleContainer.textContent = calculatedTitle; + this.titleContainer.setAttribute('title', calculatedTitle); + } + + if (this.iconContainer) { + this.iconContainer.title = calculatedTitle; + this.iconContainer.setAttribute('aria-label', calculatedTitle); + } + + this._title = title; + this._onDidChangeTitleArea.fire(); + } + + private setTitleDescription(description: string | undefined) { + if (this.titleDescriptionContainer) { + this.titleDescriptionContainer.textContent = description ?? ''; + this.titleDescriptionContainer.setAttribute('title', description ?? ''); + } + else if (description && this.titleContainer) { + this.titleDescriptionContainer = after(this.titleContainer, $('span.description', { title: description }, description)); + } + } + + protected updateTitleDescription(description?: string | undefined): void { + this.setTitleDescription(description); + + this._titleDescription = description; + this._onDidChangeTitleArea.fire(); + } + + private calculateTitle(title: string): string { + const viewContainer = this.viewDescriptorService.getViewContainerByViewId(this.id)!; + const model = this.viewDescriptorService.getViewContainerModel(viewContainer); + const viewDescriptor = this.viewDescriptorService.getViewDescriptorById(this.id); + const isDefault = this.viewDescriptorService.getDefaultContainerById(this.id) === viewContainer; + + if (!isDefault && viewDescriptor?.containerTitle && model.title !== viewDescriptor.containerTitle) { + return `${viewDescriptor.containerTitle}: ${title}`; + } + + return title; + } + + private scrollableElement!: DomScrollableElement; + + protected renderBody(container: HTMLElement): void { + this.bodyContainer = container; + + const viewWelcomeContainer = append(container, $('.welcome-view')); + this.viewWelcomeContainer = $('.welcome-view-content', { tabIndex: 0 }); + this.scrollableElement = this._register(new DomScrollableElement(this.viewWelcomeContainer, { + alwaysConsumeMouseWheel: true, + horizontal: ScrollbarVisibility.Hidden, + vertical: ScrollbarVisibility.Visible, + })); + + append(viewWelcomeContainer, this.scrollableElement.getDomNode()); + + const onViewWelcomeChange = Event.any(this.viewWelcomeController.onDidChange, this.onDidChangeViewWelcomeState); + this._register(onViewWelcomeChange(this.updateViewWelcome, this)); + this.updateViewWelcome(); + } + + protected layoutBody(height: number, width: number): void { + if (this.shouldShowWelcome()) { + this.viewWelcomeContainer.style.height = `${height}px`; + this.viewWelcomeContainer.style.width = `${width}px`; + this.viewWelcomeContainer.classList.toggle('wide', width > 640); + this.scrollableElement.scanDomNode(); + } + } + + onDidScrollRoot() { + // noop + } + + getProgressIndicator() { + if (this.progressBar === undefined) { + // Progress bar + this.progressBar = this._register(new ProgressBar(this.element, defaultProgressBarStyles)); + this.progressBar.hide(); + } + + if (this.progressIndicator === undefined) { + const that = this; + this.progressIndicator = new ScopedProgressIndicator(assertIsDefined(this.progressBar), new class extends AbstractProgressScope { + constructor() { + super(that.id, that.isBodyVisible()); + this._register(that.onDidChangeBodyVisibility(isVisible => isVisible ? this.onScopeOpened(that.id) : this.onScopeClosed(that.id))); + } + }()); + } + return this.progressIndicator; + } + + protected getProgressLocation(): string { + return this.viewDescriptorService.getViewContainerByViewId(this.id)!.id; + } + + protected getBackgroundColor(): string { + switch (this.viewDescriptorService.getViewLocationById(this.id)) { + case ViewContainerLocation.Panel: + return PANEL_BACKGROUND; + case ViewContainerLocation.Sidebar: + case ViewContainerLocation.AuxiliaryBar: + return SIDE_BAR_BACKGROUND; + } + + return SIDE_BAR_BACKGROUND; + } + + focus(): void { + if (this.shouldShowWelcome()) { + this.viewWelcomeContainer.focus(); + } else if (this.element) { + this.element.focus(); + this._onDidFocus.fire(); + } + } + + private setActions(): void { + if (this.toolbar) { + const primaryActions = [...this.menuActions.getPrimaryActions()]; + if (this.shouldShowFilterInHeader()) { + primaryActions.unshift(VIEWPANE_FILTER_ACTION); + } + this.toolbar.setActions(prepareActions(primaryActions), prepareActions(this.menuActions.getSecondaryActions())); + this.toolbar.context = this.getActionsContext(); + } + } + + private updateActionsVisibility(): void { + if (!this.headerContainer) { + return; + } + const shouldAlwaysShowActions = this.configurationService.getValue('workbench.view.alwaysShowHeaderActions'); + this.headerContainer.classList.toggle('actions-always-visible', shouldAlwaysShowActions); + } + + protected updateActions(): void { + this.setActions(); + this._onDidChangeTitleArea.fire(); + } + + getActionViewItem(action: IAction, options?: IDropdownMenuActionViewItemOptions): IActionViewItem | undefined { + if (action.id === VIEWPANE_FILTER_ACTION.id) { + const that = this; + return new class extends BaseActionViewItem { + constructor() { super(null, action); } + override setFocusable(): void { /* noop input elements are focusable by default */ } + override get trapsArrowNavigation(): boolean { return true; } + override render(container: HTMLElement): void { + container.classList.add('viewpane-filter-container'); + append(container, that.getFilterWidget()!.element); + } + }; + } + return createActionViewItem(this.instantiationService, action, { ...options, ...{ menuAsChild: action instanceof SubmenuItemAction } }); + } + + getActionsContext(): unknown { + return undefined; + } + + getActionRunner(): IActionRunner | undefined { + return undefined; + } + + getOptimalWidth(): number { + return 0; + } + + saveState(): void { + // Subclasses to implement for saving state + } + + private updateViewWelcome(): void { + this.viewWelcomeDisposable.dispose(); + + if (!this.shouldShowWelcome()) { + this.bodyContainer.classList.remove('welcome'); + this.viewWelcomeContainer.innerText = ''; + this.scrollableElement.scanDomNode(); + return; + } + + const contents = this.viewWelcomeController.contents; + + if (contents.length === 0) { + this.bodyContainer.classList.remove('welcome'); + this.viewWelcomeContainer.innerText = ''; + this.scrollableElement.scanDomNode(); + return; + } + + const disposables = new DisposableStore(); + this.bodyContainer.classList.add('welcome'); + this.viewWelcomeContainer.innerText = ''; + + for (const { content, precondition } of contents) { + const lines = content.split('\n'); + + for (let line of lines) { + line = line.trim(); + + if (!line) { + continue; + } + + const linkedText = parseLinkedText(line); + + if (linkedText.nodes.length === 1 && typeof linkedText.nodes[0] !== 'string') { + const node = linkedText.nodes[0]; + const buttonContainer = append(this.viewWelcomeContainer, $('.button-container')); + const button = new Button(buttonContainer, { title: node.title, supportIcons: true, ...defaultButtonStyles }); + button.label = node.label; + button.onDidClick(_ => { + this.telemetryService.publicLog2<{ viewId: string; uri: string }, WelcomeActionClassification>('views.welcomeAction', { viewId: this.id, uri: node.href }); + this.openerService.open(node.href, { allowCommands: true }); + }, null, disposables); + disposables.add(button); + + if (precondition) { + const updateEnablement = () => button.enabled = this.contextKeyService.contextMatchesRules(precondition); + updateEnablement(); + + const keys = new Set(); + precondition.keys().forEach(key => keys.add(key)); + const onDidChangeContext = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(keys)); + onDidChangeContext(updateEnablement, null, disposables); + } + } else { + const p = append(this.viewWelcomeContainer, $('p')); + + for (const node of linkedText.nodes) { + if (typeof node === 'string') { + append(p, document.createTextNode(node)); + } else { + const link = disposables.add(this.instantiationService.createInstance(Link, p, node, {})); + + if (precondition && node.href.startsWith('command:')) { + const updateEnablement = () => link.enabled = this.contextKeyService.contextMatchesRules(precondition); + updateEnablement(); + + const keys = new Set(); + precondition.keys().forEach(key => keys.add(key)); + const onDidChangeContext = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(keys)); + onDidChangeContext(updateEnablement, null, disposables); + } + } + } + } + } + } + + this.scrollableElement.scanDomNode(); + this.viewWelcomeDisposable = disposables; + } + + shouldShowWelcome(): boolean { + return false; + } + + getFilterWidget() \ No newline at end of file diff --git a/code/src/vs/editor/test/node/diffing/fixtures/noisy-move1/2.tst b/code/src/vs/editor/test/node/diffing/fixtures/noisy-move1/2.tst new file mode 100644 index 00000000000..744eb775f10 --- /dev/null +++ b/code/src/vs/editor/test/node/diffing/fixtures/noisy-move1/2.tst @@ -0,0 +1,565 @@ + + layout(height: number, width: number) { + if (!this.enabled) { + return; + } + + this.element!.style.height = `${height}px`; + this.element!.style.width = `${width}px`; + this.element!.classList.toggle('wide', width > 640); + this.scrollableElement!.scanDomNode(); + } + + focus() { + if (!this.enabled) { + return; + } + + this.element!.focus(); + } + + private onDidChangeViewWelcomeState(): void { + const enabled = this.delegate.shouldShowWelcome(); + + if (this.enabled === enabled) { + return; + } + + this.enabled = enabled; + + if (!enabled) { + this.enabledDisposables.clear(); + return; + } + + this.container.classList.add('welcome'); + const viewWelcomeContainer = append(this.container, $('.welcome-view')); + this.element = $('.welcome-view-content', { tabIndex: 0 }); + this.scrollableElement = new DomScrollableElement(this.element, { alwaysConsumeMouseWheel: true, horizontal: ScrollbarVisibility.Hidden, vertical: ScrollbarVisibility.Visible, }); + append(viewWelcomeContainer, this.scrollableElement.getDomNode()); + + this.enabledDisposables.add(toDisposable(() => { + this.container.classList.remove('welcome'); + this.scrollableElement!.dispose(); + viewWelcomeContainer.remove(); + this.scrollableElement = undefined; + this.element = undefined; + })); + + this.contextKeyService.onDidChangeContext(this.onDidChangeContext, this, this.enabledDisposables); + Event.chain(viewsRegistry.onDidChangeViewWelcomeContent, $ => $.filter(id => id === this.delegate.id)) + (this.onDidChangeViewWelcomeContent, this, this.enabledDisposables); + this.onDidChangeViewWelcomeContent(); + } + + private onDidChangeViewWelcomeContent(): void { + const descriptors = viewsRegistry.getViewWelcomeContent(this.delegate.id); + + this.items = []; + + for (const descriptor of descriptors) { + if (descriptor.when === 'default') { + this.defaultItem = { descriptor, visible: true }; + } else { + const visible = descriptor.when ? this.contextKeyService.contextMatchesRules(descriptor.when) : true; + this.items.push({ descriptor, visible }); + } + } + + this.render(); + } + + private onDidChangeContext(): void { + let didChange = false; + + for (const item of this.items) { + if (!item.descriptor.when || item.descriptor.when === 'default') { + continue; + } + + const visible = this.contextKeyService.contextMatchesRules(item.descriptor.when); + + if (item.visible === visible) { + continue; + } + + item.visible = visible; + didChange = true; + } + + if (didChange) { + this.render(); + } + } + + private render(): void { + this.renderDisposables.clear(); + + const contents = this.getContentDescriptors(); + + if (contents.length === 0) { + this.container.classList.remove('welcome'); + this.element!.innerText = ''; + this.scrollableElement!.scanDomNode(); + return; + } + + this.container.classList.add('welcome'); + this.element!.innerText = ''; + + for (const { content, precondition } of contents) { + const lines = content.split('\n'); + + for (let line of lines) { + line = line.trim(); + + if (!line) { + continue; + } + + const linkedText = parseLinkedText(line); + + if (linkedText.nodes.length === 1 && typeof linkedText.nodes[0] !== 'string') { + const node = linkedText.nodes[0]; + const buttonContainer = append(this.element!, $('.button-container')); + const button = new Button(buttonContainer, { title: node.title, supportIcons: true, ...defaultButtonStyles }); + button.label = node.label; + button.onDidClick(_ => { + this.telemetryService.publicLog2<{ viewId: string; uri: string }, WelcomeActionClassification>('views.welcomeAction', { viewId: this.delegate.id, uri: node.href }); + this.openerService.open(node.href, { allowCommands: true }); + }, null, this.renderDisposables); + this.renderDisposables.add(button); + + if (precondition) { + const updateEnablement = () => button.enabled = this.contextKeyService.contextMatchesRules(precondition); + updateEnablement(); + + const keys = new Set(precondition.keys()); + const onDidChangeContext = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(keys)); + onDidChangeContext(updateEnablement, null, this.renderDisposables); + } + } else { + const p = append(this.element!, $('p')); + + for (const node of linkedText.nodes) { + if (typeof node === 'string') { + append(p, document.createTextNode(node)); + } else { + const link = this.renderDisposables.add(this.instantiationService.createInstance(Link, p, node, {})); + + if (precondition && node.href.startsWith('command:')) { + const updateEnablement = () => link.enabled = this.contextKeyService.contextMatchesRules(precondition); + updateEnablement(); + + const keys = new Set(precondition.keys()); + const onDidChangeContext = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(keys)); + onDidChangeContext(updateEnablement, null, this.renderDisposables); + } + } + } + } + } + } + + this.scrollableElement!.scanDomNode(); + } + + private getContentDescriptors(): IViewContentDescriptor[] { + const visibleItems = this.items.filter(v => v.visible); + + if (visibleItems.length === 0 && this.defaultItem) { + return [this.defaultItem.descriptor]; + } + + return visibleItems.map(v => v.descriptor); + } + + dispose(): void { + this.disposables.dispose(); + } + } + + export abstract class ViewPane extends Pane implements IView { + + private static readonly AlwaysShowActionsConfig = 'workbench.view.alwaysShowHeaderActions'; + + private _onDidFocus = this._register(new Emitter()); + readonly onDidFocus: Event = this._onDidFocus.event; + + private _onDidBlur = this._register(new Emitter()); + readonly onDidBlur: Event = this._onDidBlur.event; + + private _onDidChangeBodyVisibility = this._register(new Emitter()); + readonly onDidChangeBodyVisibility: Event = this._onDidChangeBodyVisibility.event; + + protected _onDidChangeTitleArea = this._register(new Emitter()); + readonly onDidChangeTitleArea: Event = this._onDidChangeTitleArea.event; + + protected _onDidChangeViewWelcomeState = this._register(new Emitter()); + readonly onDidChangeViewWelcomeState: Event = this._onDidChangeViewWelcomeState.event; + + private _isVisible: boolean = false; + readonly id: string; + + private _title: string; + public get title(): string { + return this._title; + } + + private _titleDescription: string | undefined; + public get titleDescription(): string | undefined { + return this._titleDescription; + } + + readonly menuActions: CompositeMenuActions; + + private progressBar!: ProgressBar; + private progressIndicator!: IProgressIndicator; + + private toolbar?: WorkbenchToolBar; + private readonly showActions: ViewPaneShowActions; + private headerContainer?: HTMLElement; + private titleContainer?: HTMLElement; + private titleDescriptionContainer?: HTMLElement; + private iconContainer?: HTMLElement; + protected twistiesContainer?: HTMLElement; + private viewWelcomeController!: ViewWelcomeController; + + protected readonly scopedContextKeyService: IContextKeyService; + + constructor( + options: IViewPaneOptions, + @IKeybindingService protected keybindingService: IKeybindingService, + @IContextMenuService protected contextMenuService: IContextMenuService, + @IConfigurationService protected readonly configurationService: IConfigurationService, + @IContextKeyService protected contextKeyService: IContextKeyService, + @IViewDescriptorService protected viewDescriptorService: IViewDescriptorService, + @IInstantiationService protected instantiationService: IInstantiationService, + @IOpenerService protected openerService: IOpenerService, + @IThemeService protected themeService: IThemeService, + @ITelemetryService protected telemetryService: ITelemetryService, + ) { + super({ ...options, ...{ orientation: viewDescriptorService.getViewLocationById(options.id) === ViewContainerLocation.Panel ? Orientation.HORIZONTAL : Orientation.VERTICAL } }); + + this.id = options.id; + this._title = options.title; + this._titleDescription = options.titleDescription; + this.showActions = options.showActions ?? ViewPaneShowActions.Default; + + this.scopedContextKeyService = this._register(contextKeyService.createScoped(this.element)); + this.scopedContextKeyService.createKey('view', this.id); + const viewLocationKey = this.scopedContextKeyService.createKey('viewLocation', ViewContainerLocationToString(viewDescriptorService.getViewLocationById(this.id)!)); + this._register(Event.filter(viewDescriptorService.onDidChangeLocation, e => e.views.some(view => view.id === this.id))(() => viewLocationKey.set(ViewContainerLocationToString(viewDescriptorService.getViewLocationById(this.id)!)))); + + this.menuActions = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService])).createInstance(CompositeMenuActions, options.titleMenuId ?? MenuId.ViewTitle, MenuId.ViewTitleContext, { shouldForwardArgs: !options.donotForwardArgs })); + this._register(this.menuActions.onDidChange(() => this.updateActions())); + } + + override get headerVisible(): boolean { + return super.headerVisible; + } + + override set headerVisible(visible: boolean) { + super.headerVisible = visible; + this.element.classList.toggle('merged-header', !visible); + } + + setVisible(visible: boolean): void { + if (this._isVisible !== visible) { + this._isVisible = visible; + + if (this.isExpanded()) { + this._onDidChangeBodyVisibility.fire(visible); + } + } + } + + isVisible(): boolean { + return this._isVisible; + } + + isBodyVisible(): boolean { + return this._isVisible && this.isExpanded(); + } + + override setExpanded(expanded: boolean): boolean { + const changed = super.setExpanded(expanded); + if (changed) { + this._onDidChangeBodyVisibility.fire(expanded); + } + if (this.twistiesContainer) { + this.twistiesContainer.classList.remove(...ThemeIcon.asClassNameArray(this.getTwistyIcon(!expanded))); + this.twistiesContainer.classList.add(...ThemeIcon.asClassNameArray(this.getTwistyIcon(expanded))); + } + return changed; + } + + override render(): void { + super.render(); + + const focusTracker = trackFocus(this.element); + this._register(focusTracker); + this._register(focusTracker.onDidFocus(() => this._onDidFocus.fire())); + this._register(focusTracker.onDidBlur(() => this._onDidBlur.fire())); + } + + protected renderHeader(container: HTMLElement): void { + this.headerContainer = container; + + this.twistiesContainer = append(container, $(ThemeIcon.asCSSSelector(this.getTwistyIcon(this.isExpanded())))); + + this.renderHeaderTitle(container, this.title); + + const actions = append(container, $('.actions')); + actions.classList.toggle('show-always', this.showActions === ViewPaneShowActions.Always); + actions.classList.toggle('show-expanded', this.showActions === ViewPaneShowActions.WhenExpanded); + this.toolbar = this.instantiationService.createInstance(WorkbenchToolBar, actions, { + orientation: ActionsOrientation.HORIZONTAL, + actionViewItemProvider: action => this.getActionViewItem(action), + ariaLabel: nls.localize('viewToolbarAriaLabel', "{0} actions", this.title), + getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id), + renderDropdownAsChildElement: true, + actionRunner: this.getActionRunner(), + resetMenu: this.menuActions.menuId + }); + + this._register(this.toolbar); + this.setActions(); + + this._register(addDisposableListener(actions, EventType.CLICK, e => e.preventDefault())); + + const viewContainerModel = this.viewDescriptorService.getViewContainerByViewId(this.id); + if (viewContainerModel) { + this._register(this.viewDescriptorService.getViewContainerModel(viewContainerModel).onDidChangeContainerInfo(({ title }) => this.updateTitle(this.title))); + } else { + console.error(`View container model not found for view ${this.id}`); + } + + const onDidRelevantConfigurationChange = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(ViewPane.AlwaysShowActionsConfig)); + this._register(onDidRelevantConfigurationChange(this.updateActionsVisibility, this)); + this.updateActionsVisibility(); + } + + protected getTwistyIcon(expanded: boolean): ThemeIcon { + return expanded ? viewPaneContainerExpandedIcon : viewPaneContainerCollapsedIcon; + } + + override style(styles: IPaneStyles): void { + super.style(styles); + + const icon = this.getIcon(); + if (this.iconContainer) { + const fgColor = asCssValueWithDefault(styles.headerForeground, asCssVariable(foreground)); + if (URI.isUri(icon)) { + // Apply background color to activity bar item provided with iconUrls + this.iconContainer.style.backgroundColor = fgColor; + this.iconContainer.style.color = ''; + } else { + // Apply foreground color to activity bar items provided with codicons + this.iconContainer.style.color = fgColor; + this.iconContainer.style.backgroundColor = ''; + } + } + } + + private getIcon(): ThemeIcon | URI { + return this.viewDescriptorService.getViewDescriptorById(this.id)?.containerIcon || defaultViewIcon; + } + + protected renderHeaderTitle(container: HTMLElement, title: string): void { + this.iconContainer = append(container, $('.icon', undefined)); + const icon = this.getIcon(); + + let cssClass: string | undefined = undefined; + if (URI.isUri(icon)) { + cssClass = `view-${this.id.replace(/[\.\:]/g, '-')}`; + const iconClass = `.pane-header .icon.${cssClass}`; + + createCSSRule(iconClass, ` + mask: ${asCSSUrl(icon)} no-repeat 50% 50%; + mask-size: 24px; + -webkit-mask: ${asCSSUrl(icon)} no-repeat 50% 50%; + -webkit-mask-size: 16px; + `); + } else if (ThemeIcon.isThemeIcon(icon)) { + cssClass = ThemeIcon.asClassName(icon); + } + + if (cssClass) { + this.iconContainer.classList.add(...cssClass.split(' ')); + } + + const calculatedTitle = this.calculateTitle(title); + this.titleContainer = append(container, $('h3.title', { title: calculatedTitle }, calculatedTitle)); + + if (this._titleDescription) { + this.setTitleDescription(this._titleDescription); + } + + this.iconContainer.title = calculatedTitle; + this.iconContainer.setAttribute('aria-label', calculatedTitle); + } + + protected updateTitle(title: string): void { + const calculatedTitle = this.calculateTitle(title); + if (this.titleContainer) { + this.titleContainer.textContent = calculatedTitle; + this.titleContainer.setAttribute('title', calculatedTitle); + } + + if (this.iconContainer) { + this.iconContainer.title = calculatedTitle; + this.iconContainer.setAttribute('aria-label', calculatedTitle); + } + + this._title = title; + this._onDidChangeTitleArea.fire(); + } + + private setTitleDescription(description: string | undefined) { + if (this.titleDescriptionContainer) { + this.titleDescriptionContainer.textContent = description ?? ''; + this.titleDescriptionContainer.setAttribute('title', description ?? ''); + } + else if (description && this.titleContainer) { + this.titleDescriptionContainer = after(this.titleContainer, $('span.description', { title: description }, description)); + } + } + + protected updateTitleDescription(description?: string | undefined): void { + this.setTitleDescription(description); + + this._titleDescription = description; + this._onDidChangeTitleArea.fire(); + } + + private calculateTitle(title: string): string { + const viewContainer = this.viewDescriptorService.getViewContainerByViewId(this.id)!; + const model = this.viewDescriptorService.getViewContainerModel(viewContainer); + const viewDescriptor = this.viewDescriptorService.getViewDescriptorById(this.id); + const isDefault = this.viewDescriptorService.getDefaultContainerById(this.id) === viewContainer; + + if (!isDefault && viewDescriptor?.containerTitle && model.title !== viewDescriptor.containerTitle) { + return `${viewDescriptor.containerTitle}: ${title}`; + } + + return title; + } + + protected renderBody(container: HTMLElement): void { + this.viewWelcomeController = this._register(new ViewWelcomeController(container, this, this.instantiationService, this.openerService, this.telemetryService, this.contextKeyService)); + } + + protected layoutBody(height: number, width: number): void { + this.viewWelcomeController.layout(height, width); + } + + onDidScrollRoot() { + // noop + } + + getProgressIndicator() { + if (this.progressBar === undefined) { + // Progress bar + this.progressBar = this._register(new ProgressBar(this.element, defaultProgressBarStyles)); + this.progressBar.hide(); + } + + if (this.progressIndicator === undefined) { + const that = this; + this.progressIndicator = new ScopedProgressIndicator(assertIsDefined(this.progressBar), new class extends AbstractProgressScope { + constructor() { + super(that.id, that.isBodyVisible()); + this._register(that.onDidChangeBodyVisibility(isVisible => isVisible ? this.onScopeOpened(that.id) : this.onScopeClosed(that.id))); + } + }()); + } + return this.progressIndicator; + } + + protected getProgressLocation(): string { + return this.viewDescriptorService.getViewContainerByViewId(this.id)!.id; + } + + protected getBackgroundColor(): string { + switch (this.viewDescriptorService.getViewLocationById(this.id)) { + case ViewContainerLocation.Panel: + return PANEL_BACKGROUND; + case ViewContainerLocation.Sidebar: + case ViewContainerLocation.AuxiliaryBar: + return SIDE_BAR_BACKGROUND; + } + + return SIDE_BAR_BACKGROUND; + } + + focus(): void { + if (this.shouldShowWelcome()) { + this.viewWelcomeController.focus(); + } else if (this.element) { + this.element.focus(); + this._onDidFocus.fire(); + } + } + + private setActions(): void { + if (this.toolbar) { + const primaryActions = [...this.menuActions.getPrimaryActions()]; + if (this.shouldShowFilterInHeader()) { + primaryActions.unshift(VIEWPANE_FILTER_ACTION); + } + this.toolbar.setActions(prepareActions(primaryActions), prepareActions(this.menuActions.getSecondaryActions())); + this.toolbar.context = this.getActionsContext(); + } + } + + private updateActionsVisibility(): void { + if (!this.headerContainer) { + return; + } + const shouldAlwaysShowActions = this.configurationService.getValue('workbench.view.alwaysShowHeaderActions'); + this.headerContainer.classList.toggle('actions-always-visible', shouldAlwaysShowActions); + } + + protected updateActions(): void { + this.setActions(); + this._onDidChangeTitleArea.fire(); + } + + getActionViewItem(action: IAction, options?: IDropdownMenuActionViewItemOptions): IActionViewItem | undefined { + if (action.id === VIEWPANE_FILTER_ACTION.id) { + const that = this; + return new class extends BaseActionViewItem { + constructor() { super(null, action); } + override setFocusable(): void { /* noop input elements are focusable by default */ } + override get trapsArrowNavigation(): boolean { return true; } + override render(container: HTMLElement): void { + container.classList.add('viewpane-filter-container'); + append(container, that.getFilterWidget()!.element); + } + }; + } + return createActionViewItem(this.instantiationService, action, { ...options, ...{ menuAsChild: action instanceof SubmenuItemAction } }); + } + + getActionsContext(): unknown { + return undefined; + } + + getActionRunner(): IActionRunner | undefined { + return undefined; + } + + getOptimalWidth(): number { + return 0; + } + + saveState(): void { + // Subclasses to implement for saving state + } + + shouldShowWelcome(): boolean { + return false; + } + + getFilterWidget() \ No newline at end of file diff --git a/code/src/vs/editor/test/node/diffing/fixtures/noisy-move1/advanced.expected.diff.json b/code/src/vs/editor/test/node/diffing/fixtures/noisy-move1/advanced.expected.diff.json new file mode 100644 index 00000000000..6fd1598ffe3 --- /dev/null +++ b/code/src/vs/editor/test/node/diffing/fixtures/noisy-move1/advanced.expected.diff.json @@ -0,0 +1,316 @@ +{ + "original": { + "content": "\tcontextKeyService.onDidChangeContext(this.onDidChangeContext, this, this.disposables);\n\t\tthis.disposables.add(Event.filter(viewsRegistry.onDidChangeViewWelcomeContent, id => id === this.id)(this.onDidChangeViewWelcomeContent, this, this.disposables));\n\t\tthis.onDidChangeViewWelcomeContent();\n\t}\n\n\tprivate onDidChangeViewWelcomeContent(): void {\n\t\tconst descriptors = viewsRegistry.getViewWelcomeContent(this.id);\n\n\t\tthis.items = [];\n\n\t\tfor (const descriptor of descriptors) {\n\t\t\tif (descriptor.when === 'default') {\n\t\t\t\tthis.defaultItem = { descriptor, visible: true };\n\t\t\t} else {\n\t\t\t\tconst visible = descriptor.when ? this.contextKeyService.contextMatchesRules(descriptor.when) : true;\n\t\t\t\tthis.items.push({ descriptor, visible });\n\t\t\t}\n\t\t}\n\n\t\tthis._onDidChange.fire();\n\t}\n\n\tprivate onDidChangeContext(): void {\n\t\tlet didChange = false;\n\n\t\tfor (const item of this.items) {\n\t\t\tif (!item.descriptor.when || item.descriptor.when === 'default') {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst visible = this.contextKeyService.contextMatchesRules(item.descriptor.when);\n\n\t\t\tif (item.visible === visible) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\titem.visible = visible;\n\t\t\tdidChange = true;\n\t\t}\n\n\t\tif (didChange) {\n\t\t\tthis._onDidChange.fire();\n\t\t}\n\t}\n\n\tdispose(): void {\n\t\tthis.disposables.dispose();\n\t}\n}\n\nexport abstract class ViewPane extends Pane implements IView {\n\n\tprivate static readonly AlwaysShowActionsConfig = 'workbench.view.alwaysShowHeaderActions';\n\n\tprivate _onDidFocus = this._register(new Emitter());\n\treadonly onDidFocus: Event = this._onDidFocus.event;\n\n\tprivate _onDidBlur = this._register(new Emitter());\n\treadonly onDidBlur: Event = this._onDidBlur.event;\n\n\tprivate _onDidChangeBodyVisibility = this._register(new Emitter());\n\treadonly onDidChangeBodyVisibility: Event = this._onDidChangeBodyVisibility.event;\n\n\tprotected _onDidChangeTitleArea = this._register(new Emitter());\n\treadonly onDidChangeTitleArea: Event = this._onDidChangeTitleArea.event;\n\n\tprotected _onDidChangeViewWelcomeState = this._register(new Emitter());\n\treadonly onDidChangeViewWelcomeState: Event = this._onDidChangeViewWelcomeState.event;\n\n\tprivate _isVisible: boolean = false;\n\treadonly id: string;\n\n\tprivate _title: string;\n\tpublic get title(): string {\n\t\treturn this._title;\n\t}\n\n\tprivate _titleDescription: string | undefined;\n\tpublic get titleDescription(): string | undefined {\n\t\treturn this._titleDescription;\n\t}\n\n\treadonly menuActions: CompositeMenuActions;\n\n\tprivate progressBar!: ProgressBar;\n\tprivate progressIndicator!: IProgressIndicator;\n\n\tprivate toolbar?: WorkbenchToolBar;\n\tprivate readonly showActions: ViewPaneShowActions;\n\tprivate headerContainer?: HTMLElement;\n\tprivate titleContainer?: HTMLElement;\n\tprivate titleDescriptionContainer?: HTMLElement;\n\tprivate iconContainer?: HTMLElement;\n\tprotected twistiesContainer?: HTMLElement;\n\n\tprivate bodyContainer!: HTMLElement;\n\tprivate viewWelcomeContainer!: HTMLElement;\n\tprivate viewWelcomeDisposable: IDisposable = Disposable.None;\n\tprivate viewWelcomeController: ViewWelcomeController;\n\n\tprotected readonly scopedContextKeyService: IContextKeyService;\n\n\tconstructor(\n\t\toptions: IViewPaneOptions,\n\t\t@IKeybindingService protected keybindingService: IKeybindingService,\n\t\t@IContextMenuService protected contextMenuService: IContextMenuService,\n\t\t@IConfigurationService protected readonly configurationService: IConfigurationService,\n\t\t@IContextKeyService protected contextKeyService: IContextKeyService,\n\t\t@IViewDescriptorService protected viewDescriptorService: IViewDescriptorService,\n\t\t@IInstantiationService protected instantiationService: IInstantiationService,\n\t\t@IOpenerService protected openerService: IOpenerService,\n\t\t@IThemeService protected themeService: IThemeService,\n\t\t@ITelemetryService protected telemetryService: ITelemetryService,\n\t) {\n\t\tsuper({ ...options, ...{ orientation: viewDescriptorService.getViewLocationById(options.id) === ViewContainerLocation.Panel ? Orientation.HORIZONTAL : Orientation.VERTICAL } });\n\n\t\tthis.id = options.id;\n\t\tthis._title = options.title;\n\t\tthis._titleDescription = options.titleDescription;\n\t\tthis.showActions = options.showActions ?? ViewPaneShowActions.Default;\n\n\t\tthis.scopedContextKeyService = this._register(contextKeyService.createScoped(this.element));\n\t\tthis.scopedContextKeyService.createKey('view', this.id);\n\t\tconst viewLocationKey = this.scopedContextKeyService.createKey('viewLocation', ViewContainerLocationToString(viewDescriptorService.getViewLocationById(this.id)!));\n\t\tthis._register(Event.filter(viewDescriptorService.onDidChangeLocation, e => e.views.some(view => view.id === this.id))(() => viewLocationKey.set(ViewContainerLocationToString(viewDescriptorService.getViewLocationById(this.id)!))));\n\n\t\tthis.menuActions = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService])).createInstance(CompositeMenuActions, options.titleMenuId ?? MenuId.ViewTitle, MenuId.ViewTitleContext, { shouldForwardArgs: !options.donotForwardArgs }));\n\t\tthis._register(this.menuActions.onDidChange(() => this.updateActions()));\n\n\t\tthis.viewWelcomeController = this._register(new ViewWelcomeController(this.id, contextKeyService));\n\t}\n\n\toverride get headerVisible(): boolean {\n\t\treturn super.headerVisible;\n\t}\n\n\toverride set headerVisible(visible: boolean) {\n\t\tsuper.headerVisible = visible;\n\t\tthis.element.classList.toggle('merged-header', !visible);\n\t}\n\n\tsetVisible(visible: boolean): void {\n\t\tif (this._isVisible !== visible) {\n\t\t\tthis._isVisible = visible;\n\n\t\t\tif (this.isExpanded()) {\n\t\t\t\tthis._onDidChangeBodyVisibility.fire(visible);\n\t\t\t}\n\t\t}\n\t}\n\n\tisVisible(): boolean {\n\t\treturn this._isVisible;\n\t}\n\n\tisBodyVisible(): boolean {\n\t\treturn this._isVisible && this.isExpanded();\n\t}\n\n\toverride setExpanded(expanded: boolean): boolean {\n\t\tconst changed = super.setExpanded(expanded);\n\t\tif (changed) {\n\t\t\tthis._onDidChangeBodyVisibility.fire(expanded);\n\t\t}\n\t\tif (this.twistiesContainer) {\n\t\t\tthis.twistiesContainer.classList.remove(...ThemeIcon.asClassNameArray(this.getTwistyIcon(!expanded)));\n\t\t\tthis.twistiesContainer.classList.add(...ThemeIcon.asClassNameArray(this.getTwistyIcon(expanded)));\n\t\t}\n\t\treturn changed;\n\t}\n\n\toverride render(): void {\n\t\tsuper.render();\n\n\t\tconst focusTracker = trackFocus(this.element);\n\t\tthis._register(focusTracker);\n\t\tthis._register(focusTracker.onDidFocus(() => this._onDidFocus.fire()));\n\t\tthis._register(focusTracker.onDidBlur(() => this._onDidBlur.fire()));\n\t}\n\n\tprotected renderHeader(container: HTMLElement): void {\n\t\tthis.headerContainer = container;\n\n\t\tthis.twistiesContainer = append(container, $(ThemeIcon.asCSSSelector(this.getTwistyIcon(this.isExpanded()))));\n\n\t\tthis.renderHeaderTitle(container, this.title);\n\n\t\tconst actions = append(container, $('.actions'));\n\t\tactions.classList.toggle('show-always', this.showActions === ViewPaneShowActions.Always);\n\t\tactions.classList.toggle('show-expanded', this.showActions === ViewPaneShowActions.WhenExpanded);\n\t\tthis.toolbar = this.instantiationService.createInstance(WorkbenchToolBar, actions, {\n\t\t\torientation: ActionsOrientation.HORIZONTAL,\n\t\t\tactionViewItemProvider: action => this.getActionViewItem(action),\n\t\t\tariaLabel: nls.localize('viewToolbarAriaLabel', \"{0} actions\", this.title),\n\t\t\tgetKeyBinding: action => this.keybindingService.lookupKeybinding(action.id),\n\t\t\trenderDropdownAsChildElement: true,\n\t\t\tactionRunner: this.getActionRunner(),\n\t\t\tresetMenu: this.menuActions.menuId\n\t\t});\n\n\t\tthis._register(this.toolbar);\n\t\tthis.setActions();\n\n\t\tthis._register(addDisposableListener(actions, EventType.CLICK, e => e.preventDefault()));\n\n\t\tconst viewContainerModel = this.viewDescriptorService.getViewContainerByViewId(this.id);\n\t\tif (viewContainerModel) {\n\t\t\tthis._register(this.viewDescriptorService.getViewContainerModel(viewContainerModel).onDidChangeContainerInfo(({ title }) => this.updateTitle(this.title)));\n\t\t} else {\n\t\t\tconsole.error(`View container model not found for view ${this.id}`);\n\t\t}\n\n\t\tconst onDidRelevantConfigurationChange = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(ViewPane.AlwaysShowActionsConfig));\n\t\tthis._register(onDidRelevantConfigurationChange(this.updateActionsVisibility, this));\n\t\tthis.updateActionsVisibility();\n\t}\n\n\tprotected getTwistyIcon(expanded: boolean): ThemeIcon {\n\t\treturn expanded ? viewPaneContainerExpandedIcon : viewPaneContainerCollapsedIcon;\n\t}\n\n\toverride style(styles: IPaneStyles): void {\n\t\tsuper.style(styles);\n\n\t\tconst icon = this.getIcon();\n\t\tif (this.iconContainer) {\n\t\t\tconst fgColor = asCssValueWithDefault(styles.headerForeground, asCssVariable(foreground));\n\t\t\tif (URI.isUri(icon)) {\n\t\t\t\t// Apply background color to activity bar item provided with iconUrls\n\t\t\t\tthis.iconContainer.style.backgroundColor = fgColor;\n\t\t\t\tthis.iconContainer.style.color = '';\n\t\t\t} else {\n\t\t\t\t// Apply foreground color to activity bar items provided with codicons\n\t\t\t\tthis.iconContainer.style.color = fgColor;\n\t\t\t\tthis.iconContainer.style.backgroundColor = '';\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate getIcon(): ThemeIcon | URI {\n\t\treturn this.viewDescriptorService.getViewDescriptorById(this.id)?.containerIcon || defaultViewIcon;\n\t}\n\n\tprotected renderHeaderTitle(container: HTMLElement, title: string): void {\n\t\tthis.iconContainer = append(container, $('.icon', undefined));\n\t\tconst icon = this.getIcon();\n\n\t\tlet cssClass: string | undefined = undefined;\n\t\tif (URI.isUri(icon)) {\n\t\t\tcssClass = `view-${this.id.replace(/[\\.\\:]/g, '-')}`;\n\t\t\tconst iconClass = `.pane-header .icon.${cssClass}`;\n\n\t\t\tcreateCSSRule(iconClass, `\n\t\t\t\tmask: ${asCSSUrl(icon)} no-repeat 50% 50%;\n\t\t\t\tmask-size: 24px;\n\t\t\t\t-webkit-mask: ${asCSSUrl(icon)} no-repeat 50% 50%;\n\t\t\t\t-webkit-mask-size: 16px;\n\t\t\t`);\n\t\t} else if (ThemeIcon.isThemeIcon(icon)) {\n\t\t\tcssClass = ThemeIcon.asClassName(icon);\n\t\t}\n\n\t\tif (cssClass) {\n\t\t\tthis.iconContainer.classList.add(...cssClass.split(' '));\n\t\t}\n\n\t\tconst calculatedTitle = this.calculateTitle(title);\n\t\tthis.titleContainer = append(container, $('h3.title', { title: calculatedTitle }, calculatedTitle));\n\n\t\tif (this._titleDescription) {\n\t\t\tthis.setTitleDescription(this._titleDescription);\n\t\t}\n\n\t\tthis.iconContainer.title = calculatedTitle;\n\t\tthis.iconContainer.setAttribute('aria-label', calculatedTitle);\n\t}\n\n\tprotected updateTitle(title: string): void {\n\t\tconst calculatedTitle = this.calculateTitle(title);\n\t\tif (this.titleContainer) {\n\t\t\tthis.titleContainer.textContent = calculatedTitle;\n\t\t\tthis.titleContainer.setAttribute('title', calculatedTitle);\n\t\t}\n\n\t\tif (this.iconContainer) {\n\t\t\tthis.iconContainer.title = calculatedTitle;\n\t\t\tthis.iconContainer.setAttribute('aria-label', calculatedTitle);\n\t\t}\n\n\t\tthis._title = title;\n\t\tthis._onDidChangeTitleArea.fire();\n\t}\n\n\tprivate setTitleDescription(description: string | undefined) {\n\t\tif (this.titleDescriptionContainer) {\n\t\t\tthis.titleDescriptionContainer.textContent = description ?? '';\n\t\t\tthis.titleDescriptionContainer.setAttribute('title', description ?? '');\n\t\t}\n\t\telse if (description && this.titleContainer) {\n\t\t\tthis.titleDescriptionContainer = after(this.titleContainer, $('span.description', { title: description }, description));\n\t\t}\n\t}\n\n\tprotected updateTitleDescription(description?: string | undefined): void {\n\t\tthis.setTitleDescription(description);\n\n\t\tthis._titleDescription = description;\n\t\tthis._onDidChangeTitleArea.fire();\n\t}\n\n\tprivate calculateTitle(title: string): string {\n\t\tconst viewContainer = this.viewDescriptorService.getViewContainerByViewId(this.id)!;\n\t\tconst model = this.viewDescriptorService.getViewContainerModel(viewContainer);\n\t\tconst viewDescriptor = this.viewDescriptorService.getViewDescriptorById(this.id);\n\t\tconst isDefault = this.viewDescriptorService.getDefaultContainerById(this.id) === viewContainer;\n\n\t\tif (!isDefault && viewDescriptor?.containerTitle && model.title !== viewDescriptor.containerTitle) {\n\t\t\treturn `${viewDescriptor.containerTitle}: ${title}`;\n\t\t}\n\n\t\treturn title;\n\t}\n\n\tprivate scrollableElement!: DomScrollableElement;\n\n\tprotected renderBody(container: HTMLElement): void {\n\t\tthis.bodyContainer = container;\n\n\t\tconst viewWelcomeContainer = append(container, $('.welcome-view'));\n\t\tthis.viewWelcomeContainer = $('.welcome-view-content', { tabIndex: 0 });\n\t\tthis.scrollableElement = this._register(new DomScrollableElement(this.viewWelcomeContainer, {\n\t\t\talwaysConsumeMouseWheel: true,\n\t\t\thorizontal: ScrollbarVisibility.Hidden,\n\t\t\tvertical: ScrollbarVisibility.Visible,\n\t\t}));\n\n\t\tappend(viewWelcomeContainer, this.scrollableElement.getDomNode());\n\n\t\tconst onViewWelcomeChange = Event.any(this.viewWelcomeController.onDidChange, this.onDidChangeViewWelcomeState);\n\t\tthis._register(onViewWelcomeChange(this.updateViewWelcome, this));\n\t\tthis.updateViewWelcome();\n\t}\n\n\tprotected layoutBody(height: number, width: number): void {\n\t\tif (this.shouldShowWelcome()) {\n\t\t\tthis.viewWelcomeContainer.style.height = `${height}px`;\n\t\t\tthis.viewWelcomeContainer.style.width = `${width}px`;\n\t\t\tthis.viewWelcomeContainer.classList.toggle('wide', width > 640);\n\t\t\tthis.scrollableElement.scanDomNode();\n\t\t}\n\t}\n\n\tonDidScrollRoot() {\n\t\t// noop\n\t}\n\n\tgetProgressIndicator() {\n\t\tif (this.progressBar === undefined) {\n\t\t\t// Progress bar\n\t\t\tthis.progressBar = this._register(new ProgressBar(this.element, defaultProgressBarStyles));\n\t\t\tthis.progressBar.hide();\n\t\t}\n\n\t\tif (this.progressIndicator === undefined) {\n\t\t\tconst that = this;\n\t\t\tthis.progressIndicator = new ScopedProgressIndicator(assertIsDefined(this.progressBar), new class extends AbstractProgressScope {\n\t\t\t\tconstructor() {\n\t\t\t\t\tsuper(that.id, that.isBodyVisible());\n\t\t\t\t\tthis._register(that.onDidChangeBodyVisibility(isVisible => isVisible ? this.onScopeOpened(that.id) : this.onScopeClosed(that.id)));\n\t\t\t\t}\n\t\t\t}());\n\t\t}\n\t\treturn this.progressIndicator;\n\t}\n\n\tprotected getProgressLocation(): string {\n\t\treturn this.viewDescriptorService.getViewContainerByViewId(this.id)!.id;\n\t}\n\n\tprotected getBackgroundColor(): string {\n\t\tswitch (this.viewDescriptorService.getViewLocationById(this.id)) {\n\t\t\tcase ViewContainerLocation.Panel:\n\t\t\t\treturn PANEL_BACKGROUND;\n\t\t\tcase ViewContainerLocation.Sidebar:\n\t\t\tcase ViewContainerLocation.AuxiliaryBar:\n\t\t\t\treturn SIDE_BAR_BACKGROUND;\n\t\t}\n\n\t\treturn SIDE_BAR_BACKGROUND;\n\t}\n\n\tfocus(): void {\n\t\tif (this.shouldShowWelcome()) {\n\t\t\tthis.viewWelcomeContainer.focus();\n\t\t} else if (this.element) {\n\t\t\tthis.element.focus();\n\t\t\tthis._onDidFocus.fire();\n\t\t}\n\t}\n\n\tprivate setActions(): void {\n\t\tif (this.toolbar) {\n\t\t\tconst primaryActions = [...this.menuActions.getPrimaryActions()];\n\t\t\tif (this.shouldShowFilterInHeader()) {\n\t\t\t\tprimaryActions.unshift(VIEWPANE_FILTER_ACTION);\n\t\t\t}\n\t\t\tthis.toolbar.setActions(prepareActions(primaryActions), prepareActions(this.menuActions.getSecondaryActions()));\n\t\t\tthis.toolbar.context = this.getActionsContext();\n\t\t}\n\t}\n\n\tprivate updateActionsVisibility(): void {\n\t\tif (!this.headerContainer) {\n\t\t\treturn;\n\t\t}\n\t\tconst shouldAlwaysShowActions = this.configurationService.getValue('workbench.view.alwaysShowHeaderActions');\n\t\tthis.headerContainer.classList.toggle('actions-always-visible', shouldAlwaysShowActions);\n\t}\n\n\tprotected updateActions(): void {\n\t\tthis.setActions();\n\t\tthis._onDidChangeTitleArea.fire();\n\t}\n\n\tgetActionViewItem(action: IAction, options?: IDropdownMenuActionViewItemOptions): IActionViewItem | undefined {\n\t\tif (action.id === VIEWPANE_FILTER_ACTION.id) {\n\t\t\tconst that = this;\n\t\t\treturn new class extends BaseActionViewItem {\n\t\t\t\tconstructor() { super(null, action); }\n\t\t\t\toverride setFocusable(): void { /* noop input elements are focusable by default */ }\n\t\t\t\toverride get trapsArrowNavigation(): boolean { return true; }\n\t\t\t\toverride render(container: HTMLElement): void {\n\t\t\t\t\tcontainer.classList.add('viewpane-filter-container');\n\t\t\t\t\tappend(container, that.getFilterWidget()!.element);\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\t\treturn createActionViewItem(this.instantiationService, action, { ...options, ...{ menuAsChild: action instanceof SubmenuItemAction } });\n\t}\n\n\tgetActionsContext(): unknown {\n\t\treturn undefined;\n\t}\n\n\tgetActionRunner(): IActionRunner | undefined {\n\t\treturn undefined;\n\t}\n\n\tgetOptimalWidth(): number {\n\t\treturn 0;\n\t}\n\n\tsaveState(): void {\n\t\t// Subclasses to implement for saving state\n\t}\n\n\tprivate updateViewWelcome(): void {\n\t\tthis.viewWelcomeDisposable.dispose();\n\n\t\tif (!this.shouldShowWelcome()) {\n\t\t\tthis.bodyContainer.classList.remove('welcome');\n\t\t\tthis.viewWelcomeContainer.innerText = '';\n\t\t\tthis.scrollableElement.scanDomNode();\n\t\t\treturn;\n\t\t}\n\n\t\tconst contents = this.viewWelcomeController.contents;\n\n\t\tif (contents.length === 0) {\n\t\t\tthis.bodyContainer.classList.remove('welcome');\n\t\t\tthis.viewWelcomeContainer.innerText = '';\n\t\t\tthis.scrollableElement.scanDomNode();\n\t\t\treturn;\n\t\t}\n\n\t\tconst disposables = new DisposableStore();\n\t\tthis.bodyContainer.classList.add('welcome');\n\t\tthis.viewWelcomeContainer.innerText = '';\n\n\t\tfor (const { content, precondition } of contents) {\n\t\t\tconst lines = content.split('\\n');\n\n\t\t\tfor (let line of lines) {\n\t\t\t\tline = line.trim();\n\n\t\t\t\tif (!line) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst linkedText = parseLinkedText(line);\n\n\t\t\t\tif (linkedText.nodes.length === 1 && typeof linkedText.nodes[0] !== 'string') {\n\t\t\t\t\tconst node = linkedText.nodes[0];\n\t\t\t\t\tconst buttonContainer = append(this.viewWelcomeContainer, $('.button-container'));\n\t\t\t\t\tconst button = new Button(buttonContainer, { title: node.title, supportIcons: true, ...defaultButtonStyles });\n\t\t\t\t\tbutton.label = node.label;\n\t\t\t\t\tbutton.onDidClick(_ => {\n\t\t\t\t\t\tthis.telemetryService.publicLog2<{ viewId: string; uri: string }, WelcomeActionClassification>('views.welcomeAction', { viewId: this.id, uri: node.href });\n\t\t\t\t\t\tthis.openerService.open(node.href, { allowCommands: true });\n\t\t\t\t\t}, null, disposables);\n\t\t\t\t\tdisposables.add(button);\n\n\t\t\t\t\tif (precondition) {\n\t\t\t\t\t\tconst updateEnablement = () => button.enabled = this.contextKeyService.contextMatchesRules(precondition);\n\t\t\t\t\t\tupdateEnablement();\n\n\t\t\t\t\t\tconst keys = new Set();\n\t\t\t\t\t\tprecondition.keys().forEach(key => keys.add(key));\n\t\t\t\t\t\tconst onDidChangeContext = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(keys));\n\t\t\t\t\t\tonDidChangeContext(updateEnablement, null, disposables);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tconst p = append(this.viewWelcomeContainer, $('p'));\n\n\t\t\t\t\tfor (const node of linkedText.nodes) {\n\t\t\t\t\t\tif (typeof node === 'string') {\n\t\t\t\t\t\t\tappend(p, document.createTextNode(node));\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tconst link = disposables.add(this.instantiationService.createInstance(Link, p, node, {}));\n\n\t\t\t\t\t\t\tif (precondition && node.href.startsWith('command:')) {\n\t\t\t\t\t\t\t\tconst updateEnablement = () => link.enabled = this.contextKeyService.contextMatchesRules(precondition);\n\t\t\t\t\t\t\t\tupdateEnablement();\n\n\t\t\t\t\t\t\t\tconst keys = new Set();\n\t\t\t\t\t\t\t\tprecondition.keys().forEach(key => keys.add(key));\n\t\t\t\t\t\t\t\tconst onDidChangeContext = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(keys));\n\t\t\t\t\t\t\t\tonDidChangeContext(updateEnablement, null, disposables);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.scrollableElement.scanDomNode();\n\t\tthis.viewWelcomeDisposable = disposables;\n\t}\n\n\tshouldShowWelcome(): boolean {\n\t\treturn false;\n\t}\n\n\tgetFilterWidget()", + "fileName": "./1.tst" + }, + "modified": { + "content": "\n\tlayout(height: number, width: number) {\n\t\tif (!this.enabled) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.element!.style.height = `${height}px`;\n\t\tthis.element!.style.width = `${width}px`;\n\t\tthis.element!.classList.toggle('wide', width > 640);\n\t\tthis.scrollableElement!.scanDomNode();\n\t}\n\n\tfocus() {\n\t\tif (!this.enabled) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.element!.focus();\n\t}\n\n\tprivate onDidChangeViewWelcomeState(): void {\n\t\tconst enabled = this.delegate.shouldShowWelcome();\n\n\t\tif (this.enabled === enabled) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.enabled = enabled;\n\n\t\tif (!enabled) {\n\t\t\tthis.enabledDisposables.clear();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.container.classList.add('welcome');\n\t\tconst viewWelcomeContainer = append(this.container, $('.welcome-view'));\n\t\tthis.element = $('.welcome-view-content', { tabIndex: 0 });\n\t\tthis.scrollableElement = new DomScrollableElement(this.element, { alwaysConsumeMouseWheel: true, horizontal: ScrollbarVisibility.Hidden, vertical: ScrollbarVisibility.Visible, });\n\t\tappend(viewWelcomeContainer, this.scrollableElement.getDomNode());\n\n\t\tthis.enabledDisposables.add(toDisposable(() => {\n\t\t\tthis.container.classList.remove('welcome');\n\t\t\tthis.scrollableElement!.dispose();\n\t\t\tviewWelcomeContainer.remove();\n\t\t\tthis.scrollableElement = undefined;\n\t\t\tthis.element = undefined;\n\t\t}));\n\n\t\tthis.contextKeyService.onDidChangeContext(this.onDidChangeContext, this, this.enabledDisposables);\n\t\tEvent.chain(viewsRegistry.onDidChangeViewWelcomeContent, $ => $.filter(id => id === this.delegate.id))\n\t\t\t(this.onDidChangeViewWelcomeContent, this, this.enabledDisposables);\n\t\tthis.onDidChangeViewWelcomeContent();\n\t}\n\n\tprivate onDidChangeViewWelcomeContent(): void {\n\t\tconst descriptors = viewsRegistry.getViewWelcomeContent(this.delegate.id);\n\n\t\tthis.items = [];\n\n\t\tfor (const descriptor of descriptors) {\n\t\t\tif (descriptor.when === 'default') {\n\t\t\t\tthis.defaultItem = { descriptor, visible: true };\n\t\t\t} else {\n\t\t\t\tconst visible = descriptor.when ? this.contextKeyService.contextMatchesRules(descriptor.when) : true;\n\t\t\t\tthis.items.push({ descriptor, visible });\n\t\t\t}\n\t\t}\n\n\t\tthis.render();\n\t}\n\n\tprivate onDidChangeContext(): void {\n\t\tlet didChange = false;\n\n\t\tfor (const item of this.items) {\n\t\t\tif (!item.descriptor.when || item.descriptor.when === 'default') {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst visible = this.contextKeyService.contextMatchesRules(item.descriptor.when);\n\n\t\t\tif (item.visible === visible) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\titem.visible = visible;\n\t\t\tdidChange = true;\n\t\t}\n\n\t\tif (didChange) {\n\t\t\tthis.render();\n\t\t}\n\t}\n\n\tprivate render(): void {\n\t\tthis.renderDisposables.clear();\n\n\t\tconst contents = this.getContentDescriptors();\n\n\t\tif (contents.length === 0) {\n\t\t\tthis.container.classList.remove('welcome');\n\t\t\tthis.element!.innerText = '';\n\t\t\tthis.scrollableElement!.scanDomNode();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.container.classList.add('welcome');\n\t\tthis.element!.innerText = '';\n\n\t\tfor (const { content, precondition } of contents) {\n\t\t\tconst lines = content.split('\\n');\n\n\t\t\tfor (let line of lines) {\n\t\t\t\tline = line.trim();\n\n\t\t\t\tif (!line) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst linkedText = parseLinkedText(line);\n\n\t\t\t\tif (linkedText.nodes.length === 1 && typeof linkedText.nodes[0] !== 'string') {\n\t\t\t\t\tconst node = linkedText.nodes[0];\n\t\t\t\t\tconst buttonContainer = append(this.element!, $('.button-container'));\n\t\t\t\t\tconst button = new Button(buttonContainer, { title: node.title, supportIcons: true, ...defaultButtonStyles });\n\t\t\t\t\tbutton.label = node.label;\n\t\t\t\t\tbutton.onDidClick(_ => {\n\t\t\t\t\t\tthis.telemetryService.publicLog2<{ viewId: string; uri: string }, WelcomeActionClassification>('views.welcomeAction', { viewId: this.delegate.id, uri: node.href });\n\t\t\t\t\t\tthis.openerService.open(node.href, { allowCommands: true });\n\t\t\t\t\t}, null, this.renderDisposables);\n\t\t\t\t\tthis.renderDisposables.add(button);\n\n\t\t\t\t\tif (precondition) {\n\t\t\t\t\t\tconst updateEnablement = () => button.enabled = this.contextKeyService.contextMatchesRules(precondition);\n\t\t\t\t\t\tupdateEnablement();\n\n\t\t\t\t\t\tconst keys = new Set(precondition.keys());\n\t\t\t\t\t\tconst onDidChangeContext = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(keys));\n\t\t\t\t\t\tonDidChangeContext(updateEnablement, null, this.renderDisposables);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tconst p = append(this.element!, $('p'));\n\n\t\t\t\t\tfor (const node of linkedText.nodes) {\n\t\t\t\t\t\tif (typeof node === 'string') {\n\t\t\t\t\t\t\tappend(p, document.createTextNode(node));\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tconst link = this.renderDisposables.add(this.instantiationService.createInstance(Link, p, node, {}));\n\n\t\t\t\t\t\t\tif (precondition && node.href.startsWith('command:')) {\n\t\t\t\t\t\t\t\tconst updateEnablement = () => link.enabled = this.contextKeyService.contextMatchesRules(precondition);\n\t\t\t\t\t\t\t\tupdateEnablement();\n\n\t\t\t\t\t\t\t\tconst keys = new Set(precondition.keys());\n\t\t\t\t\t\t\t\tconst onDidChangeContext = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(keys));\n\t\t\t\t\t\t\t\tonDidChangeContext(updateEnablement, null, this.renderDisposables);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.scrollableElement!.scanDomNode();\n\t}\n\n\tprivate getContentDescriptors(): IViewContentDescriptor[] {\n\t\tconst visibleItems = this.items.filter(v => v.visible);\n\n\t\tif (visibleItems.length === 0 && this.defaultItem) {\n\t\t\treturn [this.defaultItem.descriptor];\n\t\t}\n\n\t\treturn visibleItems.map(v => v.descriptor);\n\t}\n\n\tdispose(): void {\n\t\tthis.disposables.dispose();\n\t}\n\t}\n\n\texport abstract class ViewPane extends Pane implements IView {\n\n\tprivate static readonly AlwaysShowActionsConfig = 'workbench.view.alwaysShowHeaderActions';\n\n\tprivate _onDidFocus = this._register(new Emitter());\n\treadonly onDidFocus: Event = this._onDidFocus.event;\n\n\tprivate _onDidBlur = this._register(new Emitter());\n\treadonly onDidBlur: Event = this._onDidBlur.event;\n\n\tprivate _onDidChangeBodyVisibility = this._register(new Emitter());\n\treadonly onDidChangeBodyVisibility: Event = this._onDidChangeBodyVisibility.event;\n\n\tprotected _onDidChangeTitleArea = this._register(new Emitter());\n\treadonly onDidChangeTitleArea: Event = this._onDidChangeTitleArea.event;\n\n\tprotected _onDidChangeViewWelcomeState = this._register(new Emitter());\n\treadonly onDidChangeViewWelcomeState: Event = this._onDidChangeViewWelcomeState.event;\n\n\tprivate _isVisible: boolean = false;\n\treadonly id: string;\n\n\tprivate _title: string;\n\tpublic get title(): string {\n\t\treturn this._title;\n\t}\n\n\tprivate _titleDescription: string | undefined;\n\tpublic get titleDescription(): string | undefined {\n\t\treturn this._titleDescription;\n\t}\n\n\treadonly menuActions: CompositeMenuActions;\n\n\tprivate progressBar!: ProgressBar;\n\tprivate progressIndicator!: IProgressIndicator;\n\n\tprivate toolbar?: WorkbenchToolBar;\n\tprivate readonly showActions: ViewPaneShowActions;\n\tprivate headerContainer?: HTMLElement;\n\tprivate titleContainer?: HTMLElement;\n\tprivate titleDescriptionContainer?: HTMLElement;\n\tprivate iconContainer?: HTMLElement;\n\tprotected twistiesContainer?: HTMLElement;\n\tprivate viewWelcomeController!: ViewWelcomeController;\n\n\tprotected readonly scopedContextKeyService: IContextKeyService;\n\n\tconstructor(\n\t\toptions: IViewPaneOptions,\n\t\t@IKeybindingService protected keybindingService: IKeybindingService,\n\t\t@IContextMenuService protected contextMenuService: IContextMenuService,\n\t\t@IConfigurationService protected readonly configurationService: IConfigurationService,\n\t\t@IContextKeyService protected contextKeyService: IContextKeyService,\n\t\t@IViewDescriptorService protected viewDescriptorService: IViewDescriptorService,\n\t\t@IInstantiationService protected instantiationService: IInstantiationService,\n\t\t@IOpenerService protected openerService: IOpenerService,\n\t\t@IThemeService protected themeService: IThemeService,\n\t\t@ITelemetryService protected telemetryService: ITelemetryService,\n\t) {\n\t\tsuper({ ...options, ...{ orientation: viewDescriptorService.getViewLocationById(options.id) === ViewContainerLocation.Panel ? Orientation.HORIZONTAL : Orientation.VERTICAL } });\n\n\t\tthis.id = options.id;\n\t\tthis._title = options.title;\n\t\tthis._titleDescription = options.titleDescription;\n\t\tthis.showActions = options.showActions ?? ViewPaneShowActions.Default;\n\n\t\tthis.scopedContextKeyService = this._register(contextKeyService.createScoped(this.element));\n\t\tthis.scopedContextKeyService.createKey('view', this.id);\n\t\tconst viewLocationKey = this.scopedContextKeyService.createKey('viewLocation', ViewContainerLocationToString(viewDescriptorService.getViewLocationById(this.id)!));\n\t\tthis._register(Event.filter(viewDescriptorService.onDidChangeLocation, e => e.views.some(view => view.id === this.id))(() => viewLocationKey.set(ViewContainerLocationToString(viewDescriptorService.getViewLocationById(this.id)!))));\n\n\t\tthis.menuActions = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService])).createInstance(CompositeMenuActions, options.titleMenuId ?? MenuId.ViewTitle, MenuId.ViewTitleContext, { shouldForwardArgs: !options.donotForwardArgs }));\n\t\tthis._register(this.menuActions.onDidChange(() => this.updateActions()));\n\t}\n\n\toverride get headerVisible(): boolean {\n\t\treturn super.headerVisible;\n\t}\n\n\toverride set headerVisible(visible: boolean) {\n\t\tsuper.headerVisible = visible;\n\t\tthis.element.classList.toggle('merged-header', !visible);\n\t}\n\n\tsetVisible(visible: boolean): void {\n\t\tif (this._isVisible !== visible) {\n\t\t\tthis._isVisible = visible;\n\n\t\t\tif (this.isExpanded()) {\n\t\t\t\tthis._onDidChangeBodyVisibility.fire(visible);\n\t\t\t}\n\t\t}\n\t}\n\n\tisVisible(): boolean {\n\t\treturn this._isVisible;\n\t}\n\n\tisBodyVisible(): boolean {\n\t\treturn this._isVisible && this.isExpanded();\n\t}\n\n\toverride setExpanded(expanded: boolean): boolean {\n\t\tconst changed = super.setExpanded(expanded);\n\t\tif (changed) {\n\t\t\tthis._onDidChangeBodyVisibility.fire(expanded);\n\t\t}\n\t\tif (this.twistiesContainer) {\n\t\t\tthis.twistiesContainer.classList.remove(...ThemeIcon.asClassNameArray(this.getTwistyIcon(!expanded)));\n\t\t\tthis.twistiesContainer.classList.add(...ThemeIcon.asClassNameArray(this.getTwistyIcon(expanded)));\n\t\t}\n\t\treturn changed;\n\t}\n\n\toverride render(): void {\n\t\tsuper.render();\n\n\t\tconst focusTracker = trackFocus(this.element);\n\t\tthis._register(focusTracker);\n\t\tthis._register(focusTracker.onDidFocus(() => this._onDidFocus.fire()));\n\t\tthis._register(focusTracker.onDidBlur(() => this._onDidBlur.fire()));\n\t}\n\n\tprotected renderHeader(container: HTMLElement): void {\n\t\tthis.headerContainer = container;\n\n\t\tthis.twistiesContainer = append(container, $(ThemeIcon.asCSSSelector(this.getTwistyIcon(this.isExpanded()))));\n\n\t\tthis.renderHeaderTitle(container, this.title);\n\n\t\tconst actions = append(container, $('.actions'));\n\t\tactions.classList.toggle('show-always', this.showActions === ViewPaneShowActions.Always);\n\t\tactions.classList.toggle('show-expanded', this.showActions === ViewPaneShowActions.WhenExpanded);\n\t\tthis.toolbar = this.instantiationService.createInstance(WorkbenchToolBar, actions, {\n\t\t\torientation: ActionsOrientation.HORIZONTAL,\n\t\t\tactionViewItemProvider: action => this.getActionViewItem(action),\n\t\t\tariaLabel: nls.localize('viewToolbarAriaLabel', \"{0} actions\", this.title),\n\t\t\tgetKeyBinding: action => this.keybindingService.lookupKeybinding(action.id),\n\t\t\trenderDropdownAsChildElement: true,\n\t\t\tactionRunner: this.getActionRunner(),\n\t\t\tresetMenu: this.menuActions.menuId\n\t\t});\n\n\t\tthis._register(this.toolbar);\n\t\tthis.setActions();\n\n\t\tthis._register(addDisposableListener(actions, EventType.CLICK, e => e.preventDefault()));\n\n\t\tconst viewContainerModel = this.viewDescriptorService.getViewContainerByViewId(this.id);\n\t\tif (viewContainerModel) {\n\t\t\tthis._register(this.viewDescriptorService.getViewContainerModel(viewContainerModel).onDidChangeContainerInfo(({ title }) => this.updateTitle(this.title)));\n\t\t} else {\n\t\t\tconsole.error(`View container model not found for view ${this.id}`);\n\t\t}\n\n\t\tconst onDidRelevantConfigurationChange = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(ViewPane.AlwaysShowActionsConfig));\n\t\tthis._register(onDidRelevantConfigurationChange(this.updateActionsVisibility, this));\n\t\tthis.updateActionsVisibility();\n\t}\n\n\tprotected getTwistyIcon(expanded: boolean): ThemeIcon {\n\t\treturn expanded ? viewPaneContainerExpandedIcon : viewPaneContainerCollapsedIcon;\n\t}\n\n\toverride style(styles: IPaneStyles): void {\n\t\tsuper.style(styles);\n\n\t\tconst icon = this.getIcon();\n\t\tif (this.iconContainer) {\n\t\t\tconst fgColor = asCssValueWithDefault(styles.headerForeground, asCssVariable(foreground));\n\t\t\tif (URI.isUri(icon)) {\n\t\t\t\t// Apply background color to activity bar item provided with iconUrls\n\t\t\t\tthis.iconContainer.style.backgroundColor = fgColor;\n\t\t\t\tthis.iconContainer.style.color = '';\n\t\t\t} else {\n\t\t\t\t// Apply foreground color to activity bar items provided with codicons\n\t\t\t\tthis.iconContainer.style.color = fgColor;\n\t\t\t\tthis.iconContainer.style.backgroundColor = '';\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate getIcon(): ThemeIcon | URI {\n\t\treturn this.viewDescriptorService.getViewDescriptorById(this.id)?.containerIcon || defaultViewIcon;\n\t}\n\n\tprotected renderHeaderTitle(container: HTMLElement, title: string): void {\n\t\tthis.iconContainer = append(container, $('.icon', undefined));\n\t\tconst icon = this.getIcon();\n\n\t\tlet cssClass: string | undefined = undefined;\n\t\tif (URI.isUri(icon)) {\n\t\t\tcssClass = `view-${this.id.replace(/[\\.\\:]/g, '-')}`;\n\t\t\tconst iconClass = `.pane-header .icon.${cssClass}`;\n\n\t\t\tcreateCSSRule(iconClass, `\n\t\t\t\tmask: ${asCSSUrl(icon)} no-repeat 50% 50%;\n\t\t\t\tmask-size: 24px;\n\t\t\t\t-webkit-mask: ${asCSSUrl(icon)} no-repeat 50% 50%;\n\t\t\t\t-webkit-mask-size: 16px;\n\t\t\t`);\n\t\t} else if (ThemeIcon.isThemeIcon(icon)) {\n\t\t\tcssClass = ThemeIcon.asClassName(icon);\n\t\t}\n\n\t\tif (cssClass) {\n\t\t\tthis.iconContainer.classList.add(...cssClass.split(' '));\n\t\t}\n\n\t\tconst calculatedTitle = this.calculateTitle(title);\n\t\tthis.titleContainer = append(container, $('h3.title', { title: calculatedTitle }, calculatedTitle));\n\n\t\tif (this._titleDescription) {\n\t\t\tthis.setTitleDescription(this._titleDescription);\n\t\t}\n\n\t\tthis.iconContainer.title = calculatedTitle;\n\t\tthis.iconContainer.setAttribute('aria-label', calculatedTitle);\n\t}\n\n\tprotected updateTitle(title: string): void {\n\t\tconst calculatedTitle = this.calculateTitle(title);\n\t\tif (this.titleContainer) {\n\t\t\tthis.titleContainer.textContent = calculatedTitle;\n\t\t\tthis.titleContainer.setAttribute('title', calculatedTitle);\n\t\t}\n\n\t\tif (this.iconContainer) {\n\t\t\tthis.iconContainer.title = calculatedTitle;\n\t\t\tthis.iconContainer.setAttribute('aria-label', calculatedTitle);\n\t\t}\n\n\t\tthis._title = title;\n\t\tthis._onDidChangeTitleArea.fire();\n\t}\n\n\tprivate setTitleDescription(description: string | undefined) {\n\t\tif (this.titleDescriptionContainer) {\n\t\t\tthis.titleDescriptionContainer.textContent = description ?? '';\n\t\t\tthis.titleDescriptionContainer.setAttribute('title', description ?? '');\n\t\t}\n\t\telse if (description && this.titleContainer) {\n\t\t\tthis.titleDescriptionContainer = after(this.titleContainer, $('span.description', { title: description }, description));\n\t\t}\n\t}\n\n\tprotected updateTitleDescription(description?: string | undefined): void {\n\t\tthis.setTitleDescription(description);\n\n\t\tthis._titleDescription = description;\n\t\tthis._onDidChangeTitleArea.fire();\n\t}\n\n\tprivate calculateTitle(title: string): string {\n\t\tconst viewContainer = this.viewDescriptorService.getViewContainerByViewId(this.id)!;\n\t\tconst model = this.viewDescriptorService.getViewContainerModel(viewContainer);\n\t\tconst viewDescriptor = this.viewDescriptorService.getViewDescriptorById(this.id);\n\t\tconst isDefault = this.viewDescriptorService.getDefaultContainerById(this.id) === viewContainer;\n\n\t\tif (!isDefault && viewDescriptor?.containerTitle && model.title !== viewDescriptor.containerTitle) {\n\t\t\treturn `${viewDescriptor.containerTitle}: ${title}`;\n\t\t}\n\n\t\treturn title;\n\t}\n\n\tprotected renderBody(container: HTMLElement): void {\n\t\tthis.viewWelcomeController = this._register(new ViewWelcomeController(container, this, this.instantiationService, this.openerService, this.telemetryService, this.contextKeyService));\n\t}\n\n\tprotected layoutBody(height: number, width: number): void {\n\t\tthis.viewWelcomeController.layout(height, width);\n\t}\n\n\tonDidScrollRoot() {\n\t\t// noop\n\t}\n\n\tgetProgressIndicator() {\n\t\tif (this.progressBar === undefined) {\n\t\t\t// Progress bar\n\t\t\tthis.progressBar = this._register(new ProgressBar(this.element, defaultProgressBarStyles));\n\t\t\tthis.progressBar.hide();\n\t\t}\n\n\t\tif (this.progressIndicator === undefined) {\n\t\t\tconst that = this;\n\t\t\tthis.progressIndicator = new ScopedProgressIndicator(assertIsDefined(this.progressBar), new class extends AbstractProgressScope {\n\t\t\t\tconstructor() {\n\t\t\t\t\tsuper(that.id, that.isBodyVisible());\n\t\t\t\t\tthis._register(that.onDidChangeBodyVisibility(isVisible => isVisible ? this.onScopeOpened(that.id) : this.onScopeClosed(that.id)));\n\t\t\t\t}\n\t\t\t}());\n\t\t}\n\t\treturn this.progressIndicator;\n\t}\n\n\tprotected getProgressLocation(): string {\n\t\treturn this.viewDescriptorService.getViewContainerByViewId(this.id)!.id;\n\t}\n\n\tprotected getBackgroundColor(): string {\n\t\tswitch (this.viewDescriptorService.getViewLocationById(this.id)) {\n\t\t\tcase ViewContainerLocation.Panel:\n\t\t\t\treturn PANEL_BACKGROUND;\n\t\t\tcase ViewContainerLocation.Sidebar:\n\t\t\tcase ViewContainerLocation.AuxiliaryBar:\n\t\t\t\treturn SIDE_BAR_BACKGROUND;\n\t\t}\n\n\t\treturn SIDE_BAR_BACKGROUND;\n\t}\n\n\tfocus(): void {\n\t\tif (this.shouldShowWelcome()) {\n\t\t\tthis.viewWelcomeController.focus();\n\t\t} else if (this.element) {\n\t\t\tthis.element.focus();\n\t\t\tthis._onDidFocus.fire();\n\t\t}\n\t}\n\n\tprivate setActions(): void {\n\t\tif (this.toolbar) {\n\t\t\tconst primaryActions = [...this.menuActions.getPrimaryActions()];\n\t\t\tif (this.shouldShowFilterInHeader()) {\n\t\t\t\tprimaryActions.unshift(VIEWPANE_FILTER_ACTION);\n\t\t\t}\n\t\t\tthis.toolbar.setActions(prepareActions(primaryActions), prepareActions(this.menuActions.getSecondaryActions()));\n\t\t\tthis.toolbar.context = this.getActionsContext();\n\t\t}\n\t}\n\n\tprivate updateActionsVisibility(): void {\n\t\tif (!this.headerContainer) {\n\t\t\treturn;\n\t\t}\n\t\tconst shouldAlwaysShowActions = this.configurationService.getValue('workbench.view.alwaysShowHeaderActions');\n\t\tthis.headerContainer.classList.toggle('actions-always-visible', shouldAlwaysShowActions);\n\t}\n\n\tprotected updateActions(): void {\n\t\tthis.setActions();\n\t\tthis._onDidChangeTitleArea.fire();\n\t}\n\n\tgetActionViewItem(action: IAction, options?: IDropdownMenuActionViewItemOptions): IActionViewItem | undefined {\n\t\tif (action.id === VIEWPANE_FILTER_ACTION.id) {\n\t\t\tconst that = this;\n\t\t\treturn new class extends BaseActionViewItem {\n\t\t\t\tconstructor() { super(null, action); }\n\t\t\t\toverride setFocusable(): void { /* noop input elements are focusable by default */ }\n\t\t\t\toverride get trapsArrowNavigation(): boolean { return true; }\n\t\t\t\toverride render(container: HTMLElement): void {\n\t\t\t\t\tcontainer.classList.add('viewpane-filter-container');\n\t\t\t\t\tappend(container, that.getFilterWidget()!.element);\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\t\treturn createActionViewItem(this.instantiationService, action, { ...options, ...{ menuAsChild: action instanceof SubmenuItemAction } });\n\t}\n\n\tgetActionsContext(): unknown {\n\t\treturn undefined;\n\t}\n\n\tgetActionRunner(): IActionRunner | undefined {\n\t\treturn undefined;\n\t}\n\n\tgetOptimalWidth(): number {\n\t\treturn 0;\n\t}\n\n\tsaveState(): void {\n\t\t// Subclasses to implement for saving state\n\t}\n\n\tshouldShowWelcome(): boolean {\n\t\treturn false;\n\t}\n\n\tgetFilterWidget()", + "fileName": "./2.tst" + }, + "diffs": [ + { + "originalRange": "[1,3)", + "modifiedRange": "[1,52)", + "innerChanges": [ + { + "originalRange": "[1,1 -> 2,37]", + "modifiedRange": "[1,1 -> 50,15]" + }, + { + "originalRange": "[2,82 -> 2,82]", + "modifiedRange": "[50,60 -> 50,74]" + }, + { + "originalRange": "[2,99 -> 2,99]", + "modifiedRange": "[50,91 -> 50,100]" + }, + { + "originalRange": "[2,103 -> 2,103]", + "modifiedRange": "[50,104 -> 51,4]" + }, + { + "originalRange": "[2,151 -> 2,152]", + "modifiedRange": "[51,52 -> 51,60]" + }, + { + "originalRange": "[2,163 -> 2,164]", + "modifiedRange": "[51,71 -> 51,71]" + } + ] + }, + { + "originalRange": "[7,8)", + "modifiedRange": "[56,57)", + "innerChanges": [ + { + "originalRange": "[7,63 -> 7,63]", + "modifiedRange": "[56,63 -> 56,72]" + } + ] + }, + { + "originalRange": "[20,21)", + "modifiedRange": "[69,70)", + "innerChanges": [ + { + "originalRange": "[20,8 -> 20,25]", + "modifiedRange": "[69,8 -> 69,14]" + } + ] + }, + { + "originalRange": "[42,44)", + "modifiedRange": "[91,175)", + "innerChanges": [ + { + "originalRange": "[42,9 -> 44,1]", + "modifiedRange": "[91,9 -> 175,1]" + } + ] + }, + { + "originalRange": "[49,50)", + "modifiedRange": "[180,181)", + "innerChanges": [ + { + "originalRange": "[49,1 -> 49,1]", + "modifiedRange": "[180,1 -> 180,2]" + } + ] + }, + { + "originalRange": "[51,52)", + "modifiedRange": "[182,183)", + "innerChanges": [ + { + "originalRange": "[51,1 -> 51,1]", + "modifiedRange": "[182,1 -> 182,2]" + } + ] + }, + { + "originalRange": "[95,100)", + "modifiedRange": "[226,227)", + "innerChanges": [ + { + "originalRange": "[95,1 -> 99,1]", + "modifiedRange": "[226,1 -> 226,1]" + }, + { + "originalRange": "[99,31 -> 99,31]", + "modifiedRange": "[226,31 -> 226,32]" + } + ] + }, + { + "originalRange": "[129,131)", + "modifiedRange": "[256,256)", + "innerChanges": [ + { + "originalRange": "[129,1 -> 131,1]", + "modifiedRange": "[256,1 -> 256,1]" + } + ] + }, + { + "originalRange": "[323,325)", + "modifiedRange": "[448,448)", + "innerChanges": [ + { + "originalRange": "[323,1 -> 325,1 EOL]", + "modifiedRange": "[448,1 -> 448,1 EOL]" + } + ] + }, + { + "originalRange": "[327,342)", + "modifiedRange": "[450,451)", + "innerChanges": [ + { + "originalRange": "[327,8 -> 342,1]", + "modifiedRange": "[450,8 -> 451,1]" + } + ] + }, + { + "originalRange": "[345,351)", + "modifiedRange": "[454,455)", + "innerChanges": [ + { + "originalRange": "[345,1 -> 346,2]", + "modifiedRange": "[454,1 -> 454,1]" + }, + { + "originalRange": "[346,24 -> 346,27]", + "modifiedRange": "[454,23 -> 454,27]" + }, + { + "originalRange": "[346,30 -> 346,48]", + "modifiedRange": "[454,30 -> 454,37]" + }, + { + "originalRange": "[346,54 -> 350,1]", + "modifiedRange": "[454,43 -> 455,1]" + }, + { + "originalRange": "[350,1 -> 351,1]", + "modifiedRange": "[455,1 -> 455,1]" + } + ] + }, + { + "originalRange": "[394,395)", + "modifiedRange": "[498,499)", + "innerChanges": [ + { + "originalRange": "[394,24 -> 394,27]", + "modifiedRange": "[498,24 -> 498,28]" + } + ] + }, + { + "originalRange": "[456,539)", + "modifiedRange": "[560,560)", + "innerChanges": [ + { + "originalRange": "[456,1 -> 539,1 EOL]", + "modifiedRange": "[560,1 -> 560,1 EOL]" + } + ] + } + ], + "moves": [ + { + "originalRange": "[477,537)", + "modifiedRange": "[107,165)", + "changes": [ + { + "originalRange": "[477,479)", + "modifiedRange": "[107,109)", + "innerChanges": [ + { + "originalRange": "[477,8 -> 477,13]", + "modifiedRange": "[107,8 -> 107,9]" + }, + { + "originalRange": "[478,8 -> 478,28]", + "modifiedRange": "[108,8 -> 108,16]" + } + ] + }, + { + "originalRange": "[494,495)", + "modifiedRange": "[124,125)", + "innerChanges": [ + { + "originalRange": "[494,42 -> 494,62]", + "modifiedRange": "[124,42 -> 124,50]" + } + ] + }, + { + "originalRange": "[498,499)", + "modifiedRange": "[128,129)", + "innerChanges": [ + { + "originalRange": "[498,139 -> 498,139]", + "modifiedRange": "[128,139 -> 128,148]" + } + ] + }, + { + "originalRange": "[500,502)", + "modifiedRange": "[130,132)", + "innerChanges": [ + { + "originalRange": "[500,15 -> 500,16]", + "modifiedRange": "[130,15 -> 130,27]" + }, + { + "originalRange": "[501,6 -> 501,7]", + "modifiedRange": "[131,6 -> 131,18]" + } + ] + }, + { + "originalRange": "[507,509)", + "modifiedRange": "[137,138)", + "innerChanges": [ + { + "originalRange": "[507,28 -> 508,7]", + "modifiedRange": "[137,28 -> 137,28]" + }, + { + "originalRange": "[508,25 -> 508,54]", + "modifiedRange": "[137,46 -> 137,46]" + } + ] + }, + { + "originalRange": "[510,511)", + "modifiedRange": "[139,140)", + "innerChanges": [ + { + "originalRange": "[510,50 -> 510,51]", + "modifiedRange": "[139,50 -> 139,62]" + } + ] + }, + { + "originalRange": "[513,514)", + "modifiedRange": "[142,143)", + "innerChanges": [ + { + "originalRange": "[513,28 -> 513,48]", + "modifiedRange": "[142,28 -> 142,36]" + } + ] + }, + { + "originalRange": "[519,520)", + "modifiedRange": "[148,149)", + "innerChanges": [ + { + "originalRange": "[519,21 -> 519,22]", + "modifiedRange": "[148,21 -> 148,33]" + } + ] + }, + { + "originalRange": "[525,527)", + "modifiedRange": "[154,155)", + "innerChanges": [ + { + "originalRange": "[525,30 -> 526,9]", + "modifiedRange": "[154,30 -> 154,30]" + }, + { + "originalRange": "[526,27 -> 526,56]", + "modifiedRange": "[154,48 -> 154,48]" + } + ] + }, + { + "originalRange": "[528,529)", + "modifiedRange": "[156,157)", + "innerChanges": [ + { + "originalRange": "[528,52 -> 528,53]", + "modifiedRange": "[156,52 -> 156,64]" + } + ] + }, + { + "originalRange": "[536,537)", + "modifiedRange": "[164,165)", + "innerChanges": [ + { + "originalRange": "[536,25 -> 536,25]", + "modifiedRange": "[164,25 -> 164,26]" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/code/src/vs/editor/test/node/diffing/fixtures/noisy-move1/legacy.expected.diff.json b/code/src/vs/editor/test/node/diffing/fixtures/noisy-move1/legacy.expected.diff.json new file mode 100644 index 00000000000..c6bdd931d44 --- /dev/null +++ b/code/src/vs/editor/test/node/diffing/fixtures/noisy-move1/legacy.expected.diff.json @@ -0,0 +1,178 @@ +{ + "original": { + "content": "\tcontextKeyService.onDidChangeContext(this.onDidChangeContext, this, this.disposables);\n\t\tthis.disposables.add(Event.filter(viewsRegistry.onDidChangeViewWelcomeContent, id => id === this.id)(this.onDidChangeViewWelcomeContent, this, this.disposables));\n\t\tthis.onDidChangeViewWelcomeContent();\n\t}\n\n\tprivate onDidChangeViewWelcomeContent(): void {\n\t\tconst descriptors = viewsRegistry.getViewWelcomeContent(this.id);\n\n\t\tthis.items = [];\n\n\t\tfor (const descriptor of descriptors) {\n\t\t\tif (descriptor.when === 'default') {\n\t\t\t\tthis.defaultItem = { descriptor, visible: true };\n\t\t\t} else {\n\t\t\t\tconst visible = descriptor.when ? this.contextKeyService.contextMatchesRules(descriptor.when) : true;\n\t\t\t\tthis.items.push({ descriptor, visible });\n\t\t\t}\n\t\t}\n\n\t\tthis._onDidChange.fire();\n\t}\n\n\tprivate onDidChangeContext(): void {\n\t\tlet didChange = false;\n\n\t\tfor (const item of this.items) {\n\t\t\tif (!item.descriptor.when || item.descriptor.when === 'default') {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst visible = this.contextKeyService.contextMatchesRules(item.descriptor.when);\n\n\t\t\tif (item.visible === visible) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\titem.visible = visible;\n\t\t\tdidChange = true;\n\t\t}\n\n\t\tif (didChange) {\n\t\t\tthis._onDidChange.fire();\n\t\t}\n\t}\n\n\tdispose(): void {\n\t\tthis.disposables.dispose();\n\t}\n}\n\nexport abstract class ViewPane extends Pane implements IView {\n\n\tprivate static readonly AlwaysShowActionsConfig = 'workbench.view.alwaysShowHeaderActions';\n\n\tprivate _onDidFocus = this._register(new Emitter());\n\treadonly onDidFocus: Event = this._onDidFocus.event;\n\n\tprivate _onDidBlur = this._register(new Emitter());\n\treadonly onDidBlur: Event = this._onDidBlur.event;\n\n\tprivate _onDidChangeBodyVisibility = this._register(new Emitter());\n\treadonly onDidChangeBodyVisibility: Event = this._onDidChangeBodyVisibility.event;\n\n\tprotected _onDidChangeTitleArea = this._register(new Emitter());\n\treadonly onDidChangeTitleArea: Event = this._onDidChangeTitleArea.event;\n\n\tprotected _onDidChangeViewWelcomeState = this._register(new Emitter());\n\treadonly onDidChangeViewWelcomeState: Event = this._onDidChangeViewWelcomeState.event;\n\n\tprivate _isVisible: boolean = false;\n\treadonly id: string;\n\n\tprivate _title: string;\n\tpublic get title(): string {\n\t\treturn this._title;\n\t}\n\n\tprivate _titleDescription: string | undefined;\n\tpublic get titleDescription(): string | undefined {\n\t\treturn this._titleDescription;\n\t}\n\n\treadonly menuActions: CompositeMenuActions;\n\n\tprivate progressBar!: ProgressBar;\n\tprivate progressIndicator!: IProgressIndicator;\n\n\tprivate toolbar?: WorkbenchToolBar;\n\tprivate readonly showActions: ViewPaneShowActions;\n\tprivate headerContainer?: HTMLElement;\n\tprivate titleContainer?: HTMLElement;\n\tprivate titleDescriptionContainer?: HTMLElement;\n\tprivate iconContainer?: HTMLElement;\n\tprotected twistiesContainer?: HTMLElement;\n\n\tprivate bodyContainer!: HTMLElement;\n\tprivate viewWelcomeContainer!: HTMLElement;\n\tprivate viewWelcomeDisposable: IDisposable = Disposable.None;\n\tprivate viewWelcomeController: ViewWelcomeController;\n\n\tprotected readonly scopedContextKeyService: IContextKeyService;\n\n\tconstructor(\n\t\toptions: IViewPaneOptions,\n\t\t@IKeybindingService protected keybindingService: IKeybindingService,\n\t\t@IContextMenuService protected contextMenuService: IContextMenuService,\n\t\t@IConfigurationService protected readonly configurationService: IConfigurationService,\n\t\t@IContextKeyService protected contextKeyService: IContextKeyService,\n\t\t@IViewDescriptorService protected viewDescriptorService: IViewDescriptorService,\n\t\t@IInstantiationService protected instantiationService: IInstantiationService,\n\t\t@IOpenerService protected openerService: IOpenerService,\n\t\t@IThemeService protected themeService: IThemeService,\n\t\t@ITelemetryService protected telemetryService: ITelemetryService,\n\t) {\n\t\tsuper({ ...options, ...{ orientation: viewDescriptorService.getViewLocationById(options.id) === ViewContainerLocation.Panel ? Orientation.HORIZONTAL : Orientation.VERTICAL } });\n\n\t\tthis.id = options.id;\n\t\tthis._title = options.title;\n\t\tthis._titleDescription = options.titleDescription;\n\t\tthis.showActions = options.showActions ?? ViewPaneShowActions.Default;\n\n\t\tthis.scopedContextKeyService = this._register(contextKeyService.createScoped(this.element));\n\t\tthis.scopedContextKeyService.createKey('view', this.id);\n\t\tconst viewLocationKey = this.scopedContextKeyService.createKey('viewLocation', ViewContainerLocationToString(viewDescriptorService.getViewLocationById(this.id)!));\n\t\tthis._register(Event.filter(viewDescriptorService.onDidChangeLocation, e => e.views.some(view => view.id === this.id))(() => viewLocationKey.set(ViewContainerLocationToString(viewDescriptorService.getViewLocationById(this.id)!))));\n\n\t\tthis.menuActions = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService])).createInstance(CompositeMenuActions, options.titleMenuId ?? MenuId.ViewTitle, MenuId.ViewTitleContext, { shouldForwardArgs: !options.donotForwardArgs }));\n\t\tthis._register(this.menuActions.onDidChange(() => this.updateActions()));\n\n\t\tthis.viewWelcomeController = this._register(new ViewWelcomeController(this.id, contextKeyService));\n\t}\n\n\toverride get headerVisible(): boolean {\n\t\treturn super.headerVisible;\n\t}\n\n\toverride set headerVisible(visible: boolean) {\n\t\tsuper.headerVisible = visible;\n\t\tthis.element.classList.toggle('merged-header', !visible);\n\t}\n\n\tsetVisible(visible: boolean): void {\n\t\tif (this._isVisible !== visible) {\n\t\t\tthis._isVisible = visible;\n\n\t\t\tif (this.isExpanded()) {\n\t\t\t\tthis._onDidChangeBodyVisibility.fire(visible);\n\t\t\t}\n\t\t}\n\t}\n\n\tisVisible(): boolean {\n\t\treturn this._isVisible;\n\t}\n\n\tisBodyVisible(): boolean {\n\t\treturn this._isVisible && this.isExpanded();\n\t}\n\n\toverride setExpanded(expanded: boolean): boolean {\n\t\tconst changed = super.setExpanded(expanded);\n\t\tif (changed) {\n\t\t\tthis._onDidChangeBodyVisibility.fire(expanded);\n\t\t}\n\t\tif (this.twistiesContainer) {\n\t\t\tthis.twistiesContainer.classList.remove(...ThemeIcon.asClassNameArray(this.getTwistyIcon(!expanded)));\n\t\t\tthis.twistiesContainer.classList.add(...ThemeIcon.asClassNameArray(this.getTwistyIcon(expanded)));\n\t\t}\n\t\treturn changed;\n\t}\n\n\toverride render(): void {\n\t\tsuper.render();\n\n\t\tconst focusTracker = trackFocus(this.element);\n\t\tthis._register(focusTracker);\n\t\tthis._register(focusTracker.onDidFocus(() => this._onDidFocus.fire()));\n\t\tthis._register(focusTracker.onDidBlur(() => this._onDidBlur.fire()));\n\t}\n\n\tprotected renderHeader(container: HTMLElement): void {\n\t\tthis.headerContainer = container;\n\n\t\tthis.twistiesContainer = append(container, $(ThemeIcon.asCSSSelector(this.getTwistyIcon(this.isExpanded()))));\n\n\t\tthis.renderHeaderTitle(container, this.title);\n\n\t\tconst actions = append(container, $('.actions'));\n\t\tactions.classList.toggle('show-always', this.showActions === ViewPaneShowActions.Always);\n\t\tactions.classList.toggle('show-expanded', this.showActions === ViewPaneShowActions.WhenExpanded);\n\t\tthis.toolbar = this.instantiationService.createInstance(WorkbenchToolBar, actions, {\n\t\t\torientation: ActionsOrientation.HORIZONTAL,\n\t\t\tactionViewItemProvider: action => this.getActionViewItem(action),\n\t\t\tariaLabel: nls.localize('viewToolbarAriaLabel', \"{0} actions\", this.title),\n\t\t\tgetKeyBinding: action => this.keybindingService.lookupKeybinding(action.id),\n\t\t\trenderDropdownAsChildElement: true,\n\t\t\tactionRunner: this.getActionRunner(),\n\t\t\tresetMenu: this.menuActions.menuId\n\t\t});\n\n\t\tthis._register(this.toolbar);\n\t\tthis.setActions();\n\n\t\tthis._register(addDisposableListener(actions, EventType.CLICK, e => e.preventDefault()));\n\n\t\tconst viewContainerModel = this.viewDescriptorService.getViewContainerByViewId(this.id);\n\t\tif (viewContainerModel) {\n\t\t\tthis._register(this.viewDescriptorService.getViewContainerModel(viewContainerModel).onDidChangeContainerInfo(({ title }) => this.updateTitle(this.title)));\n\t\t} else {\n\t\t\tconsole.error(`View container model not found for view ${this.id}`);\n\t\t}\n\n\t\tconst onDidRelevantConfigurationChange = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(ViewPane.AlwaysShowActionsConfig));\n\t\tthis._register(onDidRelevantConfigurationChange(this.updateActionsVisibility, this));\n\t\tthis.updateActionsVisibility();\n\t}\n\n\tprotected getTwistyIcon(expanded: boolean): ThemeIcon {\n\t\treturn expanded ? viewPaneContainerExpandedIcon : viewPaneContainerCollapsedIcon;\n\t}\n\n\toverride style(styles: IPaneStyles): void {\n\t\tsuper.style(styles);\n\n\t\tconst icon = this.getIcon();\n\t\tif (this.iconContainer) {\n\t\t\tconst fgColor = asCssValueWithDefault(styles.headerForeground, asCssVariable(foreground));\n\t\t\tif (URI.isUri(icon)) {\n\t\t\t\t// Apply background color to activity bar item provided with iconUrls\n\t\t\t\tthis.iconContainer.style.backgroundColor = fgColor;\n\t\t\t\tthis.iconContainer.style.color = '';\n\t\t\t} else {\n\t\t\t\t// Apply foreground color to activity bar items provided with codicons\n\t\t\t\tthis.iconContainer.style.color = fgColor;\n\t\t\t\tthis.iconContainer.style.backgroundColor = '';\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate getIcon(): ThemeIcon | URI {\n\t\treturn this.viewDescriptorService.getViewDescriptorById(this.id)?.containerIcon || defaultViewIcon;\n\t}\n\n\tprotected renderHeaderTitle(container: HTMLElement, title: string): void {\n\t\tthis.iconContainer = append(container, $('.icon', undefined));\n\t\tconst icon = this.getIcon();\n\n\t\tlet cssClass: string | undefined = undefined;\n\t\tif (URI.isUri(icon)) {\n\t\t\tcssClass = `view-${this.id.replace(/[\\.\\:]/g, '-')}`;\n\t\t\tconst iconClass = `.pane-header .icon.${cssClass}`;\n\n\t\t\tcreateCSSRule(iconClass, `\n\t\t\t\tmask: ${asCSSUrl(icon)} no-repeat 50% 50%;\n\t\t\t\tmask-size: 24px;\n\t\t\t\t-webkit-mask: ${asCSSUrl(icon)} no-repeat 50% 50%;\n\t\t\t\t-webkit-mask-size: 16px;\n\t\t\t`);\n\t\t} else if (ThemeIcon.isThemeIcon(icon)) {\n\t\t\tcssClass = ThemeIcon.asClassName(icon);\n\t\t}\n\n\t\tif (cssClass) {\n\t\t\tthis.iconContainer.classList.add(...cssClass.split(' '));\n\t\t}\n\n\t\tconst calculatedTitle = this.calculateTitle(title);\n\t\tthis.titleContainer = append(container, $('h3.title', { title: calculatedTitle }, calculatedTitle));\n\n\t\tif (this._titleDescription) {\n\t\t\tthis.setTitleDescription(this._titleDescription);\n\t\t}\n\n\t\tthis.iconContainer.title = calculatedTitle;\n\t\tthis.iconContainer.setAttribute('aria-label', calculatedTitle);\n\t}\n\n\tprotected updateTitle(title: string): void {\n\t\tconst calculatedTitle = this.calculateTitle(title);\n\t\tif (this.titleContainer) {\n\t\t\tthis.titleContainer.textContent = calculatedTitle;\n\t\t\tthis.titleContainer.setAttribute('title', calculatedTitle);\n\t\t}\n\n\t\tif (this.iconContainer) {\n\t\t\tthis.iconContainer.title = calculatedTitle;\n\t\t\tthis.iconContainer.setAttribute('aria-label', calculatedTitle);\n\t\t}\n\n\t\tthis._title = title;\n\t\tthis._onDidChangeTitleArea.fire();\n\t}\n\n\tprivate setTitleDescription(description: string | undefined) {\n\t\tif (this.titleDescriptionContainer) {\n\t\t\tthis.titleDescriptionContainer.textContent = description ?? '';\n\t\t\tthis.titleDescriptionContainer.setAttribute('title', description ?? '');\n\t\t}\n\t\telse if (description && this.titleContainer) {\n\t\t\tthis.titleDescriptionContainer = after(this.titleContainer, $('span.description', { title: description }, description));\n\t\t}\n\t}\n\n\tprotected updateTitleDescription(description?: string | undefined): void {\n\t\tthis.setTitleDescription(description);\n\n\t\tthis._titleDescription = description;\n\t\tthis._onDidChangeTitleArea.fire();\n\t}\n\n\tprivate calculateTitle(title: string): string {\n\t\tconst viewContainer = this.viewDescriptorService.getViewContainerByViewId(this.id)!;\n\t\tconst model = this.viewDescriptorService.getViewContainerModel(viewContainer);\n\t\tconst viewDescriptor = this.viewDescriptorService.getViewDescriptorById(this.id);\n\t\tconst isDefault = this.viewDescriptorService.getDefaultContainerById(this.id) === viewContainer;\n\n\t\tif (!isDefault && viewDescriptor?.containerTitle && model.title !== viewDescriptor.containerTitle) {\n\t\t\treturn `${viewDescriptor.containerTitle}: ${title}`;\n\t\t}\n\n\t\treturn title;\n\t}\n\n\tprivate scrollableElement!: DomScrollableElement;\n\n\tprotected renderBody(container: HTMLElement): void {\n\t\tthis.bodyContainer = container;\n\n\t\tconst viewWelcomeContainer = append(container, $('.welcome-view'));\n\t\tthis.viewWelcomeContainer = $('.welcome-view-content', { tabIndex: 0 });\n\t\tthis.scrollableElement = this._register(new DomScrollableElement(this.viewWelcomeContainer, {\n\t\t\talwaysConsumeMouseWheel: true,\n\t\t\thorizontal: ScrollbarVisibility.Hidden,\n\t\t\tvertical: ScrollbarVisibility.Visible,\n\t\t}));\n\n\t\tappend(viewWelcomeContainer, this.scrollableElement.getDomNode());\n\n\t\tconst onViewWelcomeChange = Event.any(this.viewWelcomeController.onDidChange, this.onDidChangeViewWelcomeState);\n\t\tthis._register(onViewWelcomeChange(this.updateViewWelcome, this));\n\t\tthis.updateViewWelcome();\n\t}\n\n\tprotected layoutBody(height: number, width: number): void {\n\t\tif (this.shouldShowWelcome()) {\n\t\t\tthis.viewWelcomeContainer.style.height = `${height}px`;\n\t\t\tthis.viewWelcomeContainer.style.width = `${width}px`;\n\t\t\tthis.viewWelcomeContainer.classList.toggle('wide', width > 640);\n\t\t\tthis.scrollableElement.scanDomNode();\n\t\t}\n\t}\n\n\tonDidScrollRoot() {\n\t\t// noop\n\t}\n\n\tgetProgressIndicator() {\n\t\tif (this.progressBar === undefined) {\n\t\t\t// Progress bar\n\t\t\tthis.progressBar = this._register(new ProgressBar(this.element, defaultProgressBarStyles));\n\t\t\tthis.progressBar.hide();\n\t\t}\n\n\t\tif (this.progressIndicator === undefined) {\n\t\t\tconst that = this;\n\t\t\tthis.progressIndicator = new ScopedProgressIndicator(assertIsDefined(this.progressBar), new class extends AbstractProgressScope {\n\t\t\t\tconstructor() {\n\t\t\t\t\tsuper(that.id, that.isBodyVisible());\n\t\t\t\t\tthis._register(that.onDidChangeBodyVisibility(isVisible => isVisible ? this.onScopeOpened(that.id) : this.onScopeClosed(that.id)));\n\t\t\t\t}\n\t\t\t}());\n\t\t}\n\t\treturn this.progressIndicator;\n\t}\n\n\tprotected getProgressLocation(): string {\n\t\treturn this.viewDescriptorService.getViewContainerByViewId(this.id)!.id;\n\t}\n\n\tprotected getBackgroundColor(): string {\n\t\tswitch (this.viewDescriptorService.getViewLocationById(this.id)) {\n\t\t\tcase ViewContainerLocation.Panel:\n\t\t\t\treturn PANEL_BACKGROUND;\n\t\t\tcase ViewContainerLocation.Sidebar:\n\t\t\tcase ViewContainerLocation.AuxiliaryBar:\n\t\t\t\treturn SIDE_BAR_BACKGROUND;\n\t\t}\n\n\t\treturn SIDE_BAR_BACKGROUND;\n\t}\n\n\tfocus(): void {\n\t\tif (this.shouldShowWelcome()) {\n\t\t\tthis.viewWelcomeContainer.focus();\n\t\t} else if (this.element) {\n\t\t\tthis.element.focus();\n\t\t\tthis._onDidFocus.fire();\n\t\t}\n\t}\n\n\tprivate setActions(): void {\n\t\tif (this.toolbar) {\n\t\t\tconst primaryActions = [...this.menuActions.getPrimaryActions()];\n\t\t\tif (this.shouldShowFilterInHeader()) {\n\t\t\t\tprimaryActions.unshift(VIEWPANE_FILTER_ACTION);\n\t\t\t}\n\t\t\tthis.toolbar.setActions(prepareActions(primaryActions), prepareActions(this.menuActions.getSecondaryActions()));\n\t\t\tthis.toolbar.context = this.getActionsContext();\n\t\t}\n\t}\n\n\tprivate updateActionsVisibility(): void {\n\t\tif (!this.headerContainer) {\n\t\t\treturn;\n\t\t}\n\t\tconst shouldAlwaysShowActions = this.configurationService.getValue('workbench.view.alwaysShowHeaderActions');\n\t\tthis.headerContainer.classList.toggle('actions-always-visible', shouldAlwaysShowActions);\n\t}\n\n\tprotected updateActions(): void {\n\t\tthis.setActions();\n\t\tthis._onDidChangeTitleArea.fire();\n\t}\n\n\tgetActionViewItem(action: IAction, options?: IDropdownMenuActionViewItemOptions): IActionViewItem | undefined {\n\t\tif (action.id === VIEWPANE_FILTER_ACTION.id) {\n\t\t\tconst that = this;\n\t\t\treturn new class extends BaseActionViewItem {\n\t\t\t\tconstructor() { super(null, action); }\n\t\t\t\toverride setFocusable(): void { /* noop input elements are focusable by default */ }\n\t\t\t\toverride get trapsArrowNavigation(): boolean { return true; }\n\t\t\t\toverride render(container: HTMLElement): void {\n\t\t\t\t\tcontainer.classList.add('viewpane-filter-container');\n\t\t\t\t\tappend(container, that.getFilterWidget()!.element);\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\t\treturn createActionViewItem(this.instantiationService, action, { ...options, ...{ menuAsChild: action instanceof SubmenuItemAction } });\n\t}\n\n\tgetActionsContext(): unknown {\n\t\treturn undefined;\n\t}\n\n\tgetActionRunner(): IActionRunner | undefined {\n\t\treturn undefined;\n\t}\n\n\tgetOptimalWidth(): number {\n\t\treturn 0;\n\t}\n\n\tsaveState(): void {\n\t\t// Subclasses to implement for saving state\n\t}\n\n\tprivate updateViewWelcome(): void {\n\t\tthis.viewWelcomeDisposable.dispose();\n\n\t\tif (!this.shouldShowWelcome()) {\n\t\t\tthis.bodyContainer.classList.remove('welcome');\n\t\t\tthis.viewWelcomeContainer.innerText = '';\n\t\t\tthis.scrollableElement.scanDomNode();\n\t\t\treturn;\n\t\t}\n\n\t\tconst contents = this.viewWelcomeController.contents;\n\n\t\tif (contents.length === 0) {\n\t\t\tthis.bodyContainer.classList.remove('welcome');\n\t\t\tthis.viewWelcomeContainer.innerText = '';\n\t\t\tthis.scrollableElement.scanDomNode();\n\t\t\treturn;\n\t\t}\n\n\t\tconst disposables = new DisposableStore();\n\t\tthis.bodyContainer.classList.add('welcome');\n\t\tthis.viewWelcomeContainer.innerText = '';\n\n\t\tfor (const { content, precondition } of contents) {\n\t\t\tconst lines = content.split('\\n');\n\n\t\t\tfor (let line of lines) {\n\t\t\t\tline = line.trim();\n\n\t\t\t\tif (!line) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst linkedText = parseLinkedText(line);\n\n\t\t\t\tif (linkedText.nodes.length === 1 && typeof linkedText.nodes[0] !== 'string') {\n\t\t\t\t\tconst node = linkedText.nodes[0];\n\t\t\t\t\tconst buttonContainer = append(this.viewWelcomeContainer, $('.button-container'));\n\t\t\t\t\tconst button = new Button(buttonContainer, { title: node.title, supportIcons: true, ...defaultButtonStyles });\n\t\t\t\t\tbutton.label = node.label;\n\t\t\t\t\tbutton.onDidClick(_ => {\n\t\t\t\t\t\tthis.telemetryService.publicLog2<{ viewId: string; uri: string }, WelcomeActionClassification>('views.welcomeAction', { viewId: this.id, uri: node.href });\n\t\t\t\t\t\tthis.openerService.open(node.href, { allowCommands: true });\n\t\t\t\t\t}, null, disposables);\n\t\t\t\t\tdisposables.add(button);\n\n\t\t\t\t\tif (precondition) {\n\t\t\t\t\t\tconst updateEnablement = () => button.enabled = this.contextKeyService.contextMatchesRules(precondition);\n\t\t\t\t\t\tupdateEnablement();\n\n\t\t\t\t\t\tconst keys = new Set();\n\t\t\t\t\t\tprecondition.keys().forEach(key => keys.add(key));\n\t\t\t\t\t\tconst onDidChangeContext = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(keys));\n\t\t\t\t\t\tonDidChangeContext(updateEnablement, null, disposables);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tconst p = append(this.viewWelcomeContainer, $('p'));\n\n\t\t\t\t\tfor (const node of linkedText.nodes) {\n\t\t\t\t\t\tif (typeof node === 'string') {\n\t\t\t\t\t\t\tappend(p, document.createTextNode(node));\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tconst link = disposables.add(this.instantiationService.createInstance(Link, p, node, {}));\n\n\t\t\t\t\t\t\tif (precondition && node.href.startsWith('command:')) {\n\t\t\t\t\t\t\t\tconst updateEnablement = () => link.enabled = this.contextKeyService.contextMatchesRules(precondition);\n\t\t\t\t\t\t\t\tupdateEnablement();\n\n\t\t\t\t\t\t\t\tconst keys = new Set();\n\t\t\t\t\t\t\t\tprecondition.keys().forEach(key => keys.add(key));\n\t\t\t\t\t\t\t\tconst onDidChangeContext = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(keys));\n\t\t\t\t\t\t\t\tonDidChangeContext(updateEnablement, null, disposables);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.scrollableElement.scanDomNode();\n\t\tthis.viewWelcomeDisposable = disposables;\n\t}\n\n\tshouldShowWelcome(): boolean {\n\t\treturn false;\n\t}\n\n\tgetFilterWidget()", + "fileName": "./1.tst" + }, + "modified": { + "content": "\n\tlayout(height: number, width: number) {\n\t\tif (!this.enabled) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.element!.style.height = `${height}px`;\n\t\tthis.element!.style.width = `${width}px`;\n\t\tthis.element!.classList.toggle('wide', width > 640);\n\t\tthis.scrollableElement!.scanDomNode();\n\t}\n\n\tfocus() {\n\t\tif (!this.enabled) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.element!.focus();\n\t}\n\n\tprivate onDidChangeViewWelcomeState(): void {\n\t\tconst enabled = this.delegate.shouldShowWelcome();\n\n\t\tif (this.enabled === enabled) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.enabled = enabled;\n\n\t\tif (!enabled) {\n\t\t\tthis.enabledDisposables.clear();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.container.classList.add('welcome');\n\t\tconst viewWelcomeContainer = append(this.container, $('.welcome-view'));\n\t\tthis.element = $('.welcome-view-content', { tabIndex: 0 });\n\t\tthis.scrollableElement = new DomScrollableElement(this.element, { alwaysConsumeMouseWheel: true, horizontal: ScrollbarVisibility.Hidden, vertical: ScrollbarVisibility.Visible, });\n\t\tappend(viewWelcomeContainer, this.scrollableElement.getDomNode());\n\n\t\tthis.enabledDisposables.add(toDisposable(() => {\n\t\t\tthis.container.classList.remove('welcome');\n\t\t\tthis.scrollableElement!.dispose();\n\t\t\tviewWelcomeContainer.remove();\n\t\t\tthis.scrollableElement = undefined;\n\t\t\tthis.element = undefined;\n\t\t}));\n\n\t\tthis.contextKeyService.onDidChangeContext(this.onDidChangeContext, this, this.enabledDisposables);\n\t\tEvent.chain(viewsRegistry.onDidChangeViewWelcomeContent, $ => $.filter(id => id === this.delegate.id))\n\t\t\t(this.onDidChangeViewWelcomeContent, this, this.enabledDisposables);\n\t\tthis.onDidChangeViewWelcomeContent();\n\t}\n\n\tprivate onDidChangeViewWelcomeContent(): void {\n\t\tconst descriptors = viewsRegistry.getViewWelcomeContent(this.delegate.id);\n\n\t\tthis.items = [];\n\n\t\tfor (const descriptor of descriptors) {\n\t\t\tif (descriptor.when === 'default') {\n\t\t\t\tthis.defaultItem = { descriptor, visible: true };\n\t\t\t} else {\n\t\t\t\tconst visible = descriptor.when ? this.contextKeyService.contextMatchesRules(descriptor.when) : true;\n\t\t\t\tthis.items.push({ descriptor, visible });\n\t\t\t}\n\t\t}\n\n\t\tthis.render();\n\t}\n\n\tprivate onDidChangeContext(): void {\n\t\tlet didChange = false;\n\n\t\tfor (const item of this.items) {\n\t\t\tif (!item.descriptor.when || item.descriptor.when === 'default') {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst visible = this.contextKeyService.contextMatchesRules(item.descriptor.when);\n\n\t\t\tif (item.visible === visible) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\titem.visible = visible;\n\t\t\tdidChange = true;\n\t\t}\n\n\t\tif (didChange) {\n\t\t\tthis.render();\n\t\t}\n\t}\n\n\tprivate render(): void {\n\t\tthis.renderDisposables.clear();\n\n\t\tconst contents = this.getContentDescriptors();\n\n\t\tif (contents.length === 0) {\n\t\t\tthis.container.classList.remove('welcome');\n\t\t\tthis.element!.innerText = '';\n\t\t\tthis.scrollableElement!.scanDomNode();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.container.classList.add('welcome');\n\t\tthis.element!.innerText = '';\n\n\t\tfor (const { content, precondition } of contents) {\n\t\t\tconst lines = content.split('\\n');\n\n\t\t\tfor (let line of lines) {\n\t\t\t\tline = line.trim();\n\n\t\t\t\tif (!line) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst linkedText = parseLinkedText(line);\n\n\t\t\t\tif (linkedText.nodes.length === 1 && typeof linkedText.nodes[0] !== 'string') {\n\t\t\t\t\tconst node = linkedText.nodes[0];\n\t\t\t\t\tconst buttonContainer = append(this.element!, $('.button-container'));\n\t\t\t\t\tconst button = new Button(buttonContainer, { title: node.title, supportIcons: true, ...defaultButtonStyles });\n\t\t\t\t\tbutton.label = node.label;\n\t\t\t\t\tbutton.onDidClick(_ => {\n\t\t\t\t\t\tthis.telemetryService.publicLog2<{ viewId: string; uri: string }, WelcomeActionClassification>('views.welcomeAction', { viewId: this.delegate.id, uri: node.href });\n\t\t\t\t\t\tthis.openerService.open(node.href, { allowCommands: true });\n\t\t\t\t\t}, null, this.renderDisposables);\n\t\t\t\t\tthis.renderDisposables.add(button);\n\n\t\t\t\t\tif (precondition) {\n\t\t\t\t\t\tconst updateEnablement = () => button.enabled = this.contextKeyService.contextMatchesRules(precondition);\n\t\t\t\t\t\tupdateEnablement();\n\n\t\t\t\t\t\tconst keys = new Set(precondition.keys());\n\t\t\t\t\t\tconst onDidChangeContext = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(keys));\n\t\t\t\t\t\tonDidChangeContext(updateEnablement, null, this.renderDisposables);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tconst p = append(this.element!, $('p'));\n\n\t\t\t\t\tfor (const node of linkedText.nodes) {\n\t\t\t\t\t\tif (typeof node === 'string') {\n\t\t\t\t\t\t\tappend(p, document.createTextNode(node));\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tconst link = this.renderDisposables.add(this.instantiationService.createInstance(Link, p, node, {}));\n\n\t\t\t\t\t\t\tif (precondition && node.href.startsWith('command:')) {\n\t\t\t\t\t\t\t\tconst updateEnablement = () => link.enabled = this.contextKeyService.contextMatchesRules(precondition);\n\t\t\t\t\t\t\t\tupdateEnablement();\n\n\t\t\t\t\t\t\t\tconst keys = new Set(precondition.keys());\n\t\t\t\t\t\t\t\tconst onDidChangeContext = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(keys));\n\t\t\t\t\t\t\t\tonDidChangeContext(updateEnablement, null, this.renderDisposables);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.scrollableElement!.scanDomNode();\n\t}\n\n\tprivate getContentDescriptors(): IViewContentDescriptor[] {\n\t\tconst visibleItems = this.items.filter(v => v.visible);\n\n\t\tif (visibleItems.length === 0 && this.defaultItem) {\n\t\t\treturn [this.defaultItem.descriptor];\n\t\t}\n\n\t\treturn visibleItems.map(v => v.descriptor);\n\t}\n\n\tdispose(): void {\n\t\tthis.disposables.dispose();\n\t}\n\t}\n\n\texport abstract class ViewPane extends Pane implements IView {\n\n\tprivate static readonly AlwaysShowActionsConfig = 'workbench.view.alwaysShowHeaderActions';\n\n\tprivate _onDidFocus = this._register(new Emitter());\n\treadonly onDidFocus: Event = this._onDidFocus.event;\n\n\tprivate _onDidBlur = this._register(new Emitter());\n\treadonly onDidBlur: Event = this._onDidBlur.event;\n\n\tprivate _onDidChangeBodyVisibility = this._register(new Emitter());\n\treadonly onDidChangeBodyVisibility: Event = this._onDidChangeBodyVisibility.event;\n\n\tprotected _onDidChangeTitleArea = this._register(new Emitter());\n\treadonly onDidChangeTitleArea: Event = this._onDidChangeTitleArea.event;\n\n\tprotected _onDidChangeViewWelcomeState = this._register(new Emitter());\n\treadonly onDidChangeViewWelcomeState: Event = this._onDidChangeViewWelcomeState.event;\n\n\tprivate _isVisible: boolean = false;\n\treadonly id: string;\n\n\tprivate _title: string;\n\tpublic get title(): string {\n\t\treturn this._title;\n\t}\n\n\tprivate _titleDescription: string | undefined;\n\tpublic get titleDescription(): string | undefined {\n\t\treturn this._titleDescription;\n\t}\n\n\treadonly menuActions: CompositeMenuActions;\n\n\tprivate progressBar!: ProgressBar;\n\tprivate progressIndicator!: IProgressIndicator;\n\n\tprivate toolbar?: WorkbenchToolBar;\n\tprivate readonly showActions: ViewPaneShowActions;\n\tprivate headerContainer?: HTMLElement;\n\tprivate titleContainer?: HTMLElement;\n\tprivate titleDescriptionContainer?: HTMLElement;\n\tprivate iconContainer?: HTMLElement;\n\tprotected twistiesContainer?: HTMLElement;\n\tprivate viewWelcomeController!: ViewWelcomeController;\n\n\tprotected readonly scopedContextKeyService: IContextKeyService;\n\n\tconstructor(\n\t\toptions: IViewPaneOptions,\n\t\t@IKeybindingService protected keybindingService: IKeybindingService,\n\t\t@IContextMenuService protected contextMenuService: IContextMenuService,\n\t\t@IConfigurationService protected readonly configurationService: IConfigurationService,\n\t\t@IContextKeyService protected contextKeyService: IContextKeyService,\n\t\t@IViewDescriptorService protected viewDescriptorService: IViewDescriptorService,\n\t\t@IInstantiationService protected instantiationService: IInstantiationService,\n\t\t@IOpenerService protected openerService: IOpenerService,\n\t\t@IThemeService protected themeService: IThemeService,\n\t\t@ITelemetryService protected telemetryService: ITelemetryService,\n\t) {\n\t\tsuper({ ...options, ...{ orientation: viewDescriptorService.getViewLocationById(options.id) === ViewContainerLocation.Panel ? Orientation.HORIZONTAL : Orientation.VERTICAL } });\n\n\t\tthis.id = options.id;\n\t\tthis._title = options.title;\n\t\tthis._titleDescription = options.titleDescription;\n\t\tthis.showActions = options.showActions ?? ViewPaneShowActions.Default;\n\n\t\tthis.scopedContextKeyService = this._register(contextKeyService.createScoped(this.element));\n\t\tthis.scopedContextKeyService.createKey('view', this.id);\n\t\tconst viewLocationKey = this.scopedContextKeyService.createKey('viewLocation', ViewContainerLocationToString(viewDescriptorService.getViewLocationById(this.id)!));\n\t\tthis._register(Event.filter(viewDescriptorService.onDidChangeLocation, e => e.views.some(view => view.id === this.id))(() => viewLocationKey.set(ViewContainerLocationToString(viewDescriptorService.getViewLocationById(this.id)!))));\n\n\t\tthis.menuActions = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService])).createInstance(CompositeMenuActions, options.titleMenuId ?? MenuId.ViewTitle, MenuId.ViewTitleContext, { shouldForwardArgs: !options.donotForwardArgs }));\n\t\tthis._register(this.menuActions.onDidChange(() => this.updateActions()));\n\t}\n\n\toverride get headerVisible(): boolean {\n\t\treturn super.headerVisible;\n\t}\n\n\toverride set headerVisible(visible: boolean) {\n\t\tsuper.headerVisible = visible;\n\t\tthis.element.classList.toggle('merged-header', !visible);\n\t}\n\n\tsetVisible(visible: boolean): void {\n\t\tif (this._isVisible !== visible) {\n\t\t\tthis._isVisible = visible;\n\n\t\t\tif (this.isExpanded()) {\n\t\t\t\tthis._onDidChangeBodyVisibility.fire(visible);\n\t\t\t}\n\t\t}\n\t}\n\n\tisVisible(): boolean {\n\t\treturn this._isVisible;\n\t}\n\n\tisBodyVisible(): boolean {\n\t\treturn this._isVisible && this.isExpanded();\n\t}\n\n\toverride setExpanded(expanded: boolean): boolean {\n\t\tconst changed = super.setExpanded(expanded);\n\t\tif (changed) {\n\t\t\tthis._onDidChangeBodyVisibility.fire(expanded);\n\t\t}\n\t\tif (this.twistiesContainer) {\n\t\t\tthis.twistiesContainer.classList.remove(...ThemeIcon.asClassNameArray(this.getTwistyIcon(!expanded)));\n\t\t\tthis.twistiesContainer.classList.add(...ThemeIcon.asClassNameArray(this.getTwistyIcon(expanded)));\n\t\t}\n\t\treturn changed;\n\t}\n\n\toverride render(): void {\n\t\tsuper.render();\n\n\t\tconst focusTracker = trackFocus(this.element);\n\t\tthis._register(focusTracker);\n\t\tthis._register(focusTracker.onDidFocus(() => this._onDidFocus.fire()));\n\t\tthis._register(focusTracker.onDidBlur(() => this._onDidBlur.fire()));\n\t}\n\n\tprotected renderHeader(container: HTMLElement): void {\n\t\tthis.headerContainer = container;\n\n\t\tthis.twistiesContainer = append(container, $(ThemeIcon.asCSSSelector(this.getTwistyIcon(this.isExpanded()))));\n\n\t\tthis.renderHeaderTitle(container, this.title);\n\n\t\tconst actions = append(container, $('.actions'));\n\t\tactions.classList.toggle('show-always', this.showActions === ViewPaneShowActions.Always);\n\t\tactions.classList.toggle('show-expanded', this.showActions === ViewPaneShowActions.WhenExpanded);\n\t\tthis.toolbar = this.instantiationService.createInstance(WorkbenchToolBar, actions, {\n\t\t\torientation: ActionsOrientation.HORIZONTAL,\n\t\t\tactionViewItemProvider: action => this.getActionViewItem(action),\n\t\t\tariaLabel: nls.localize('viewToolbarAriaLabel', \"{0} actions\", this.title),\n\t\t\tgetKeyBinding: action => this.keybindingService.lookupKeybinding(action.id),\n\t\t\trenderDropdownAsChildElement: true,\n\t\t\tactionRunner: this.getActionRunner(),\n\t\t\tresetMenu: this.menuActions.menuId\n\t\t});\n\n\t\tthis._register(this.toolbar);\n\t\tthis.setActions();\n\n\t\tthis._register(addDisposableListener(actions, EventType.CLICK, e => e.preventDefault()));\n\n\t\tconst viewContainerModel = this.viewDescriptorService.getViewContainerByViewId(this.id);\n\t\tif (viewContainerModel) {\n\t\t\tthis._register(this.viewDescriptorService.getViewContainerModel(viewContainerModel).onDidChangeContainerInfo(({ title }) => this.updateTitle(this.title)));\n\t\t} else {\n\t\t\tconsole.error(`View container model not found for view ${this.id}`);\n\t\t}\n\n\t\tconst onDidRelevantConfigurationChange = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(ViewPane.AlwaysShowActionsConfig));\n\t\tthis._register(onDidRelevantConfigurationChange(this.updateActionsVisibility, this));\n\t\tthis.updateActionsVisibility();\n\t}\n\n\tprotected getTwistyIcon(expanded: boolean): ThemeIcon {\n\t\treturn expanded ? viewPaneContainerExpandedIcon : viewPaneContainerCollapsedIcon;\n\t}\n\n\toverride style(styles: IPaneStyles): void {\n\t\tsuper.style(styles);\n\n\t\tconst icon = this.getIcon();\n\t\tif (this.iconContainer) {\n\t\t\tconst fgColor = asCssValueWithDefault(styles.headerForeground, asCssVariable(foreground));\n\t\t\tif (URI.isUri(icon)) {\n\t\t\t\t// Apply background color to activity bar item provided with iconUrls\n\t\t\t\tthis.iconContainer.style.backgroundColor = fgColor;\n\t\t\t\tthis.iconContainer.style.color = '';\n\t\t\t} else {\n\t\t\t\t// Apply foreground color to activity bar items provided with codicons\n\t\t\t\tthis.iconContainer.style.color = fgColor;\n\t\t\t\tthis.iconContainer.style.backgroundColor = '';\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate getIcon(): ThemeIcon | URI {\n\t\treturn this.viewDescriptorService.getViewDescriptorById(this.id)?.containerIcon || defaultViewIcon;\n\t}\n\n\tprotected renderHeaderTitle(container: HTMLElement, title: string): void {\n\t\tthis.iconContainer = append(container, $('.icon', undefined));\n\t\tconst icon = this.getIcon();\n\n\t\tlet cssClass: string | undefined = undefined;\n\t\tif (URI.isUri(icon)) {\n\t\t\tcssClass = `view-${this.id.replace(/[\\.\\:]/g, '-')}`;\n\t\t\tconst iconClass = `.pane-header .icon.${cssClass}`;\n\n\t\t\tcreateCSSRule(iconClass, `\n\t\t\t\tmask: ${asCSSUrl(icon)} no-repeat 50% 50%;\n\t\t\t\tmask-size: 24px;\n\t\t\t\t-webkit-mask: ${asCSSUrl(icon)} no-repeat 50% 50%;\n\t\t\t\t-webkit-mask-size: 16px;\n\t\t\t`);\n\t\t} else if (ThemeIcon.isThemeIcon(icon)) {\n\t\t\tcssClass = ThemeIcon.asClassName(icon);\n\t\t}\n\n\t\tif (cssClass) {\n\t\t\tthis.iconContainer.classList.add(...cssClass.split(' '));\n\t\t}\n\n\t\tconst calculatedTitle = this.calculateTitle(title);\n\t\tthis.titleContainer = append(container, $('h3.title', { title: calculatedTitle }, calculatedTitle));\n\n\t\tif (this._titleDescription) {\n\t\t\tthis.setTitleDescription(this._titleDescription);\n\t\t}\n\n\t\tthis.iconContainer.title = calculatedTitle;\n\t\tthis.iconContainer.setAttribute('aria-label', calculatedTitle);\n\t}\n\n\tprotected updateTitle(title: string): void {\n\t\tconst calculatedTitle = this.calculateTitle(title);\n\t\tif (this.titleContainer) {\n\t\t\tthis.titleContainer.textContent = calculatedTitle;\n\t\t\tthis.titleContainer.setAttribute('title', calculatedTitle);\n\t\t}\n\n\t\tif (this.iconContainer) {\n\t\t\tthis.iconContainer.title = calculatedTitle;\n\t\t\tthis.iconContainer.setAttribute('aria-label', calculatedTitle);\n\t\t}\n\n\t\tthis._title = title;\n\t\tthis._onDidChangeTitleArea.fire();\n\t}\n\n\tprivate setTitleDescription(description: string | undefined) {\n\t\tif (this.titleDescriptionContainer) {\n\t\t\tthis.titleDescriptionContainer.textContent = description ?? '';\n\t\t\tthis.titleDescriptionContainer.setAttribute('title', description ?? '');\n\t\t}\n\t\telse if (description && this.titleContainer) {\n\t\t\tthis.titleDescriptionContainer = after(this.titleContainer, $('span.description', { title: description }, description));\n\t\t}\n\t}\n\n\tprotected updateTitleDescription(description?: string | undefined): void {\n\t\tthis.setTitleDescription(description);\n\n\t\tthis._titleDescription = description;\n\t\tthis._onDidChangeTitleArea.fire();\n\t}\n\n\tprivate calculateTitle(title: string): string {\n\t\tconst viewContainer = this.viewDescriptorService.getViewContainerByViewId(this.id)!;\n\t\tconst model = this.viewDescriptorService.getViewContainerModel(viewContainer);\n\t\tconst viewDescriptor = this.viewDescriptorService.getViewDescriptorById(this.id);\n\t\tconst isDefault = this.viewDescriptorService.getDefaultContainerById(this.id) === viewContainer;\n\n\t\tif (!isDefault && viewDescriptor?.containerTitle && model.title !== viewDescriptor.containerTitle) {\n\t\t\treturn `${viewDescriptor.containerTitle}: ${title}`;\n\t\t}\n\n\t\treturn title;\n\t}\n\n\tprotected renderBody(container: HTMLElement): void {\n\t\tthis.viewWelcomeController = this._register(new ViewWelcomeController(container, this, this.instantiationService, this.openerService, this.telemetryService, this.contextKeyService));\n\t}\n\n\tprotected layoutBody(height: number, width: number): void {\n\t\tthis.viewWelcomeController.layout(height, width);\n\t}\n\n\tonDidScrollRoot() {\n\t\t// noop\n\t}\n\n\tgetProgressIndicator() {\n\t\tif (this.progressBar === undefined) {\n\t\t\t// Progress bar\n\t\t\tthis.progressBar = this._register(new ProgressBar(this.element, defaultProgressBarStyles));\n\t\t\tthis.progressBar.hide();\n\t\t}\n\n\t\tif (this.progressIndicator === undefined) {\n\t\t\tconst that = this;\n\t\t\tthis.progressIndicator = new ScopedProgressIndicator(assertIsDefined(this.progressBar), new class extends AbstractProgressScope {\n\t\t\t\tconstructor() {\n\t\t\t\t\tsuper(that.id, that.isBodyVisible());\n\t\t\t\t\tthis._register(that.onDidChangeBodyVisibility(isVisible => isVisible ? this.onScopeOpened(that.id) : this.onScopeClosed(that.id)));\n\t\t\t\t}\n\t\t\t}());\n\t\t}\n\t\treturn this.progressIndicator;\n\t}\n\n\tprotected getProgressLocation(): string {\n\t\treturn this.viewDescriptorService.getViewContainerByViewId(this.id)!.id;\n\t}\n\n\tprotected getBackgroundColor(): string {\n\t\tswitch (this.viewDescriptorService.getViewLocationById(this.id)) {\n\t\t\tcase ViewContainerLocation.Panel:\n\t\t\t\treturn PANEL_BACKGROUND;\n\t\t\tcase ViewContainerLocation.Sidebar:\n\t\t\tcase ViewContainerLocation.AuxiliaryBar:\n\t\t\t\treturn SIDE_BAR_BACKGROUND;\n\t\t}\n\n\t\treturn SIDE_BAR_BACKGROUND;\n\t}\n\n\tfocus(): void {\n\t\tif (this.shouldShowWelcome()) {\n\t\t\tthis.viewWelcomeController.focus();\n\t\t} else if (this.element) {\n\t\t\tthis.element.focus();\n\t\t\tthis._onDidFocus.fire();\n\t\t}\n\t}\n\n\tprivate setActions(): void {\n\t\tif (this.toolbar) {\n\t\t\tconst primaryActions = [...this.menuActions.getPrimaryActions()];\n\t\t\tif (this.shouldShowFilterInHeader()) {\n\t\t\t\tprimaryActions.unshift(VIEWPANE_FILTER_ACTION);\n\t\t\t}\n\t\t\tthis.toolbar.setActions(prepareActions(primaryActions), prepareActions(this.menuActions.getSecondaryActions()));\n\t\t\tthis.toolbar.context = this.getActionsContext();\n\t\t}\n\t}\n\n\tprivate updateActionsVisibility(): void {\n\t\tif (!this.headerContainer) {\n\t\t\treturn;\n\t\t}\n\t\tconst shouldAlwaysShowActions = this.configurationService.getValue('workbench.view.alwaysShowHeaderActions');\n\t\tthis.headerContainer.classList.toggle('actions-always-visible', shouldAlwaysShowActions);\n\t}\n\n\tprotected updateActions(): void {\n\t\tthis.setActions();\n\t\tthis._onDidChangeTitleArea.fire();\n\t}\n\n\tgetActionViewItem(action: IAction, options?: IDropdownMenuActionViewItemOptions): IActionViewItem | undefined {\n\t\tif (action.id === VIEWPANE_FILTER_ACTION.id) {\n\t\t\tconst that = this;\n\t\t\treturn new class extends BaseActionViewItem {\n\t\t\t\tconstructor() { super(null, action); }\n\t\t\t\toverride setFocusable(): void { /* noop input elements are focusable by default */ }\n\t\t\t\toverride get trapsArrowNavigation(): boolean { return true; }\n\t\t\t\toverride render(container: HTMLElement): void {\n\t\t\t\t\tcontainer.classList.add('viewpane-filter-container');\n\t\t\t\t\tappend(container, that.getFilterWidget()!.element);\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\t\treturn createActionViewItem(this.instantiationService, action, { ...options, ...{ menuAsChild: action instanceof SubmenuItemAction } });\n\t}\n\n\tgetActionsContext(): unknown {\n\t\treturn undefined;\n\t}\n\n\tgetActionRunner(): IActionRunner | undefined {\n\t\treturn undefined;\n\t}\n\n\tgetOptimalWidth(): number {\n\t\treturn 0;\n\t}\n\n\tsaveState(): void {\n\t\t// Subclasses to implement for saving state\n\t}\n\n\tshouldShowWelcome(): boolean {\n\t\treturn false;\n\t}\n\n\tgetFilterWidget()", + "fileName": "./2.tst" + }, + "diffs": [ + { + "originalRange": "[1,3)", + "modifiedRange": "[1,52)", + "innerChanges": null + }, + { + "originalRange": "[7,8)", + "modifiedRange": "[56,57)", + "innerChanges": [ + { + "originalRange": "[7,64 -> 7,64]", + "modifiedRange": "[56,64 -> 56,73]" + } + ] + }, + { + "originalRange": "[20,21)", + "modifiedRange": "[69,70)", + "innerChanges": [ + { + "originalRange": "[20,8 -> 20,25]", + "modifiedRange": "[69,8 -> 69,14]" + } + ] + }, + { + "originalRange": "[42,43)", + "modifiedRange": "[91,172)", + "innerChanges": null + }, + { + "originalRange": "[44,44)", + "modifiedRange": "[173,175)", + "innerChanges": null + }, + { + "originalRange": "[49,50)", + "modifiedRange": "[180,181)", + "innerChanges": [ + { + "originalRange": "[49,1 -> 49,1]", + "modifiedRange": "[180,1 -> 180,2]" + } + ] + }, + { + "originalRange": "[51,52)", + "modifiedRange": "[182,183)", + "innerChanges": [ + { + "originalRange": "[51,1 -> 51,1]", + "modifiedRange": "[182,1 -> 182,2]" + } + ] + }, + { + "originalRange": "[95,100)", + "modifiedRange": "[226,227)", + "innerChanges": [ + { + "originalRange": "[95,1 -> 99,1]", + "modifiedRange": "[226,1 -> 226,1]" + }, + { + "originalRange": "[99,31 -> 99,31]", + "modifiedRange": "[226,31 -> 226,32]" + } + ] + }, + { + "originalRange": "[129,131)", + "modifiedRange": "[256,256)", + "innerChanges": null + }, + { + "originalRange": "[324,326)", + "modifiedRange": "[449,449)", + "innerChanges": null + }, + { + "originalRange": "[327,342)", + "modifiedRange": "[450,451)", + "innerChanges": [ + { + "originalRange": "[327,8 -> 329,9]", + "modifiedRange": "[450,8 -> 450,8]" + }, + { + "originalRange": "[329,24 -> 330,26]", + "modifiedRange": "[450,23 -> 450,27]" + }, + { + "originalRange": "[330,31 -> 331,28]", + "modifiedRange": "[450,32 -> 450,32]" + }, + { + "originalRange": "[331,47 -> 331,74]", + "modifiedRange": "[450,51 -> 450,52]" + }, + { + "originalRange": "[331,88 -> 333,9]", + "modifiedRange": "[450,66 -> 450,74]" + }, + { + "originalRange": "[333,13 -> 337,30]", + "modifiedRange": "[450,78 -> 450,88]" + }, + { + "originalRange": "[337,37 -> 339,5]", + "modifiedRange": "[450,95 -> 450,96]" + }, + { + "originalRange": "[339,8 -> 339,78]", + "modifiedRange": "[450,99 -> 450,114]" + }, + { + "originalRange": "[339,87 -> 340,3]", + "modifiedRange": "[450,123 -> 450,137]" + }, + { + "originalRange": "[340,8 -> 340,59]", + "modifiedRange": "[450,142 -> 450,157]" + }, + { + "originalRange": "[340,66 -> 341,26]", + "modifiedRange": "[450,164 -> 450,183]" + } + ] + }, + { + "originalRange": "[345,351)", + "modifiedRange": "[454,455)", + "innerChanges": [ + { + "originalRange": "[345,1 -> 346,2]", + "modifiedRange": "[454,1 -> 454,1]" + }, + { + "originalRange": "[346,24 -> 347,27]", + "modifiedRange": "[454,23 -> 454,27]" + }, + { + "originalRange": "[347,30 -> 348,53]", + "modifiedRange": "[454,30 -> 454,43]" + }, + { + "originalRange": "[348,60 -> 350,4 EOL]", + "modifiedRange": "[454,50 -> 454,52 EOL]" + } + ] + }, + { + "originalRange": "[394,395)", + "modifiedRange": "[498,499)", + "innerChanges": [ + { + "originalRange": "[394,24 -> 394,27]", + "modifiedRange": "[498,24 -> 498,28]" + } + ] + }, + { + "originalRange": "[457,540)", + "modifiedRange": "[561,561)", + "innerChanges": null + } + ] +} \ No newline at end of file diff --git a/code/src/vs/editor/test/node/diffing/fixtures/random-match-2/advanced.expected.diff.json b/code/src/vs/editor/test/node/diffing/fixtures/random-match-2/advanced.expected.diff.json index 337c2637132..65ce4a98835 100644 --- a/code/src/vs/editor/test/node/diffing/fixtures/random-match-2/advanced.expected.diff.json +++ b/code/src/vs/editor/test/node/diffing/fixtures/random-match-2/advanced.expected.diff.json @@ -13,8 +13,8 @@ "modifiedRange": "[2,4)", "innerChanges": [ { - "originalRange": "[2,8 -> 4,17]", - "modifiedRange": "[2,8 -> 2,16]" + "originalRange": "[2,1 -> 4,2]", + "modifiedRange": "[2,1 -> 2,1]" }, { "originalRange": "[4,46 -> 4,53]", diff --git a/code/src/vs/editor/test/node/diffing/fixtures/shifting-twice/1.txt b/code/src/vs/editor/test/node/diffing/fixtures/shifting-twice/1.txt new file mode 100644 index 00000000000..8150851150c --- /dev/null +++ b/code/src/vs/editor/test/node/diffing/fixtures/shifting-twice/1.txt @@ -0,0 +1,18 @@ + for (extendToBottom = 0; extendToBottom < linesBelow; extendToBottom++) { + const origLine = move.original.endLineNumberExclusive + extendToBottom; + const modLine = move.modified.endLineNumberExclusive + extendToBottom; + if (origLine > originalLines.length || modLine > modifiedLines.length) { + break; + } + if (modifiedSet.contains(modLine) || originalSet.contains(origLine)) { + break; + } + if (!areLinesSimilar(originalLines[origLine - 1], modifiedLines[modLine - 1], timeout)) { + break; + } + } + + if (extendToBottom > 0) { + originalSet.addRange(new LineRange(move.original.endLineNumberExclusive, move.original.endLineNumberExclusive + extendToBottom)); + modifiedSet.addRange(new LineRange(move.modified.endLineNumberExclusive, move.modified.endLineNumberExclusive + extendToBottom)); + } \ No newline at end of file diff --git a/code/src/vs/editor/test/node/diffing/fixtures/shifting-twice/2.txt b/code/src/vs/editor/test/node/diffing/fixtures/shifting-twice/2.txt new file mode 100644 index 00000000000..d8220433dfc --- /dev/null +++ b/code/src/vs/editor/test/node/diffing/fixtures/shifting-twice/2.txt @@ -0,0 +1,21 @@ + for (extendToBottom = 0; extendToBottom < linesBelow; extendToBottom++) { + const origLine = move.original.endLineNumberExclusive + extendToBottom; + const modLine = move.modified.endLineNumberExclusive + extendToBottom; + if (origLine > originalLines.length || modLine > modifiedLines.length) { + break; + } + if (modifiedSet.contains(modLine) || originalSet.contains(origLine)) { + break; + } + if (!areLinesSimilar(originalLines[origLine - 1], modifiedLines[modLine - 1], timeout)) { + break; + } + if (originalLines[origLine - 1].trim().length !== 0) { + extendToBottomWithoutEmptyLines = extendToBottom + 1; + } + } + + if (extendToBottomWithoutEmptyLines > 0) { + originalSet.addRange(new LineRange(move.original.endLineNumberExclusive, move.original.endLineNumberExclusive + extendToBottomWithoutEmptyLines)); + modifiedSet.addRange(new LineRange(move.modified.endLineNumberExclusive, move.modified.endLineNumberExclusive + extendToBottomWithoutEmptyLines)); + } \ No newline at end of file diff --git a/code/src/vs/editor/test/node/diffing/fixtures/shifting-twice/advanced.expected.diff.json b/code/src/vs/editor/test/node/diffing/fixtures/shifting-twice/advanced.expected.diff.json new file mode 100644 index 00000000000..dbdf35c25c7 --- /dev/null +++ b/code/src/vs/editor/test/node/diffing/fixtures/shifting-twice/advanced.expected.diff.json @@ -0,0 +1,40 @@ +{ + "original": { + "content": "\t\tfor (extendToBottom = 0; extendToBottom < linesBelow; extendToBottom++) {\n\t\t\tconst origLine = move.original.endLineNumberExclusive + extendToBottom;\n\t\t\tconst modLine = move.modified.endLineNumberExclusive + extendToBottom;\n\t\t\tif (origLine > originalLines.length || modLine > modifiedLines.length) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (modifiedSet.contains(modLine) || originalSet.contains(origLine)) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (!areLinesSimilar(originalLines[origLine - 1], modifiedLines[modLine - 1], timeout)) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (extendToBottom > 0) {\n\t\t\toriginalSet.addRange(new LineRange(move.original.endLineNumberExclusive, move.original.endLineNumberExclusive + extendToBottom));\n\t\t\tmodifiedSet.addRange(new LineRange(move.modified.endLineNumberExclusive, move.modified.endLineNumberExclusive + extendToBottom));\n\t\t}", + "fileName": "./1.txt" + }, + "modified": { + "content": "\t\tfor (extendToBottom = 0; extendToBottom < linesBelow; extendToBottom++) {\n\t\t\tconst origLine = move.original.endLineNumberExclusive + extendToBottom;\n\t\t\tconst modLine = move.modified.endLineNumberExclusive + extendToBottom;\n\t\t\tif (origLine > originalLines.length || modLine > modifiedLines.length) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (modifiedSet.contains(modLine) || originalSet.contains(origLine)) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (!areLinesSimilar(originalLines[origLine - 1], modifiedLines[modLine - 1], timeout)) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (originalLines[origLine - 1].trim().length !== 0) {\n\t\t\t\textendToBottomWithoutEmptyLines = extendToBottom + 1;\n\t\t\t}\n\t\t}\n\n\t\tif (extendToBottomWithoutEmptyLines > 0) {\n\t\t\toriginalSet.addRange(new LineRange(move.original.endLineNumberExclusive, move.original.endLineNumberExclusive + extendToBottomWithoutEmptyLines));\n\t\t\tmodifiedSet.addRange(new LineRange(move.modified.endLineNumberExclusive, move.modified.endLineNumberExclusive + extendToBottomWithoutEmptyLines));\n\t\t}", + "fileName": "./2.txt" + }, + "diffs": [ + { + "originalRange": "[13,13)", + "modifiedRange": "[13,16)", + "innerChanges": [ + { + "originalRange": "[13,1 -> 13,1]", + "modifiedRange": "[13,1 -> 16,1]" + } + ] + }, + { + "originalRange": "[15,18)", + "modifiedRange": "[18,21)", + "innerChanges": [ + { + "originalRange": "[15,7 -> 15,21]", + "modifiedRange": "[18,7 -> 18,38]" + }, + { + "originalRange": "[16,116 -> 16,130]", + "modifiedRange": "[19,116 -> 19,147]" + }, + { + "originalRange": "[17,116 -> 17,130]", + "modifiedRange": "[20,116 -> 20,147]" + } + ] + } + ] +} \ No newline at end of file diff --git a/code/src/vs/editor/test/node/diffing/fixtures/shifting-twice/legacy.expected.diff.json b/code/src/vs/editor/test/node/diffing/fixtures/shifting-twice/legacy.expected.diff.json new file mode 100644 index 00000000000..17d96759241 --- /dev/null +++ b/code/src/vs/editor/test/node/diffing/fixtures/shifting-twice/legacy.expected.diff.json @@ -0,0 +1,35 @@ +{ + "original": { + "content": "\t\tfor (extendToBottom = 0; extendToBottom < linesBelow; extendToBottom++) {\n\t\t\tconst origLine = move.original.endLineNumberExclusive + extendToBottom;\n\t\t\tconst modLine = move.modified.endLineNumberExclusive + extendToBottom;\n\t\t\tif (origLine > originalLines.length || modLine > modifiedLines.length) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (modifiedSet.contains(modLine) || originalSet.contains(origLine)) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (!areLinesSimilar(originalLines[origLine - 1], modifiedLines[modLine - 1], timeout)) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (extendToBottom > 0) {\n\t\t\toriginalSet.addRange(new LineRange(move.original.endLineNumberExclusive, move.original.endLineNumberExclusive + extendToBottom));\n\t\t\tmodifiedSet.addRange(new LineRange(move.modified.endLineNumberExclusive, move.modified.endLineNumberExclusive + extendToBottom));\n\t\t}", + "fileName": "./1.txt" + }, + "modified": { + "content": "\t\tfor (extendToBottom = 0; extendToBottom < linesBelow; extendToBottom++) {\n\t\t\tconst origLine = move.original.endLineNumberExclusive + extendToBottom;\n\t\t\tconst modLine = move.modified.endLineNumberExclusive + extendToBottom;\n\t\t\tif (origLine > originalLines.length || modLine > modifiedLines.length) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (modifiedSet.contains(modLine) || originalSet.contains(origLine)) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (!areLinesSimilar(originalLines[origLine - 1], modifiedLines[modLine - 1], timeout)) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (originalLines[origLine - 1].trim().length !== 0) {\n\t\t\t\textendToBottomWithoutEmptyLines = extendToBottom + 1;\n\t\t\t}\n\t\t}\n\n\t\tif (extendToBottomWithoutEmptyLines > 0) {\n\t\t\toriginalSet.addRange(new LineRange(move.original.endLineNumberExclusive, move.original.endLineNumberExclusive + extendToBottomWithoutEmptyLines));\n\t\t\tmodifiedSet.addRange(new LineRange(move.modified.endLineNumberExclusive, move.modified.endLineNumberExclusive + extendToBottomWithoutEmptyLines));\n\t\t}", + "fileName": "./2.txt" + }, + "diffs": [ + { + "originalRange": "[13,13)", + "modifiedRange": "[13,16)", + "innerChanges": null + }, + { + "originalRange": "[15,18)", + "modifiedRange": "[18,21)", + "innerChanges": [ + { + "originalRange": "[15,21 -> 15,21]", + "modifiedRange": "[18,21 -> 18,38]" + }, + { + "originalRange": "[16,130 -> 16,130]", + "modifiedRange": "[19,130 -> 19,147]" + }, + { + "originalRange": "[17,130 -> 17,130]", + "modifiedRange": "[20,130 -> 20,147]" + } + ] + } + ] +} \ No newline at end of file diff --git a/code/src/vs/editor/test/node/diffing/fixtures/ts-class/advanced.expected.diff.json b/code/src/vs/editor/test/node/diffing/fixtures/ts-class/advanced.expected.diff.json index 7e9d27ae2e0..313e2af62f8 100644 --- a/code/src/vs/editor/test/node/diffing/fixtures/ts-class/advanced.expected.diff.json +++ b/code/src/vs/editor/test/node/diffing/fixtures/ts-class/advanced.expected.diff.json @@ -61,8 +61,8 @@ "modifiedRange": "[14,18 -> 14,24]" }, { - "originalRange": "[23,50 -> 23,50]", - "modifiedRange": "[14,41 -> 14,42]" + "originalRange": "[23,51 -> 23,51]", + "modifiedRange": "[14,42 -> 14,43]" } ] }, diff --git a/code/src/vs/editor/test/node/diffing/fixtures/ts-shift-to-ws/advanced.expected.diff.json b/code/src/vs/editor/test/node/diffing/fixtures/ts-shift-to-ws/advanced.expected.diff.json index 3f0012bc068..ded884fcd1d 100644 --- a/code/src/vs/editor/test/node/diffing/fixtures/ts-shift-to-ws/advanced.expected.diff.json +++ b/code/src/vs/editor/test/node/diffing/fixtures/ts-shift-to-ws/advanced.expected.diff.json @@ -13,8 +13,8 @@ "modifiedRange": "[9,10)", "innerChanges": [ { - "originalRange": "[9,115 -> 9,115]", - "modifiedRange": "[9,115 -> 9,136]" + "originalRange": "[9,119 -> 9,119]", + "modifiedRange": "[9,119 -> 9,140]" } ] } diff --git a/code/src/vs/monaco.d.ts b/code/src/vs/monaco.d.ts index e322762b774..1a29648bee0 100644 --- a/code/src/vs/monaco.d.ts +++ b/code/src/vs/monaco.d.ts @@ -7628,6 +7628,7 @@ declare namespace monaco.languages { range: IRange; uri: Uri; owner: string; + isReply: boolean; } export interface CodeLens { diff --git a/code/src/vs/platform/accessibility/browser/accessibilityService.ts b/code/src/vs/platform/accessibility/browser/accessibilityService.ts index a183dd490c2..5b621b5da63 100644 --- a/code/src/vs/platform/accessibility/browser/accessibilityService.ts +++ b/code/src/vs/platform/accessibility/browser/accessibilityService.ts @@ -27,7 +27,7 @@ export class AccessibilityService extends Disposable implements IAccessibilitySe constructor( @IContextKeyService private readonly _contextKeyService: IContextKeyService, @ILayoutService private readonly _layoutService: ILayoutService, - @IConfigurationService protected readonly _configurationService: IConfigurationService, + @IConfigurationService protected readonly _configurationService: IConfigurationService ) { super(); this._accessibilityModeEnabledContext = CONTEXT_ACCESSIBILITY_MODE_ENABLED.bindTo(this._contextKeyService); diff --git a/code/src/vs/platform/accessibility/browser/accessibleNotificationService.ts b/code/src/vs/platform/accessibility/browser/accessibleNotificationService.ts new file mode 100644 index 00000000000..e7bbe5c406b --- /dev/null +++ b/code/src/vs/platform/accessibility/browser/accessibleNotificationService.ts @@ -0,0 +1,59 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { localize } from 'vs/nls'; +import { AccessibleNotificationEvent, IAccessibilityService, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; +import { AudioCue, IAudioCueService, Sound } from 'vs/platform/audioCues/browser/audioCueService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; + +export class AccessibleNotificationService extends Disposable implements IAccessibleNotificationService { + declare readonly _serviceBrand: undefined; + private _events: Map = new Map(); + constructor( + @IAudioCueService private readonly _audioCueService: IAudioCueService, + @IConfigurationService private readonly _configurationService: IConfigurationService, + @IAccessibilityService private readonly _accessibilityService: IAccessibilityService) { + super(); + this._events.set(AccessibleNotificationEvent.Clear, { audioCue: AudioCue.clear, alertMessage: localize('cleared', "Cleared") }); + this._events.set(AccessibleNotificationEvent.Save, { audioCue: AudioCue.save, alertMessage: localize('saved', "Saved") }); + } + + notify(event: AccessibleNotificationEvent): void { + const { audioCue, alertMessage } = this._events.get(event)!; + const audioCueValue = this._configurationService.getValue(audioCue.settingsKey); + if (audioCueValue === 'on' || audioCueValue === 'auto' && this._accessibilityService.isScreenReaderOptimized()) { + this._audioCueService.playAudioCue(audioCue); + } else { + this._accessibilityService.alert(alertMessage); + } + } + + notifySaved(userGesture: boolean): void { + const { audioCue, alertMessage } = this._events.get(AccessibleNotificationEvent.Save)!; + const alertSetting: NotificationSetting = this._configurationService.getValue('accessibility.alert.save'); + if (this._shouldNotify(alertSetting, userGesture)) { + this._accessibilityService.alert(alertMessage); + } + const audioCueSetting: NotificationSetting = this._configurationService.getValue(audioCue.settingsKey); + if (this._shouldNotify(audioCueSetting, userGesture)) { + // Play sound bypasses the usual audio cue checks IE screen reader optimized, auto, etc. + this._audioCueService.playSound(Sound.save, true); + } + } + + private _shouldNotify(settingValue: NotificationSetting, userGesture: boolean): boolean { + return settingValue === 'always' || settingValue === 'userGesture' && userGesture; + } +} +type NotificationSetting = 'never' | 'always' | 'userGesture'; + +export class TestAccessibleNotificationService extends Disposable implements IAccessibleNotificationService { + + declare readonly _serviceBrand: undefined; + + notify(event: AccessibleNotificationEvent): void { } + notifySaved(userGesture: boolean): void { } +} diff --git a/code/src/vs/platform/accessibility/common/accessibility.ts b/code/src/vs/platform/accessibility/common/accessibility.ts index 84d2f65d24c..48b19a7c079 100644 --- a/code/src/vs/platform/accessibility/common/accessibility.ts +++ b/code/src/vs/platform/accessibility/common/accessibility.ts @@ -47,3 +47,21 @@ export function isAccessibilityInformation(obj: any): obj is IAccessibilityInfor && typeof obj.label === 'string' && (typeof obj.role === 'undefined' || typeof obj.role === 'string'); } + +export const IAccessibleNotificationService = createDecorator('accessibleNotificationService'); +/** + * Manages whether an audio cue or an aria alert will be used + * in response to actions taken around the workbench. + * Targets screen reader and braille users. + */ +export interface IAccessibleNotificationService { + readonly _serviceBrand: undefined; + notify(event: AccessibleNotificationEvent): void; + notifySaved(userGesture: boolean): void; +} + +export const enum AccessibleNotificationEvent { + Clear = 'clear', + Save = 'save', + Format = 'format' +} diff --git a/code/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/code/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index 077393ebb51..e0081e27068 100644 --- a/code/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/code/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $, addDisposableListener, append, asCSSUrl, EventType, ModifierKeyEmitter, prepend, reset } from 'vs/base/browser/dom'; +import { $, addDisposableListener, append, asCSSUrl, EventType, ModifierKeyEmitter, prepend } from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionViewItem, BaseActionViewItem, SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { DropdownMenuActionViewItem, IDropdownMenuActionViewItemOptions } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; @@ -263,25 +263,17 @@ export class MenuEntryActionViewItem extends ActionViewItem { }); } else { - // icon path/url - add special element with SVG-mask and icon color background - const svgUrl = isDark(this._themeService.getColorTheme().type) - ? asCSSUrl(icon.dark) - : asCSSUrl(icon.light); - - const svgIcon = $('span'); - svgIcon.style.webkitMask = svgIcon.style.mask = `${svgUrl} no-repeat 50% 50%`; - svgIcon.style.background = 'var(--vscode-icon-foreground)'; - svgIcon.style.display = 'inline-block'; - svgIcon.style.width = '100%'; - svgIcon.style.height = '100%'; - - label.appendChild(svgIcon); + // icon path/url + label.style.backgroundImage = ( + isDark(this._themeService.getColorTheme().type) + ? asCSSUrl(icon.dark) + : asCSSUrl(icon.light) + ); label.classList.add('icon'); - this._itemClassDispose.value = combinedDisposable( toDisposable(() => { + label.style.backgroundImage = ''; label.classList.remove('icon'); - reset(label); }), this._themeService.onDidColorThemeChange(() => { // refresh when the theme changes in case we go between dark <-> light diff --git a/code/src/vs/platform/actions/browser/toolbar.ts b/code/src/vs/platform/actions/browser/toolbar.ts index 33c79ba76a3..69c6905635b 100644 --- a/code/src/vs/platform/actions/browser/toolbar.ts +++ b/code/src/vs/platform/actions/browser/toolbar.ts @@ -328,6 +328,7 @@ export class MenuWorkbenchToolBar extends WorkbenchToolBar { { primary, secondary }, options?.toolbarOptions?.primaryGroup, options?.toolbarOptions?.shouldInlineSubmenu, options?.toolbarOptions?.useSeparatorsInPrimaryActions ); + container.classList.toggle('has-no-actions', primary.length === 0 && secondary.length === 0); super.setActions(primary, secondary); }; diff --git a/code/src/vs/platform/actions/common/actions.ts b/code/src/vs/platform/actions/common/actions.ts index 59331ea5559..165a2ed3636 100644 --- a/code/src/vs/platform/actions/common/actions.ts +++ b/code/src/vs/platform/actions/common/actions.ts @@ -73,6 +73,7 @@ export class MenuId { static readonly ExplorerContextShare = new MenuId('ExplorerContextShare'); static readonly ExtensionContext = new MenuId('ExtensionContext'); static readonly GlobalActivity = new MenuId('GlobalActivity'); + static readonly TitleBarGlobalControlMenu = new MenuId('TitleBarGlobalControlMenu'); static readonly CommandCenter = new MenuId('CommandCenter'); static readonly CommandCenterCenter = new MenuId('CommandCenterCenter'); static readonly LayoutControlMenuSubmenu = new MenuId('LayoutControlMenuSubmenu'); @@ -89,6 +90,7 @@ export class MenuId { static readonly MenubarNewBreakpointMenu = new MenuId('MenubarNewBreakpointMenu'); static readonly PanelAlignmentMenu = new MenuId('PanelAlignmentMenu'); static readonly PanelPositionMenu = new MenuId('PanelPositionMenu'); + static readonly ActivityBarPositionMenu = new MenuId('ActivityBarPositionMenu'); static readonly MenubarPreferencesMenu = new MenuId('MenubarPreferencesMenu'); static readonly MenubarRecentMenu = new MenuId('MenubarRecentMenu'); static readonly MenubarSelectionMenu = new MenuId('MenubarSelectionMenu'); @@ -107,6 +109,7 @@ export class MenuId { static readonly SCMResourceFolderContext = new MenuId('SCMResourceFolderContext'); static readonly SCMResourceGroupContext = new MenuId('SCMResourceGroupContext'); static readonly SCMSourceControl = new MenuId('SCMSourceControl'); + static readonly SCMInputBox = new MenuId('SCMInputBox'); static readonly SCMTitle = new MenuId('SCMTitle'); static readonly SearchContext = new MenuId('SearchContext'); static readonly SearchActionMenu = new MenuId('SearchActionContext'); diff --git a/code/src/vs/platform/audioCues/browser/audioCueService.ts b/code/src/vs/platform/audioCues/browser/audioCueService.ts index bbf03de5613..0915165b7c7 100644 --- a/code/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/code/src/vs/platform/audioCues/browser/audioCueService.ts @@ -254,6 +254,8 @@ export class Sound { public static readonly chatResponseReceived2 = Sound.register({ fileName: 'chatResponseReceived2.mp3' }); public static readonly chatResponseReceived3 = Sound.register({ fileName: 'chatResponseReceived3.mp3' }); public static readonly chatResponseReceived4 = Sound.register({ fileName: 'chatResponseReceived4.mp3' }); + public static readonly clear = Sound.register({ fileName: 'clear.mp3' }); + public static readonly save = Sound.register({ fileName: 'save.mp3' }); private constructor(public readonly fileName: string) { } } @@ -419,6 +421,18 @@ export class AudioCue { settingsKey: 'audioCues.chatResponsePending' }); + public static readonly clear = AudioCue.register({ + name: localize('audioCues.clear', 'Clear'), + sound: Sound.clear, + settingsKey: 'audioCues.clear' + }); + + public static readonly save = AudioCue.register({ + name: localize('audioCues.save', 'Save'), + sound: Sound.save, + settingsKey: 'audioCues.save' + }); + private constructor( public readonly sound: SoundSource, public readonly name: string, diff --git a/code/src/vs/platform/audioCues/browser/media/clear.mp3 b/code/src/vs/platform/audioCues/browser/media/clear.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..d26fc4ac3c19d352493ada6636998e16557404ab GIT binary patch literal 34816 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 literal 0 HcmV?d00001 diff --git a/code/src/vs/platform/audioCues/browser/media/save.mp3 b/code/src/vs/platform/audioCues/browser/media/save.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..259dedc99f8e0a5fadb1d6d269275bebd0a3ca93 GIT binary patch literal 30428 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 literal 0 HcmV?d00001 diff --git a/code/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts b/code/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts new file mode 100644 index 00000000000..b2715f1b555 --- /dev/null +++ b/code/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts @@ -0,0 +1,81 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { BrowserWindow, WebContents } from 'electron'; +import { Emitter, Event } from 'vs/base/common/event'; +import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; +import { BaseWindow } from 'vs/platform/windows/electron-main/windowImpl'; + +export interface IAuxiliaryWindow { + + readonly onDidClose: Event; + + readonly id: number; + readonly win: BrowserWindow | null; + + readonly lastFocusTime: number; + + focus(options?: { force: boolean }): void; +} + +export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow { + + readonly id = this.contents.id; + + private readonly _onDidClose = this._register(new Emitter()); + readonly onDidClose = this._onDidClose.event; + + private _win: BrowserWindow | null = null; + get win() { + if (!this._win) { + const window = BrowserWindow.fromWebContents(this.contents); + if (window) { + this._win = window; + this.registerWindowListeners(window); + } + } + + return this._win; + } + + protected getWin(): BrowserWindow | null { + return this.win; + } + + private _lastFocusTime = Date.now(); // window is shown on creation so take current time + get lastFocusTime(): number { return this._lastFocusTime; } + + constructor( + private readonly contents: WebContents, + @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService + ) { + super(); + + this.create(); + } + + private create(): void { + + // Handle devtools argument + if (this.environmentMainService.args['open-devtools'] === true) { + this.contents.openDevTools({ mode: 'bottom' }); + } + } + + private registerWindowListeners(window: BrowserWindow): void { + + // Window Close + window.on('closed', () => { + this._onDidClose.fire(); + + this.dispose(); + }); + + // Window Focus + window.on('focus', () => { + this._lastFocusTime = Date.now(); + }); + } +} diff --git a/code/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows.ts b/code/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows.ts new file mode 100644 index 00000000000..8effe598041 --- /dev/null +++ b/code/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows.ts @@ -0,0 +1,23 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { BrowserWindowConstructorOptions, WebContents } from 'electron'; +import { IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const IAuxiliaryWindowsMainService = createDecorator('auxiliaryWindowsMainService'); + +export interface IAuxiliaryWindowsMainService { + + readonly _serviceBrand: undefined; + + createWindow(): BrowserWindowConstructorOptions; + registerWindow(webContents: WebContents): void; + + getWindowById(windowId: number): IAuxiliaryWindow | undefined; + + getFocusedWindow(): IAuxiliaryWindow | undefined; + getLastActiveWindow(): IAuxiliaryWindow | undefined; +} diff --git a/code/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts b/code/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts new file mode 100644 index 00000000000..a85937cf5da --- /dev/null +++ b/code/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts @@ -0,0 +1,55 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { BrowserWindow, BrowserWindowConstructorOptions, WebContents } from 'electron'; +import { Event } from 'vs/base/common/event'; +import { FileAccess } from 'vs/base/common/network'; +import { AuxiliaryWindow, IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow'; +import { IAuxiliaryWindowsMainService } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { defaultBrowserWindowOptions, getLastFocused } from 'vs/platform/windows/electron-main/windows'; + +export class AuxiliaryWindowsMainService implements IAuxiliaryWindowsMainService { + + declare readonly _serviceBrand: undefined; + + private readonly windows = new Map(); + + constructor( + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { } + + createWindow(): BrowserWindowConstructorOptions { + return this.instantiationService.invokeFunction(defaultBrowserWindowOptions, undefined, { + webPreferences: { + preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-sandbox/preload-slim.js').fsPath + } + }); + } + + registerWindow(webContents: WebContents): void { + const auxiliaryWindow = this.instantiationService.createInstance(AuxiliaryWindow, webContents); + this.windows.set(auxiliaryWindow.id, auxiliaryWindow); + + Event.once(auxiliaryWindow.onDidClose)(() => this.windows.delete(auxiliaryWindow.id)); + } + + getWindowById(windowId: number): IAuxiliaryWindow | undefined { + return this.windows.get(windowId); + } + + getFocusedWindow(): IAuxiliaryWindow | undefined { + const window = BrowserWindow.getFocusedWindow(); + if (window) { + return this.getWindowById(window.id); + } + + return undefined; + } + + getLastActiveWindow(): IAuxiliaryWindow | undefined { + return getLastFocused(Array.from(this.windows.values())); + } +} diff --git a/code/src/vs/platform/extensionManagement/common/extensionsScannerService.ts b/code/src/vs/platform/extensionManagement/common/extensionsScannerService.ts index e7306d83f4b..0e4fe618e58 100644 --- a/code/src/vs/platform/extensionManagement/common/extensionsScannerService.ts +++ b/code/src/vs/platform/extensionManagement/common/extensionsScannerService.ts @@ -263,8 +263,12 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem const manifest: IScannedExtensionManifest = JSON.parse(content); // unset if false - metaData.isMachineScoped = metaData.isMachineScoped || undefined; - metaData.isBuiltin = metaData.isBuiltin || undefined; + if (metaData.isMachineScoped === false) { + delete metaData.isMachineScoped; + } + if (metaData.isBuiltin === false) { + delete metaData.isBuiltin; + } manifest.__metadata = { ...manifest.__metadata, ...metaData }; await this.fileService.writeFile(joinPath(extensionLocation, 'package.json'), VSBuffer.fromString(JSON.stringify(manifest, null, '\t'))); diff --git a/code/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/code/src/vs/platform/extensionManagement/node/extensionManagementService.ts index f8e8204ebb0..2fb974e3992 100644 --- a/code/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/code/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -194,9 +194,15 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi metadata.preRelease = true; } // unset if false - metadata.isMachineScoped = metadata.isMachineScoped || undefined; - metadata.isBuiltin = metadata.isBuiltin || undefined; - metadata.pinned = metadata.pinned || undefined; + if (metadata.isMachineScoped === false) { + metadata.isMachineScoped = undefined; + } + if (metadata.isBuiltin === false) { + metadata.isBuiltin = undefined; + } + if (metadata.pinned === false) { + metadata.pinned = undefined; + } local = await this.extensionsScanner.updateMetadata(local, metadata, profileLocation); this.manifestCache.invalidate(profileLocation); this._onDidUpdateExtensionMetadata.fire(local); diff --git a/code/src/vs/platform/files/common/diskFileSystemProvider.ts b/code/src/vs/platform/files/common/diskFileSystemProvider.ts index 374f7c9bcf5..5f5b623201b 100644 --- a/code/src/vs/platform/files/common/diskFileSystemProvider.ts +++ b/code/src/vs/platform/files/common/diskFileSystemProvider.ts @@ -11,7 +11,7 @@ import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle' import { normalize } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; import { IFileChange, IFileSystemProvider, IWatchOptions } from 'vs/platform/files/common/files'; -import { AbstractNonRecursiveWatcherClient, AbstractUniversalWatcherClient, IDiskFileChange, ILogMessage, INonRecursiveWatchRequest, IRecursiveWatcherOptions, isRecursiveWatchRequest, IUniversalWatchRequest, toFileChanges } from 'vs/platform/files/common/watcher'; +import { AbstractNonRecursiveWatcherClient, AbstractUniversalWatcherClient, ILogMessage, INonRecursiveWatchRequest, IRecursiveWatcherOptions, isRecursiveWatchRequest, IUniversalWatchRequest, reviveFileChanges } from 'vs/platform/files/common/watcher'; import { ILogService, LogLevel } from 'vs/platform/log/common/log'; export interface IDiskFileSystemProviderOptions { @@ -72,7 +72,7 @@ export abstract class AbstractDiskFileSystemProvider extends Disposable implemen private watchUniversal(resource: URI, opts: IWatchOptions): IDisposable { // Add to list of paths to watch universally - const pathToWatch: IUniversalWatchRequest = { path: this.toFilePath(resource), excludes: opts.excludes, includes: opts.includes, recursive: opts.recursive }; + const pathToWatch: IUniversalWatchRequest = { path: this.toFilePath(resource), excludes: opts.excludes, includes: opts.includes, recursive: opts.recursive, correlationId: opts.correlationId }; const remove = insert(this.universalPathsToWatch, pathToWatch); // Trigger update @@ -102,7 +102,7 @@ export abstract class AbstractDiskFileSystemProvider extends Disposable implemen // Create watcher if this is the first time if (!this.universalWatcher) { this.universalWatcher = this._register(this.createUniversalWatcher( - changes => this._onDidChangeFile.fire(toFileChanges(changes)), + changes => this._onDidChangeFile.fire(reviveFileChanges(changes)), msg => this.onWatcherLogMessage(msg), this.logService.getLevel() === LogLevel.Trace )); @@ -136,7 +136,7 @@ export abstract class AbstractDiskFileSystemProvider extends Disposable implemen } protected abstract createUniversalWatcher( - onChange: (changes: IDiskFileChange[]) => void, + onChange: (changes: IFileChange[]) => void, onLogMessage: (msg: ILogMessage) => void, verboseLogging: boolean ): AbstractUniversalWatcherClient; @@ -153,7 +153,7 @@ export abstract class AbstractDiskFileSystemProvider extends Disposable implemen private watchNonRecursive(resource: URI, opts: IWatchOptions): IDisposable { // Add to list of paths to watch non-recursively - const pathToWatch: INonRecursiveWatchRequest = { path: this.toFilePath(resource), excludes: opts.excludes, includes: opts.includes, recursive: false }; + const pathToWatch: INonRecursiveWatchRequest = { path: this.toFilePath(resource), excludes: opts.excludes, includes: opts.includes, recursive: false, correlationId: opts.correlationId }; const remove = insert(this.nonRecursivePathsToWatch, pathToWatch); // Trigger update @@ -183,7 +183,7 @@ export abstract class AbstractDiskFileSystemProvider extends Disposable implemen // Create watcher if this is the first time if (!this.nonRecursiveWatcher) { this.nonRecursiveWatcher = this._register(this.createNonRecursiveWatcher( - changes => this._onDidChangeFile.fire(toFileChanges(changes)), + changes => this._onDidChangeFile.fire(reviveFileChanges(changes)), msg => this.onWatcherLogMessage(msg), this.logService.getLevel() === LogLevel.Trace )); @@ -199,7 +199,7 @@ export abstract class AbstractDiskFileSystemProvider extends Disposable implemen } protected abstract createNonRecursiveWatcher( - onChange: (changes: IDiskFileChange[]) => void, + onChange: (changes: IFileChange[]) => void, onLogMessage: (msg: ILogMessage) => void, verboseLogging: boolean ): AbstractNonRecursiveWatcherClient; diff --git a/code/src/vs/platform/files/common/diskFileSystemProviderClient.ts b/code/src/vs/platform/files/common/diskFileSystemProviderClient.ts index d3719ddd0e7..d7f38517446 100644 --- a/code/src/vs/platform/files/common/diskFileSystemProviderClient.ts +++ b/code/src/vs/platform/files/common/diskFileSystemProviderClient.ts @@ -10,10 +10,11 @@ import { canceled } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { newWriteableStream, ReadableStreamEventPayload, ReadableStreamEvents } from 'vs/base/common/stream'; -import { URI, UriComponents } from 'vs/base/common/uri'; +import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { createFileSystemProviderError, IFileAtomicReadOptions, FileChangeType, IFileDeleteOptions, IFileOpenOptions, IFileOverwriteOptions, IFileReadStreamOptions, FileSystemProviderCapabilities, FileSystemProviderErrorCode, FileType, IFileWriteOptions, IFileChange, IFileSystemProviderWithFileAtomicReadCapability, IFileSystemProviderWithFileCloneCapability, IFileSystemProviderWithFileFolderCopyCapability, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, IStat, IWatchOptions, IFileSystemProviderError } from 'vs/platform/files/common/files'; +import { createFileSystemProviderError, IFileAtomicReadOptions, IFileDeleteOptions, IFileOpenOptions, IFileOverwriteOptions, IFileReadStreamOptions, FileSystemProviderCapabilities, FileSystemProviderErrorCode, FileType, IFileWriteOptions, IFileChange, IFileSystemProviderWithFileAtomicReadCapability, IFileSystemProviderWithFileCloneCapability, IFileSystemProviderWithFileFolderCopyCapability, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, IStat, IWatchOptions, IFileSystemProviderError } from 'vs/platform/files/common/files'; +import { reviveFileChanges } from 'vs/platform/files/common/watcher'; export const LOCAL_FILE_SYSTEM_CHANNEL_NAME = 'localFilesystem'; @@ -229,10 +230,10 @@ export class DiskFileSystemProviderClient extends Disposable implements // for both events and errors from the watcher. So we need to // unwrap the event from the remote and emit through the proper // emitter. - this._register(this.channel.listen<{ resource: UriComponents; type: FileChangeType }[] | string>('fileChange', [this.sessionId])(eventsOrError => { + this._register(this.channel.listen('fileChange', [this.sessionId])(eventsOrError => { if (Array.isArray(eventsOrError)) { const events = eventsOrError; - this._onDidChange.fire(events.map(event => ({ resource: URI.revive(event.resource), type: event.type }))); + this._onDidChange.fire(reviveFileChanges(events)); } else { const error = eventsOrError; this._onDidWatchError.fire(error); diff --git a/code/src/vs/platform/files/common/fileService.ts b/code/src/vs/platform/files/common/fileService.ts index e17e76ee5fb..791185e9a88 100644 --- a/code/src/vs/platform/files/common/fileService.ts +++ b/code/src/vs/platform/files/common/fileService.ts @@ -18,7 +18,7 @@ import { extUri, extUriIgnorePathCase, IExtUri, isAbsolutePath } from 'vs/base/c import { consumeStream, isReadableBufferedStream, isReadableStream, listenStream, newWriteableStream, peekReadable, peekStream, transform } from 'vs/base/common/stream'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; -import { ensureFileSystemProviderError, etag, ETAG_DISABLED, FileChangesEvent, IFileDeleteOptions, FileOperation, FileOperationError, FileOperationEvent, FileOperationResult, FilePermission, FileSystemProviderCapabilities, FileSystemProviderErrorCode, FileType, hasFileAtomicReadCapability, hasFileFolderCopyCapability, hasFileReadStreamCapability, hasOpenReadWriteCloseCapability, hasReadWriteCapability, ICreateFileOptions, IFileContent, IFileService, IFileStat, IFileStatWithMetadata, IFileStreamContent, IFileSystemProvider, IFileSystemProviderActivationEvent, IFileSystemProviderCapabilitiesChangeEvent, IFileSystemProviderRegistrationEvent, IFileSystemProviderWithFileAtomicReadCapability, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, IReadFileOptions, IReadFileStreamOptions, IResolveFileOptions, IFileStatResult, IFileStatResultWithMetadata, IResolveMetadataFileOptions, IStat, IFileStatWithPartialMetadata, IWatchOptions, IWriteFileOptions, NotModifiedSinceFileOperationError, toFileOperationResult, toFileSystemProviderErrorCode, hasFileCloneCapability, TooLargeFileOperationError, hasFileAtomicDeleteCapability, hasFileAtomicWriteCapability } from 'vs/platform/files/common/files'; +import { ensureFileSystemProviderError, etag, ETAG_DISABLED, FileChangesEvent, IFileDeleteOptions, FileOperation, FileOperationError, FileOperationEvent, FileOperationResult, FilePermission, FileSystemProviderCapabilities, FileSystemProviderErrorCode, FileType, hasFileAtomicReadCapability, hasFileFolderCopyCapability, hasFileReadStreamCapability, hasOpenReadWriteCloseCapability, hasReadWriteCapability, ICreateFileOptions, IFileContent, IFileService, IFileStat, IFileStatWithMetadata, IFileStreamContent, IFileSystemProvider, IFileSystemProviderActivationEvent, IFileSystemProviderCapabilitiesChangeEvent, IFileSystemProviderRegistrationEvent, IFileSystemProviderWithFileAtomicReadCapability, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, IReadFileOptions, IReadFileStreamOptions, IResolveFileOptions, IFileStatResult, IFileStatResultWithMetadata, IResolveMetadataFileOptions, IStat, IFileStatWithPartialMetadata, IWatchOptions, IWriteFileOptions, NotModifiedSinceFileOperationError, toFileOperationResult, toFileSystemProviderErrorCode, hasFileCloneCapability, TooLargeFileOperationError, hasFileAtomicDeleteCapability, hasFileAtomicWriteCapability, IWatchOptionsWithCorrelation, IFileSystemWatcher, IWatchOptionsWithoutCorrelation } from 'vs/platform/files/common/files'; import { readFileIntoStream } from 'vs/platform/files/common/io'; import { ILogService } from 'vs/platform/log/common/log'; import { ErrorNoTelemetry } from 'vs/base/common/errors'; @@ -63,7 +63,17 @@ export class FileService extends Disposable implements IFileService { this._onDidChangeFileSystemProviderRegistrations.fire({ added: true, scheme, provider }); // Forward events from provider - providerDisposables.add(provider.onDidChangeFile(changes => this._onDidFilesChange.fire(new FileChangesEvent(changes, !this.isPathCaseSensitive(provider))))); + providerDisposables.add(provider.onDidChangeFile(changes => { + const event = new FileChangesEvent(changes, !this.isPathCaseSensitive(provider)); + + // Always emit any event internally + this.internalOnDidFilesChange.fire(event); + + // Only emit uncorrelated events in the global `onDidFilesChange` event + if (!event.hasCorrelation()) { + this._onDidUncorrelatedFilesChange.fire(event); + } + })); if (typeof provider.onDidWatchError === 'function') { providerDisposables.add(provider.onDidWatchError(error => this._onDidWatchError.fire(new Error(error)))); } @@ -1094,15 +1104,31 @@ export class FileService extends Disposable implements IFileService { //#region File Watching - private readonly _onDidFilesChange = this._register(new Emitter()); - readonly onDidFilesChange = this._onDidFilesChange.event; + private readonly internalOnDidFilesChange = this._register(new Emitter()); + + private readonly _onDidUncorrelatedFilesChange = this._register(new Emitter()); + readonly onDidFilesChange = this._onDidUncorrelatedFilesChange.event; // global `onDidFilesChange` skips correlated events private readonly _onDidWatchError = this._register(new Emitter()); readonly onDidWatchError = this._onDidWatchError.event; private readonly activeWatchers = new Map(); - watch(resource: URI, options: IWatchOptions = { recursive: false, excludes: [] }): IDisposable { + private static WATCHER_CORRELATION_IDS = 0; + + createWatcher(resource: URI, options: IWatchOptionsWithoutCorrelation): IFileSystemWatcher { + return this.watch(resource, { + ...options, + // Explicitly set a correlation id so that file events that originate + // from requests from extensions are exclusively routed back to the + // extension host and not into the workbench. + correlationId: FileService.WATCHER_CORRELATION_IDS++ + }); + } + + watch(resource: URI, options: IWatchOptionsWithCorrelation): IFileSystemWatcher; + watch(resource: URI, options?: IWatchOptionsWithoutCorrelation): IDisposable; + watch(resource: URI, options: IWatchOptions = { recursive: false, excludes: [] }): IFileSystemWatcher | IDisposable { const disposables = new DisposableStore(); // Forward watch request to provider and wire in disposables @@ -1125,6 +1151,25 @@ export class FileService extends Disposable implements IFileService { } })(); + // When a correlation identifier is set, return a specific + // watcher that only emits events matching that correalation. + const correlationId = options.correlationId; + if (typeof correlationId === 'number') { + const fileChangeEmitter = disposables.add(new Emitter()); + disposables.add(this.internalOnDidFilesChange.event(e => { + if (e.correlates(correlationId)) { + fileChangeEmitter.fire(e); + } + })); + + const watcher: IFileSystemWatcher = { + onDidChange: fileChangeEmitter.event, + dispose: () => disposables.dispose() + }; + + return watcher; + } + return disposables; } diff --git a/code/src/vs/platform/files/common/files.ts b/code/src/vs/platform/files/common/files.ts index 0913cb6eaa8..ef1f5896b64 100644 --- a/code/src/vs/platform/files/common/files.ts +++ b/code/src/vs/platform/files/common/files.ts @@ -19,6 +19,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { isWeb } from 'vs/base/common/platform'; import { Schemas } from 'vs/base/common/network'; import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { Lazy } from 'vs/base/common/lazy'; //#region file service & providers @@ -237,10 +238,27 @@ export interface IFileService { /** * Allows to start a watcher that reports file/folder change events on the provided resource. * - * Note: recursive file watching is not supported from this method. Only events from files - * that are direct children of the provided resource will be reported. + * The watcher runs correlated and thus, file events will be reported on the returned + * `IFileSystemWatcher` and not on the generic `IFileService.onDidFilesChange` event. */ - watch(resource: URI, options?: IWatchOptions): IDisposable; + createWatcher(resource: URI, options: IWatchOptionsWithoutCorrelation): IFileSystemWatcher; + + /** + * Allows to start a watcher that reports file/folder change events on the provided resource. + * + * The watcher runs correlated and thus, file events will be reported on the returned + * `IFileSystemWatcher` and not on the generic `IFileService.onDidFilesChange` event. + */ + watch(resource: URI, options: IWatchOptionsWithCorrelation): IFileSystemWatcher; + + /** + * Allows to start a watcher that reports file/folder change events on the provided resource. + * + * The watcher runs uncorrelated and thus will report all events from `IFileService.onDidFilesChange`. + * This means, most listeners in the application will receive your events. It is encouraged to + * use correlated watchers (via `IWatchOptionsWithCorrelation`) to limit events to your listener. + */ + watch(resource: URI, options?: IWatchOptionsWithoutCorrelation): IDisposable; /** * Frees up any resources occupied by this service. @@ -484,13 +502,13 @@ export interface IStat { readonly permissions?: FilePermission; } -export interface IWatchOptions { +export interface IWatchOptionsWithoutCorrelation { /** * Set to `true` to watch for changes recursively in a folder * and all of its children. */ - readonly recursive: boolean; + recursive: boolean; /** * A set of glob patterns or paths to exclude from watching. @@ -511,6 +529,36 @@ export interface IWatchOptions { includes?: Array; } +export interface IWatchOptions extends IWatchOptionsWithoutCorrelation { + + /** + * If provided, file change events from the watcher that + * are a result of this watch request will carry the same + * id. + */ + readonly correlationId?: number; +} + +export interface IWatchOptionsWithCorrelation extends IWatchOptions { + readonly correlationId: number; +} + +export interface IFileSystemWatcher extends IDisposable { + + /** + * An event which fires on file/folder change only for changes + * that correlate to the watch request with matching correlation + * identifier. + */ + readonly onDidChange: Event; +} + +export function isFileSystemWatcher(thing: unknown): thing is IFileSystemWatcher { + const candidate = thing as IFileSystemWatcher | undefined; + + return !!candidate && typeof candidate.onDidChange === 'function'; +} + export const enum FileSystemProviderCapabilities { /** @@ -882,32 +930,32 @@ export interface IFileChange { /** * The type of change that occurred to the file. */ - readonly type: FileChangeType; + type: FileChangeType; /** * The unified resource identifier of the file that changed. */ readonly resource: URI; + + /** + * If provided when starting the file watcher, the correlation + * identifier will match the original file watching request as + * a way to identify the original component that is interested + * in the change. + */ + readonly cId?: number; } export class FileChangesEvent { - private readonly added: TernarySearchTree | undefined = undefined; - private readonly updated: TernarySearchTree | undefined = undefined; - private readonly deleted: TernarySearchTree | undefined = undefined; + private static readonly MIXED_CORRELATION = null; - constructor(changes: readonly IFileChange[], ignorePathCasing: boolean) { - - const entriesByType = new Map(); + private readonly correlationId: number | undefined | typeof FileChangesEvent.MIXED_CORRELATION = undefined; + constructor(changes: readonly IFileChange[], private readonly ignorePathCasing: boolean) { for (const change of changes) { - const array = entriesByType.get(change.type); - if (array) { - array.push([change.resource, change]); - } else { - entriesByType.set(change.type, [[change.resource, change]]); - } + // Split by type switch (change.type) { case FileChangeType.ADDED: this.rawAdded.push(change.resource); @@ -919,26 +967,45 @@ export class FileChangesEvent { this.rawDeleted.push(change.resource); break; } - } - for (const [key, value] of entriesByType) { - switch (key) { - case FileChangeType.ADDED: - this.added = TernarySearchTree.forUris(() => ignorePathCasing); - this.added.fill(value); - break; - case FileChangeType.UPDATED: - this.updated = TernarySearchTree.forUris(() => ignorePathCasing); - this.updated.fill(value); - break; - case FileChangeType.DELETED: - this.deleted = TernarySearchTree.forUris(() => ignorePathCasing); - this.deleted.fill(value); - break; + // Figure out events correlation + if (this.correlationId !== FileChangesEvent.MIXED_CORRELATION) { + if (typeof change.cId === 'number') { + if (this.correlationId === undefined) { + this.correlationId = change.cId; // correlation not yet set, just take it + } else if (this.correlationId !== change.cId) { + this.correlationId = FileChangesEvent.MIXED_CORRELATION; // correlation mismatch, we have mixed correlation + } + } else { + if (this.correlationId !== undefined) { + this.correlationId = FileChangesEvent.MIXED_CORRELATION; // correlation mismatch, we have mixed correlation + } + } } } } + private readonly added = new Lazy(() => { + const added = TernarySearchTree.forUris(() => this.ignorePathCasing); + added.fill(this.rawAdded.map(resource => [resource, true])); + + return added; + }); + + private readonly updated = new Lazy(() => { + const updated = TernarySearchTree.forUris(() => this.ignorePathCasing); + updated.fill(this.rawUpdated.map(resource => [resource, true])); + + return updated; + }); + + private readonly deleted = new Lazy(() => { + const deleted = TernarySearchTree.forUris(() => this.ignorePathCasing); + deleted.fill(this.rawDeleted.map(resource => [resource, true])); + + return deleted; + }); + /** * Find out if the file change events match the provided resource. * @@ -966,33 +1033,33 @@ export class FileChangesEvent { // Added if (!hasTypesFilter || types.includes(FileChangeType.ADDED)) { - if (this.added?.get(resource)) { + if (this.added.value.get(resource)) { return true; } - if (options.includeChildren && this.added?.findSuperstr(resource)) { + if (options.includeChildren && this.added.value.findSuperstr(resource)) { return true; } } // Updated if (!hasTypesFilter || types.includes(FileChangeType.UPDATED)) { - if (this.updated?.get(resource)) { + if (this.updated.value.get(resource)) { return true; } - if (options.includeChildren && this.updated?.findSuperstr(resource)) { + if (options.includeChildren && this.updated.value.findSuperstr(resource)) { return true; } } // Deleted if (!hasTypesFilter || types.includes(FileChangeType.DELETED)) { - if (this.deleted?.findSubstr(resource) /* deleted also considers parent folders */) { + if (this.deleted.value.findSubstr(resource) /* deleted also considers parent folders */) { return true; } - if (options.includeChildren && this.deleted?.findSuperstr(resource)) { + if (options.includeChildren && this.deleted.value.findSuperstr(resource)) { return true; } } @@ -1004,21 +1071,47 @@ export class FileChangesEvent { * Returns if this event contains added files. */ gotAdded(): boolean { - return !!this.added; + return this.rawAdded.length > 0; } /** * Returns if this event contains deleted files. */ gotDeleted(): boolean { - return !!this.deleted; + return this.rawDeleted.length > 0; } /** * Returns if this event contains updated files. */ gotUpdated(): boolean { - return !!this.updated; + return this.rawUpdated.length > 0; + } + + /** + * Returns if this event contains changes that correlate to the + * provided `correlationId`. + * + * File change event correlation is an advanced watch feature that + * allows to identify from which watch request the events originate + * from. This correlation allows to route events specifically + * only to the requestor and not emit them to all listeners. + */ + correlates(correlationId: number): boolean { + return this.correlationId === correlationId; + } + + /** + * Figure out if the event contains changes that correlate to one + * correlation identifier. + * + * File change event correlation is an advanced watch feature that + * allows to identify from which watch request the events originate + * from. This correlation allows to route events specifically + * only to the requestor and not emit them to all listeners. + */ + hasCorrelation(): boolean { + return typeof this.correlationId === 'number'; } /** diff --git a/code/src/vs/platform/files/common/watcher.ts b/code/src/vs/platform/files/common/watcher.ts index 798c884805f..ae97f833078 100644 --- a/code/src/vs/platform/files/common/watcher.ts +++ b/code/src/vs/platform/files/common/watcher.ts @@ -34,6 +34,13 @@ interface IWatchRequest { * events. */ readonly includes?: Array; + + /** + * If provided, file change events from the watcher that + * are a result of this watch request will carry the same + * id. + */ + readonly correlationId?: number; } export interface INonRecursiveWatchRequest extends IWatchRequest { @@ -70,7 +77,7 @@ interface IWatcher { * A normalized file change event from the raw events * the watcher emits. */ - readonly onDidChangeFile: Event; + readonly onDidChangeFile: Event; /** * An event to indicate a message that should get logged. @@ -148,7 +155,7 @@ export abstract class AbstractWatcherClient extends Disposable { private restartCounter = 0; constructor( - private readonly onFileChanges: (changes: IDiskFileChange[]) => void, + private readonly onFileChanges: (changes: IFileChange[]) => void, private readonly onLogMessage: (msg: ILogMessage) => void, private verboseLogging: boolean, private options: { @@ -234,7 +241,7 @@ export abstract class AbstractWatcherClient extends Disposable { export abstract class AbstractNonRecursiveWatcherClient extends AbstractWatcherClient { constructor( - onFileChanges: (changes: IDiskFileChange[]) => void, + onFileChanges: (changes: IFileChange[]) => void, onLogMessage: (msg: ILogMessage) => void, verboseLogging: boolean ) { @@ -247,7 +254,7 @@ export abstract class AbstractNonRecursiveWatcherClient extends AbstractWatcherC export abstract class AbstractUniversalWatcherClient extends AbstractWatcherClient { constructor( - onFileChanges: (changes: IDiskFileChange[]) => void, + onFileChanges: (changes: IFileChange[]) => void, onLogMessage: (msg: ILogMessage) => void, verboseLogging: boolean ) { @@ -257,24 +264,20 @@ export abstract class AbstractUniversalWatcherClient extends AbstractWatcherClie protected abstract override createWatcher(disposables: DisposableStore): IUniversalWatcher; } -export interface IDiskFileChange { - type: FileChangeType; - readonly resource: URI; -} - export interface ILogMessage { readonly type: 'trace' | 'warn' | 'error' | 'info' | 'debug'; readonly message: string; } -export function toFileChanges(changes: IDiskFileChange[]): IFileChange[] { +export function reviveFileChanges(changes: IFileChange[]): IFileChange[] { return changes.map(change => ({ type: change.type, - resource: URI.revive(change.resource) + resource: URI.revive(change.resource), + cId: change.cId })); } -export function coalesceEvents(changes: IDiskFileChange[]): IDiskFileChange[] { +export function coalesceEvents(changes: IFileChange[]): IFileChange[] { // Build deltas const coalescer = new EventCoalescer(); @@ -312,10 +315,10 @@ export function parseWatcherPatterns(path: string, patterns: Array(); - private readonly mapPathToChange = new Map(); + private readonly coalesced = new Set(); + private readonly mapPathToChange = new Map(); - private toKey(event: IDiskFileChange): string { + private toKey(event: IFileChange): string { if (isLinux) { return event.resource.fsPath; } @@ -323,7 +326,7 @@ class EventCoalescer { return event.resource.fsPath.toLowerCase(); // normalise to file system case sensitivity } - processEvent(event: IDiskFileChange): void { + processEvent(event: IFileChange): void { const existingEvent = this.mapPathToChange.get(this.toKey(event)); let keepEvent = false; @@ -370,8 +373,8 @@ class EventCoalescer { } } - coalesce(): IDiskFileChange[] { - const addOrChangeEvents: IDiskFileChange[] = []; + coalesce(): IFileChange[] { + const addOrChangeEvents: IFileChange[] = []; const deletedPaths: string[] = []; // This algorithm will remove all DELETE events up to the root folder diff --git a/code/src/vs/platform/files/node/diskFileSystemProvider.ts b/code/src/vs/platform/files/node/diskFileSystemProvider.ts index 2aa00b75c55..180aa7e2960 100644 --- a/code/src/vs/platform/files/node/diskFileSystemProvider.ts +++ b/code/src/vs/platform/files/node/diskFileSystemProvider.ts @@ -19,9 +19,9 @@ import { newWriteableStream, ReadableStreamEvents } from 'vs/base/common/stream' import { URI } from 'vs/base/common/uri'; import { IDirent, Promises, RimRafMode, SymlinkSupport } from 'vs/base/node/pfs'; import { localize } from 'vs/nls'; -import { createFileSystemProviderError, IFileAtomicReadOptions, IFileDeleteOptions, IFileOpenOptions, IFileOverwriteOptions, IFileReadStreamOptions, FileSystemProviderCapabilities, FileSystemProviderError, FileSystemProviderErrorCode, FileType, IFileWriteOptions, IFileSystemProviderWithFileAtomicReadCapability, IFileSystemProviderWithFileCloneCapability, IFileSystemProviderWithFileFolderCopyCapability, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, isFileOpenForWriteOptions, IStat, FilePermission, IFileSystemProviderWithFileAtomicWriteCapability, IFileSystemProviderWithFileAtomicDeleteCapability } from 'vs/platform/files/common/files'; +import { createFileSystemProviderError, IFileAtomicReadOptions, IFileDeleteOptions, IFileOpenOptions, IFileOverwriteOptions, IFileReadStreamOptions, FileSystemProviderCapabilities, FileSystemProviderError, FileSystemProviderErrorCode, FileType, IFileWriteOptions, IFileSystemProviderWithFileAtomicReadCapability, IFileSystemProviderWithFileCloneCapability, IFileSystemProviderWithFileFolderCopyCapability, IFileSystemProviderWithFileReadStreamCapability, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, isFileOpenForWriteOptions, IStat, FilePermission, IFileSystemProviderWithFileAtomicWriteCapability, IFileSystemProviderWithFileAtomicDeleteCapability, IFileChange } from 'vs/platform/files/common/files'; import { readFileIntoStream } from 'vs/platform/files/common/io'; -import { AbstractNonRecursiveWatcherClient, AbstractUniversalWatcherClient, IDiskFileChange, ILogMessage } from 'vs/platform/files/common/watcher'; +import { AbstractNonRecursiveWatcherClient, AbstractUniversalWatcherClient, ILogMessage } from 'vs/platform/files/common/watcher'; import { ILogService } from 'vs/platform/log/common/log'; import { AbstractDiskFileSystemProvider, IDiskFileSystemProviderOptions } from 'vs/platform/files/common/diskFileSystemProvider'; import { toErrorMessage } from 'vs/base/common/errorMessage'; @@ -812,7 +812,7 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple //#region File Watching protected createUniversalWatcher( - onChange: (changes: IDiskFileChange[]) => void, + onChange: (changes: IFileChange[]) => void, onLogMessage: (msg: ILogMessage) => void, verboseLogging: boolean ): AbstractUniversalWatcherClient { @@ -820,7 +820,7 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple } protected createNonRecursiveWatcher( - onChange: (changes: IDiskFileChange[]) => void, + onChange: (changes: IFileChange[]) => void, onLogMessage: (msg: ILogMessage) => void, verboseLogging: boolean ): AbstractNonRecursiveWatcherClient { diff --git a/code/src/vs/platform/files/node/diskFileSystemProviderServer.ts b/code/src/vs/platform/files/node/diskFileSystemProviderServer.ts index fefc836be54..a6407715b10 100644 --- a/code/src/vs/platform/files/node/diskFileSystemProviderServer.ts +++ b/code/src/vs/platform/files/node/diskFileSystemProviderServer.ts @@ -292,7 +292,8 @@ export abstract class AbstractSessionFileWatcher extends Disposable implements I sessionEmitter.fire( events.map(e => ({ resource: this.uriTransformer.transformOutgoingURI(e.resource), - type: e.type + type: e.type, + cId: e.cId })) ); })); diff --git a/code/src/vs/platform/files/node/watcher/nodejs/nodejsClient.ts b/code/src/vs/platform/files/node/watcher/nodejs/nodejsClient.ts index b57721ef43c..11eb6b8a109 100644 --- a/code/src/vs/platform/files/node/watcher/nodejs/nodejsClient.ts +++ b/code/src/vs/platform/files/node/watcher/nodejs/nodejsClient.ts @@ -4,13 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { DisposableStore } from 'vs/base/common/lifecycle'; -import { IDiskFileChange, ILogMessage, AbstractNonRecursiveWatcherClient, INonRecursiveWatcher } from 'vs/platform/files/common/watcher'; +import { IFileChange } from 'vs/platform/files/common/files'; +import { ILogMessage, AbstractNonRecursiveWatcherClient, INonRecursiveWatcher } from 'vs/platform/files/common/watcher'; import { NodeJSWatcher } from 'vs/platform/files/node/watcher/nodejs/nodejsWatcher'; export class NodeJSWatcherClient extends AbstractNonRecursiveWatcherClient { constructor( - onFileChanges: (changes: IDiskFileChange[]) => void, + onFileChanges: (changes: IFileChange[]) => void, onLogMessage: (msg: ILogMessage) => void, verboseLogging: boolean ) { diff --git a/code/src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts b/code/src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts index 3a5c43c9ff5..dac55a138c5 100644 --- a/code/src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts +++ b/code/src/vs/platform/files/node/watcher/nodejs/nodejsWatcher.ts @@ -7,7 +7,8 @@ import { Event, Emitter } from 'vs/base/common/event'; import { patternsEquals } from 'vs/base/common/glob'; import { Disposable } from 'vs/base/common/lifecycle'; import { isLinux } from 'vs/base/common/platform'; -import { IDiskFileChange, ILogMessage, INonRecursiveWatchRequest, INonRecursiveWatcher } from 'vs/platform/files/common/watcher'; +import { IFileChange } from 'vs/platform/files/common/files'; +import { ILogMessage, INonRecursiveWatchRequest, INonRecursiveWatcher } from 'vs/platform/files/common/watcher'; import { NodeJSFileWatcherLibrary } from 'vs/platform/files/node/watcher/nodejs/nodejsWatcherLib'; export interface INodeJSWatcherInstance { @@ -25,7 +26,7 @@ export interface INodeJSWatcherInstance { export class NodeJSWatcher extends Disposable implements INonRecursiveWatcher { - private readonly _onDidChangeFile = this._register(new Emitter()); + private readonly _onDidChangeFile = this._register(new Emitter()); readonly onDidChangeFile = this._onDidChangeFile.event; private readonly _onDidLogMessage = this._register(new Emitter()); @@ -61,7 +62,7 @@ export class NodeJSWatcher extends Disposable implements INonRecursiveWatcher { // Logging if (requestsToStartWatching.length) { - this.trace(`Request to start watching: ${requestsToStartWatching.map(request => `${request.path} (excludes: ${request.excludes.length > 0 ? request.excludes : ''}, includes: ${request.includes && request.includes.length > 0 ? JSON.stringify(request.includes) : ''})`).join(',')}`); + this.trace(`Request to start watching: ${requestsToStartWatching.map(request => `${request.path} (excludes: ${request.excludes.length > 0 ? request.excludes : ''}, includes: ${request.includes && request.includes.length > 0 ? JSON.stringify(request.includes) : ''}, correlationId: ${typeof request.correlationId === 'number' ? request.correlationId : ''})`).join(',')}`); } if (pathsToStopWatching.length) { @@ -107,15 +108,22 @@ export class NodeJSWatcher extends Disposable implements INonRecursiveWatcher { } private normalizeRequests(requests: INonRecursiveWatchRequest[]): INonRecursiveWatchRequest[] { - const requestsMap = new Map(); + const mapCorrelationtoRequests = new Map>(); - // Ignore requests for the same paths + // Ignore requests for the same paths that have the same correlation for (const request of requests) { const path = isLinux ? request.path : request.path.toLowerCase(); // adjust for case sensitivity - requestsMap.set(path, request); + + let requestsForCorrelation = mapCorrelationtoRequests.get(request.correlationId); + if (!requestsForCorrelation) { + requestsForCorrelation = new Map(); + mapCorrelationtoRequests.set(request.correlationId, requestsForCorrelation); + } + + requestsForCorrelation.set(path, request); } - return Array.from(requestsMap.values()); + return Array.from(mapCorrelationtoRequests.values()).map(requests => Array.from(requests.values())).flat(); } async setVerboseLogging(enabled: boolean): Promise { diff --git a/code/src/vs/platform/files/node/watcher/nodejs/nodejsWatcherLib.ts b/code/src/vs/platform/files/node/watcher/nodejs/nodejsWatcherLib.ts index ea35280301f..8f0f7d6a87f 100644 --- a/code/src/vs/platform/files/node/watcher/nodejs/nodejsWatcherLib.ts +++ b/code/src/vs/platform/files/node/watcher/nodejs/nodejsWatcherLib.ts @@ -11,11 +11,12 @@ import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/ import { normalizeNFC } from 'vs/base/common/normalization'; import { basename, dirname, join } from 'vs/base/common/path'; import { isLinux, isMacintosh } from 'vs/base/common/platform'; +import { joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { realcase } from 'vs/base/node/extpath'; import { Promises } from 'vs/base/node/pfs'; -import { FileChangeType } from 'vs/platform/files/common/files'; -import { IDiskFileChange, ILogMessage, coalesceEvents, INonRecursiveWatchRequest, parseWatcherPatterns } from 'vs/platform/files/common/watcher'; +import { FileChangeType, IFileChange } from 'vs/platform/files/common/files'; +import { ILogMessage, coalesceEvents, INonRecursiveWatchRequest, parseWatcherPatterns } from 'vs/platform/files/common/watcher'; export class NodeJSFileWatcherLibrary extends Disposable { @@ -35,7 +36,7 @@ export class NodeJSFileWatcherLibrary extends Disposable { // recursive watcher because we can have many individual // node.js watchers per request. // (https://github.com/microsoft/vscode/issues/124723) - private readonly throttledFileChangesEmitter = this._register(new ThrottledWorker( + private readonly throttledFileChangesEmitter = this._register(new ThrottledWorker( { maxWorkChunkSize: 100, // only process up to 100 changes at once before... throttleDelay: 200, // ...resting for 200ms until we process events again... @@ -46,7 +47,7 @@ export class NodeJSFileWatcherLibrary extends Disposable { // Aggregate file changes over FILE_CHANGES_HANDLER_DELAY // to coalesce events and reduce spam. - private readonly fileChangesAggregator = this._register(new RunOnceWorker(events => this.handleFileChanges(events), NodeJSFileWatcherLibrary.FILE_CHANGES_HANDLER_DELAY)); + private readonly fileChangesAggregator = this._register(new RunOnceWorker(events => this.handleFileChanges(events), NodeJSFileWatcherLibrary.FILE_CHANGES_HANDLER_DELAY)); private readonly excludes = parseWatcherPatterns(this.request.path, this.request.excludes); private readonly includes = this.request.includes ? parseWatcherPatterns(this.request.path, this.request.includes) : undefined; @@ -57,7 +58,7 @@ export class NodeJSFileWatcherLibrary extends Disposable { constructor( private request: INonRecursiveWatchRequest, - private onDidFilesChange: (changes: IDiskFileChange[]) => void, + private onDidFilesChange: (changes: IFileChange[]) => void, private onLogMessage?: (msg: ILogMessage) => void, private verboseLogging?: boolean ) { @@ -128,6 +129,7 @@ export class NodeJSFileWatcherLibrary extends Disposable { const disposables = new DisposableStore(); try { + const requestResource = URI.file(this.request.path); const pathBasename = basename(path); // Creating watcher can fail with an exception @@ -261,7 +263,7 @@ export class NodeJSFileWatcherLibrary extends Disposable { type = FileChangeType.DELETED; } - this.onFileChange({ resource: URI.file(join(this.request.path, changedFileName)), type }); + this.onFileChange({ resource: joinPath(requestResource, changedFileName), type, cId: this.request.correlationId }); }, NodeJSFileWatcherLibrary.FILE_DELETE_HANDLER_DELAY); mapPathToStatDisposable.set(changedFileName, toDisposable(() => clearTimeout(timeoutHandle))); @@ -280,7 +282,7 @@ export class NodeJSFileWatcherLibrary extends Disposable { folderChildren.add(changedFileName); } - this.onFileChange({ resource: URI.file(join(this.request.path, changedFileName)), type }); + this.onFileChange({ resource: joinPath(requestResource, changedFileName), type, cId: this.request.correlationId }); } } @@ -319,14 +321,14 @@ export class NodeJSFileWatcherLibrary extends Disposable { // File still exists, so emit as change event and reapply the watcher if (fileExists) { - this.onFileChange({ resource: URI.file(this.request.path), type: FileChangeType.UPDATED }, true /* skip excludes/includes (file is explicitly watched) */); + this.onFileChange({ resource: requestResource, type: FileChangeType.UPDATED, cId: this.request.correlationId }, true /* skip excludes/includes (file is explicitly watched) */); disposables.add(await this.doWatch(path, false)); } // File seems to be really gone, so emit a deleted event and dispose else { - this.onFileChange({ resource: URI.file(this.request.path), type: FileChangeType.DELETED }, true /* skip excludes/includes (file is explicitly watched) */); + this.onFileChange({ resource: requestResource, type: FileChangeType.DELETED, cId: this.request.correlationId }, true /* skip excludes/includes (file is explicitly watched) */); // Important to flush the event delivery // before disposing the watcher, otherwise @@ -345,7 +347,7 @@ export class NodeJSFileWatcherLibrary extends Disposable { // File changed else { - this.onFileChange({ resource: URI.file(this.request.path), type: FileChangeType.UPDATED }, true /* skip excludes/includes (file is explicitly watched) */); + this.onFileChange({ resource: requestResource, type: FileChangeType.UPDATED, cId: this.request.correlationId }, true /* skip excludes/includes (file is explicitly watched) */); } } }); @@ -361,7 +363,7 @@ export class NodeJSFileWatcherLibrary extends Disposable { }); } - private onFileChange(event: IDiskFileChange, skipIncludeExcludeChecks = false): void { + private onFileChange(event: IFileChange, skipIncludeExcludeChecks = false): void { if (this.cts.token.isCancellationRequested) { return; } @@ -385,7 +387,7 @@ export class NodeJSFileWatcherLibrary extends Disposable { } } - private handleFileChanges(fileChanges: IDiskFileChange[]): void { + private handleFileChanges(fileChanges: IFileChange[]): void { // Coalesce events: merge events of same kind const coalescedFileChanges = coalesceEvents(fileChanges); @@ -394,7 +396,7 @@ export class NodeJSFileWatcherLibrary extends Disposable { // Logging if (this.verboseLogging) { for (const event of coalescedFileChanges) { - this.trace(`>> normalized ${event.type === FileChangeType.ADDED ? '[ADDED]' : event.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${event.resource.fsPath}`); + this.trace(` >> normalized ${event.type === FileChangeType.ADDED ? '[ADDED]' : event.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${event.resource.fsPath}`); } } diff --git a/code/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts b/code/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts index 89f19fd5261..d1b978043ed 100644 --- a/code/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts +++ b/code/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts @@ -20,8 +20,8 @@ import { dirname, normalize } from 'vs/base/common/path'; import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { realcaseSync, realpathSync } from 'vs/base/node/extpath'; import { NodeJSFileWatcherLibrary } from 'vs/platform/files/node/watcher/nodejs/nodejsWatcherLib'; -import { FileChangeType } from 'vs/platform/files/common/files'; -import { IDiskFileChange, ILogMessage, coalesceEvents, IRecursiveWatchRequest, IRecursiveWatcher, parseWatcherPatterns } from 'vs/platform/files/common/watcher'; +import { FileChangeType, IFileChange } from 'vs/platform/files/common/files'; +import { ILogMessage, coalesceEvents, IRecursiveWatchRequest, IRecursiveWatcher, parseWatcherPatterns } from 'vs/platform/files/common/watcher'; export interface IParcelWatcherInstance { @@ -49,7 +49,7 @@ export interface IParcelWatcherInstance { /** * An event aggregator to coalesce events and reduce duplicates. */ - readonly worker: RunOnceWorker; + readonly worker: RunOnceWorker; /** * Stops and disposes the watcher. This operation is async to await @@ -70,7 +70,7 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher { private static readonly PARCEL_WATCHER_BACKEND = isWindows ? 'windows' : isLinux ? 'inotify' : 'fs-events'; - private readonly _onDidChangeFile = this._register(new Emitter()); + private readonly _onDidChangeFile = this._register(new Emitter()); readonly onDidChangeFile = this._onDidChangeFile.event; private readonly _onDidLogMessage = this._register(new Emitter()); @@ -95,7 +95,7 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher { // Reduce likelyhood of spam from file events via throttling. // (https://github.com/microsoft/vscode/issues/124723) - private readonly throttledFileChangesEmitter = this._register(new ThrottledWorker( + private readonly throttledFileChangesEmitter = this._register(new ThrottledWorker( { maxWorkChunkSize: 500, // only process up to 500 changes at once before... throttleDelay: 200, // ...resting for 200ms until we process events again... @@ -150,7 +150,7 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher { // Logging if (requestsToStartWatching.length) { - this.trace(`Request to start watching: ${requestsToStartWatching.map(request => `${request.path} (excludes: ${request.excludes.length > 0 ? request.excludes : ''}, includes: ${request.includes && request.includes.length > 0 ? JSON.stringify(request.includes) : ''})`).join(',')}`); + this.trace(`Request to start watching: ${requestsToStartWatching.map(request => `${request.path} (excludes: ${request.excludes.length > 0 ? request.excludes : ''}, includes: ${request.includes && request.includes.length > 0 ? JSON.stringify(request.includes) : ''}, correlationId: ${typeof request.correlationId === 'number' ? request.correlationId : ''})`).join(',')}`); } if (pathsToStopWatching.length) { @@ -185,7 +185,7 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher { ready: instance.p, restarts, token: cts.token, - worker: new RunOnceWorker(events => this.handleParcelEvents(events, watcher), ParcelWatcher.FILE_CHANGES_HANDLER_DELAY), + worker: new RunOnceWorker(events => this.handleParcelEvents(events, watcher), ParcelWatcher.FILE_CHANGES_HANDLER_DELAY), stop: async () => { cts.dispose(true); @@ -256,7 +256,7 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher { ready: instance.p, restarts, token: cts.token, - worker: new RunOnceWorker(events => this.handleParcelEvents(events, watcher), ParcelWatcher.FILE_CHANGES_HANDLER_DELAY), + worker: new RunOnceWorker(events => this.handleParcelEvents(events, watcher), ParcelWatcher.FILE_CHANGES_HANDLER_DELAY), stop: async () => { cts.dispose(true); @@ -315,7 +315,7 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher { this.normalizeEvents(parcelEvents, watcher.request, realPathDiffers, realPathLength); // Check for includes - const includedEvents = this.handleIncludes(parcelEvents, includes); + const includedEvents = this.handleIncludes(watcher, parcelEvents, includes); // Add to event aggregator for later processing for (const includedEvent of includedEvents) { @@ -323,8 +323,8 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher { } } - private handleIncludes(parcelEvents: parcelWatcher.Event[], includes: ParsedPattern[] | undefined): IDiskFileChange[] { - const events: IDiskFileChange[] = []; + private handleIncludes(watcher: IParcelWatcherInstance, parcelEvents: parcelWatcher.Event[], includes: ParsedPattern[] | undefined): IFileChange[] { + const events: IFileChange[] = []; for (const { path, type: parcelEventType } of parcelEvents) { const type = ParcelWatcher.MAP_PARCEL_WATCHER_ACTION_TO_FILE_CHANGE.get(parcelEventType)!; @@ -338,14 +338,14 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher { this.trace(` >> ignored (not included) ${path}`); } } else { - events.push({ type, resource: URI.file(path) }); + events.push({ type, resource: URI.file(path), cId: watcher.request.correlationId }); } } return events; } - private handleParcelEvents(parcelEvents: IDiskFileChange[], watcher: IParcelWatcherInstance): void { + private handleParcelEvents(parcelEvents: IFileChange[], watcher: IParcelWatcherInstance): void { // Coalesce events: merge events of same kind const coalescedEvents = coalesceEvents(parcelEvents); @@ -354,7 +354,7 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher { const { events: filteredEvents, rootDeleted } = this.filterEvents(coalescedEvents, watcher); // Broadcast to clients - this.emitEvents(filteredEvents); + this.emitEvents(filteredEvents, watcher); // Handle root path deletes if (rootDeleted) { @@ -362,7 +362,7 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher { } } - private emitEvents(events: IDiskFileChange[]): void { + private emitEvents(events: IFileChange[], watcher: IParcelWatcherInstance): void { if (events.length === 0) { return; } @@ -370,7 +370,8 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher { // Logging if (this.verboseLogging) { for (const event of events) { - this.trace(` >> normalized ${event.type === FileChangeType.ADDED ? '[ADDED]' : event.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${event.resource.fsPath}`); + const traceMsg = ` >> normalized ${event.type === FileChangeType.ADDED ? '[ADDED]' : event.type === FileChangeType.DELETED ? '[DELETED]' : '[CHANGED]'} ${event.resource.fsPath}`; + this.trace(typeof watcher.request.correlationId === 'number' ? `${traceMsg} (correlationId: ${watcher.request.correlationId})` : traceMsg); } } @@ -440,8 +441,8 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher { } } - private filterEvents(events: IDiskFileChange[], watcher: IParcelWatcherInstance): { events: IDiskFileChange[]; rootDeleted?: boolean } { - const filteredEvents: IDiskFileChange[] = []; + private filterEvents(events: IFileChange[], watcher: IParcelWatcherInstance): { events: IFileChange[]; rootDeleted?: boolean } { + const filteredEvents: IFileChange[] = []; let rootDeleted = false; for (const event of events) { @@ -467,7 +468,7 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher { const parentPath = dirname(watcher.request.path); if (existsSync(parentPath)) { - const nodeWatcher = new NodeJSFileWatcherLibrary({ path: parentPath, excludes: [], recursive: false }, changes => { + const nodeWatcher = new NodeJSFileWatcherLibrary({ path: parentPath, excludes: [], recursive: false, correlationId: watcher.request.correlationId }, changes => { if (watcher.token.isCancellationRequested) { return; // return early when disposed } @@ -569,62 +570,86 @@ export class ParcelWatcher extends Disposable implements IRecursiveWatcher { } protected normalizeRequests(requests: IRecursiveWatchRequest[], validatePaths = true): IRecursiveWatchRequest[] { - const requestTrie = TernarySearchTree.forPaths(!isLinux); // Sort requests by path length to have shortest first // to have a way to prevent children to be watched if // parents exist. requests.sort((requestA, requestB) => requestA.path.length - requestB.path.length); - // Only consider requests for watching that are not - // a child of an existing request path to prevent - // duplication. In addition, drop any request where - // everything is excluded (via `**` glob). - // - // However, allow explicit requests to watch folders - // that are symbolic links because the Parcel watcher - // does not allow to recursively watch symbolic links. + // Map request paths to correlation and ignore identical paths + const mapCorrelationtoRequests = new Map>(); for (const request of requests) { if (request.excludes.includes(GLOBSTAR)) { 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}`); + const path = isLinux ? request.path : request.path.toLowerCase(); // adjust for case sensitivity + + let requestsForCorrelation = mapCorrelationtoRequests.get(request.correlationId); + if (!requestsForCorrelation) { + requestsForCorrelation = new Map(); + mapCorrelationtoRequests.set(request.correlationId, requestsForCorrelation); + } + + requestsForCorrelation.set(path, request); + } + + const normalizedRequests: IRecursiveWatchRequest[] = []; + + for (const requestsForCorrelation of mapCorrelationtoRequests.values()) { + + // Only consider requests for watching that are not + // a child of an existing request path to prevent + // duplication. In addition, drop any request where + // everything is excluded (via `**` glob). + // + // However, allow explicit requests to watch folders + // that are symbolic links because the Parcel watcher + // does not allow to recursively watch symbolic links. + + const requestTrie = TernarySearchTree.forPaths(!isLinux); + + for (const request of requestsForCorrelation.values()) { + + // 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; + } + } catch (error) { + this.trace(`ignoring a path for watching who's realpath failed to resolve: ${request.path} (error: ${error})`); continue; } - } catch (error) { - 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}`); + // 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; } - } catch (error) { - this.trace(`ignoring a path for watching who's stat info failed to resolve: ${request.path} (error: ${error})`); - - continue; } + + requestTrie.set(request.path, request); } - requestTrie.set(request.path, request); + normalizedRequests.push(...Array.from(requestTrie).map(([, request]) => request)); } - return Array.from(requestTrie).map(([, request]) => request); + return normalizedRequests; } async setVerboseLogging(enabled: boolean): Promise { diff --git a/code/src/vs/platform/files/node/watcher/watcherClient.ts b/code/src/vs/platform/files/node/watcher/watcherClient.ts index 221f7629496..86a89330cd1 100644 --- a/code/src/vs/platform/files/node/watcher/watcherClient.ts +++ b/code/src/vs/platform/files/node/watcher/watcherClient.ts @@ -7,12 +7,13 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { FileAccess } from 'vs/base/common/network'; import { getNextTickChannel, ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; import { Client } from 'vs/base/parts/ipc/node/ipc.cp'; -import { AbstractUniversalWatcherClient, IDiskFileChange, ILogMessage, IUniversalWatcher } from 'vs/platform/files/common/watcher'; +import { IFileChange } from 'vs/platform/files/common/files'; +import { AbstractUniversalWatcherClient, ILogMessage, IUniversalWatcher } from 'vs/platform/files/common/watcher'; export class UniversalWatcherClient extends AbstractUniversalWatcherClient { constructor( - onFileChanges: (changes: IDiskFileChange[]) => void, + onFileChanges: (changes: IFileChange[]) => void, onLogMessage: (msg: ILogMessage) => void, verboseLogging: boolean ) { diff --git a/code/src/vs/platform/files/test/browser/fileService.test.ts b/code/src/vs/platform/files/test/browser/fileService.test.ts index 5d0b16250ed..7c32e878b39 100644 --- a/code/src/vs/platform/files/test/browser/fileService.test.ts +++ b/code/src/vs/platform/files/test/browser/fileService.test.ts @@ -7,12 +7,13 @@ import * as assert from 'assert'; import { DeferredPromise, timeout } from 'vs/base/common/async'; import { bufferToReadable, bufferToStream, VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { isEqual } from 'vs/base/common/resources'; import { consumeStream, newWriteableStream, ReadableStreamEvents } from 'vs/base/common/stream'; import { URI } from 'vs/base/common/uri'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; -import { IFileOpenOptions, IFileReadStreamOptions, FileSystemProviderCapabilities, FileType, IFileSystemProviderCapabilitiesChangeEvent, IFileSystemProviderRegistrationEvent, IStat, IFileAtomicReadOptions, IFileAtomicWriteOptions, IFileAtomicDeleteOptions, IFileSystemProviderWithFileAtomicReadCapability, IFileSystemProviderWithFileAtomicDeleteCapability, IFileSystemProviderWithFileAtomicWriteCapability, IFileAtomicOptions } from 'vs/platform/files/common/files'; +import { IFileOpenOptions, IFileReadStreamOptions, FileSystemProviderCapabilities, FileType, IFileSystemProviderCapabilitiesChangeEvent, IFileSystemProviderRegistrationEvent, IStat, IFileAtomicReadOptions, IFileAtomicWriteOptions, IFileAtomicDeleteOptions, IFileSystemProviderWithFileAtomicReadCapability, IFileSystemProviderWithFileAtomicDeleteCapability, IFileSystemProviderWithFileAtomicWriteCapability, IFileAtomicOptions, IFileChange, isFileSystemWatcher, FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; import { NullFileSystemProvider } from 'vs/platform/files/test/common/nullFileSystemProvider'; import { NullLogService } from 'vs/platform/log/common/log'; @@ -142,6 +143,58 @@ suite('File Service', () => { service.dispose(); }); + test('watch - with corelation', async () => { + const service = disposables.add(new FileService(new NullLogService())); + + const provider = new class extends NullFileSystemProvider { + private readonly _testOnDidChangeFile = new Emitter(); + override readonly onDidChangeFile: Event = this._testOnDidChangeFile.event; + + fireFileChange(changes: readonly IFileChange[]) { + this._testOnDidChangeFile.fire(changes); + } + }; + + disposables.add(service.registerProvider('test', provider)); + await service.activateProvider('test'); + + const globalEvents: FileChangesEvent[] = []; + disposables.add(service.onDidFilesChange(e => { + globalEvents.push(e); + })); + + const watcher0 = disposables.add(service.watch(URI.parse('test://watch/folder1'), { recursive: true, excludes: [], includes: [] })); + assert.strictEqual(isFileSystemWatcher(watcher0), false); + const watcher1 = disposables.add(service.watch(URI.parse('test://watch/folder2'), { recursive: true, excludes: [], includes: [], correlationId: 100 })); + assert.strictEqual(isFileSystemWatcher(watcher1), true); + const watcher2 = disposables.add(service.watch(URI.parse('test://watch/folder3'), { recursive: true, excludes: [], includes: [], correlationId: 200 })); + assert.strictEqual(isFileSystemWatcher(watcher2), true); + + const watcher1Events: FileChangesEvent[] = []; + disposables.add(watcher1.onDidChange(e => { + watcher1Events.push(e); + })); + + const watcher2Events: FileChangesEvent[] = []; + disposables.add(watcher2.onDidChange(e => { + watcher2Events.push(e); + })); + + provider.fireFileChange([{ resource: URI.parse('test://watch/folder1'), type: FileChangeType.ADDED }]); + provider.fireFileChange([{ resource: URI.parse('test://watch/folder2'), type: FileChangeType.ADDED, cId: 100 }]); + provider.fireFileChange([{ resource: URI.parse('test://watch/folder2'), type: FileChangeType.ADDED, cId: 100 }]); + provider.fireFileChange([{ resource: URI.parse('test://watch/folder3/file'), type: FileChangeType.UPDATED, cId: 200 }]); + provider.fireFileChange([{ resource: URI.parse('test://watch/folder3'), type: FileChangeType.UPDATED, cId: 200 }]); + + provider.fireFileChange([{ resource: URI.parse('test://watch/folder4'), type: FileChangeType.ADDED, cId: 50 }]); + provider.fireFileChange([{ resource: URI.parse('test://watch/folder4'), type: FileChangeType.ADDED, cId: 60 }]); + provider.fireFileChange([{ resource: URI.parse('test://watch/folder4'), type: FileChangeType.ADDED, cId: 70 }]); + + assert.strictEqual(globalEvents.length, 1); + assert.strictEqual(watcher1Events.length, 2); + assert.strictEqual(watcher2Events.length, 2); + }); + test('error from readFile bubbles through (https://github.com/microsoft/vscode/issues/118060) - async', async () => { testReadErrorBubbles(true); }); diff --git a/code/src/vs/platform/files/test/common/files.test.ts b/code/src/vs/platform/files/test/common/files.test.ts index 2a6ae1a5a3a..1de7f86398a 100644 --- a/code/src/vs/platform/files/test/common/files.test.ts +++ b/code/src/vs/platform/files/test/common/files.test.ts @@ -8,7 +8,7 @@ import { isEqual, isEqualOrParent } from 'vs/base/common/extpath'; import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { ensureNoDisposablesAreLeakedInTestSuite, toResource } from 'vs/base/test/common/utils'; -import { FileChangesEvent, FileChangeType, isParent } from 'vs/platform/files/common/files'; +import { FileChangesEvent, FileChangeType, IFileChange, isParent } from 'vs/platform/files/common/files'; suite('Files', () => { @@ -109,6 +109,51 @@ suite('Files', () => { } }); + test('FileChangesEvent - correlation', function () { + let changes: IFileChange[] = [ + { resource: toResource.call(this, '/foo/updated.txt'), type: FileChangeType.UPDATED }, + { resource: toResource.call(this, '/foo/otherupdated.txt'), type: FileChangeType.UPDATED }, + { resource: toResource.call(this, '/added.txt'), type: FileChangeType.ADDED }, + ]; + + let event: FileChangesEvent = new FileChangesEvent(changes, true); + assert.strictEqual(event.hasCorrelation(), false); + assert.strictEqual(event.correlates(100), false); + + changes = [ + { resource: toResource.call(this, '/foo/updated.txt'), type: FileChangeType.UPDATED, cId: 100 }, + { resource: toResource.call(this, '/foo/otherupdated.txt'), type: FileChangeType.UPDATED, cId: 100 }, + { resource: toResource.call(this, '/added.txt'), type: FileChangeType.ADDED, cId: 100 }, + ]; + + event = new FileChangesEvent(changes, true); + assert.strictEqual(event.hasCorrelation(), true); + assert.strictEqual(event.correlates(100), true); + assert.strictEqual(event.correlates(120), false); + + changes = [ + { resource: toResource.call(this, '/foo/updated.txt'), type: FileChangeType.UPDATED, cId: 100 }, + { resource: toResource.call(this, '/foo/otherupdated.txt'), type: FileChangeType.UPDATED }, + { resource: toResource.call(this, '/added.txt'), type: FileChangeType.ADDED, cId: 100 }, + ]; + + event = new FileChangesEvent(changes, true); + assert.strictEqual(event.hasCorrelation(), false); + assert.strictEqual(event.correlates(100), false); + assert.strictEqual(event.correlates(120), false); + + changes = [ + { resource: toResource.call(this, '/foo/updated.txt'), type: FileChangeType.UPDATED, cId: 100 }, + { resource: toResource.call(this, '/foo/otherupdated.txt'), type: FileChangeType.UPDATED, cId: 120 }, + { resource: toResource.call(this, '/added.txt'), type: FileChangeType.ADDED, cId: 100 }, + ]; + + event = new FileChangesEvent(changes, true); + assert.strictEqual(event.hasCorrelation(), false); + assert.strictEqual(event.correlates(100), false); + assert.strictEqual(event.correlates(120), false); + }); + function testIsEqual(testMethod: (pA: string, pB: string, ignoreCase: boolean) => boolean): void { // corner cases diff --git a/code/src/vs/platform/files/test/common/watcher.test.ts b/code/src/vs/platform/files/test/common/watcher.test.ts index f1fa797119b..4fadddfc73f 100644 --- a/code/src/vs/platform/files/test/common/watcher.test.ts +++ b/code/src/vs/platform/files/test/common/watcher.test.ts @@ -11,7 +11,7 @@ import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { FileChangesEvent, FileChangeType, IFileChange } from 'vs/platform/files/common/files'; -import { IDiskFileChange, coalesceEvents, toFileChanges, parseWatcherPatterns } from 'vs/platform/files/common/watcher'; +import { coalesceEvents, reviveFileChanges, parseWatcherPatterns } from 'vs/platform/files/common/watcher'; class TestFileWatcher extends Disposable { private readonly _onDidFilesChange: Emitter<{ raw: IFileChange[]; event: FileChangesEvent }>; @@ -26,23 +26,23 @@ class TestFileWatcher extends Disposable { return this._onDidFilesChange.event; } - report(changes: IDiskFileChange[]): void { + report(changes: IFileChange[]): void { this.onRawFileEvents(changes); } - private onRawFileEvents(events: IDiskFileChange[]): void { + private onRawFileEvents(events: IFileChange[]): void { // Coalesce const coalescedEvents = coalesceEvents(events); // Emit through event emitter if (coalescedEvents.length > 0) { - this._onDidFilesChange.fire({ raw: toFileChanges(coalescedEvents), event: this.toFileChangesEvent(coalescedEvents) }); + this._onDidFilesChange.fire({ raw: reviveFileChanges(coalescedEvents), event: this.toFileChangesEvent(coalescedEvents) }); } } - private toFileChangesEvent(changes: IDiskFileChange[]): FileChangesEvent { - return new FileChangesEvent(toFileChanges(changes), !isLinux); + private toFileChangesEvent(changes: IFileChange[]): FileChangesEvent { + return new FileChangesEvent(reviveFileChanges(changes), !isLinux); } } @@ -126,7 +126,7 @@ suite('Watcher Events Normalizer', () => { const updated = URI.file('/users/data/src/updated.txt'); const deleted = URI.file('/users/data/src/deleted.txt'); - const raw: IDiskFileChange[] = [ + const raw: IFileChange[] = [ { resource: added, type: FileChangeType.ADDED }, { resource: updated, type: FileChangeType.UPDATED }, { resource: deleted, type: FileChangeType.DELETED }, @@ -159,7 +159,7 @@ suite('Watcher Events Normalizer', () => { const addedFile = URI.file(path === Path.UNIX ? '/users/data/src/added.txt' : path === Path.WINDOWS ? 'C:\\users\\data\\src\\added.txt' : '\\\\localhost\\users\\data\\src\\added.txt'); const updatedFile = URI.file(path === Path.UNIX ? '/users/data/src/updated.txt' : path === Path.WINDOWS ? 'C:\\users\\data\\src\\updated.txt' : '\\\\localhost\\users\\data\\src\\updated.txt'); - const raw: IDiskFileChange[] = [ + const raw: IFileChange[] = [ { resource: deletedFolderA, type: FileChangeType.DELETED }, { resource: deletedFolderB, type: FileChangeType.DELETED }, { resource: deletedFolderBF1, type: FileChangeType.DELETED }, @@ -194,7 +194,7 @@ suite('Watcher Events Normalizer', () => { const deleted = URI.file('/users/data/src/related'); const unrelated = URI.file('/users/data/src/unrelated'); - const raw: IDiskFileChange[] = [ + const raw: IFileChange[] = [ { resource: created, type: FileChangeType.ADDED }, { resource: deleted, type: FileChangeType.DELETED }, { resource: unrelated, type: FileChangeType.UPDATED }, @@ -219,7 +219,7 @@ suite('Watcher Events Normalizer', () => { const created = URI.file('/users/data/src/related'); const unrelated = URI.file('/users/data/src/unrelated'); - const raw: IDiskFileChange[] = [ + const raw: IFileChange[] = [ { resource: deleted, type: FileChangeType.DELETED }, { resource: created, type: FileChangeType.ADDED }, { resource: unrelated, type: FileChangeType.UPDATED }, @@ -245,7 +245,7 @@ suite('Watcher Events Normalizer', () => { const updated = URI.file('/users/data/src/related'); const unrelated = URI.file('/users/data/src/unrelated'); - const raw: IDiskFileChange[] = [ + const raw: IFileChange[] = [ { resource: created, type: FileChangeType.ADDED }, { resource: updated, type: FileChangeType.UPDATED }, { resource: unrelated, type: FileChangeType.UPDATED }, @@ -273,7 +273,7 @@ suite('Watcher Events Normalizer', () => { const deleted = URI.file('/users/data/src/related'); const unrelated = URI.file('/users/data/src/unrelated'); - const raw: IDiskFileChange[] = [ + const raw: IFileChange[] = [ { resource: updated, type: FileChangeType.UPDATED }, { resource: updated2, type: FileChangeType.UPDATED }, { resource: unrelated, type: FileChangeType.UPDATED }, @@ -300,7 +300,7 @@ suite('Watcher Events Normalizer', () => { const oldPath = URI.file('/users/data/src/added'); const newPath = URI.file('/users/data/src/ADDED'); - const raw: IDiskFileChange[] = [ + const raw: IFileChange[] = [ { resource: newPath, type: FileChangeType.ADDED }, { resource: oldPath, type: FileChangeType.DELETED } ]; diff --git a/code/src/vs/platform/files/test/node/nodejsWatcher.integrationTest.ts b/code/src/vs/platform/files/test/node/nodejsWatcher.integrationTest.ts index c7b5fc4e7f0..74dbb343c97 100644 --- a/code/src/vs/platform/files/test/node/nodejsWatcher.integrationTest.ts +++ b/code/src/vs/platform/files/test/node/nodejsWatcher.integrationTest.ts @@ -17,6 +17,9 @@ import { DeferredPromise } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { NodeJSWatcher } from 'vs/platform/files/node/watcher/nodejs/nodejsWatcher'; import { FileAccess } from 'vs/base/common/network'; +import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; +import { addUNCHostToAllowlist } from 'vs/base/node/unc'; // this suite has shown flaky runs in Azure pipelines where // tasks would just hang and timeout after a while (not in @@ -105,16 +108,22 @@ import { FileAccess } from 'vs/base/common/network'; } } - async function awaitEvent(service: TestNodeJSWatcher, path: string, type: FileChangeType): Promise { + async function awaitEvent(service: TestNodeJSWatcher, path: string, type: FileChangeType, correlationId?: number | null, expectedCount?: number): Promise { if (loggingEnabled) { console.log(`Awaiting change type '${toMsg(type)}' on file '${path}'`); } // Await the event await new Promise(resolve => { + let counter = 0; const disposable = service.onDidChangeFile(events => { for (const event of events) { - if (event.resource.fsPath === path && event.type === type) { + if (extUriBiasedIgnorePathCase.isEqual(event.resource, URI.file(path)) && event.type === type && (correlationId === null || event.cId === correlationId)) { + counter++; + if (typeof expectedCount === 'number' && counter < expectedCount) { + continue; // not yet + } + disposable.dispose(); resolve(); break; @@ -406,6 +415,13 @@ import { FileAccess } from 'vs/base/common/network'; return basicCrudTest(join(testDir, 'files-includes.txt')); }); + test('correlationId is supported', async function () { + const correlationId = Math.random(); + await watcher.watch([{ correlationId, path: testDir, excludes: [], recursive: false }]); + + return basicCrudTest(join(testDir, 'newFile.txt'), undefined, correlationId); + }); + (isWindows /* windows: cannot create file symbolic link without elevated context */ ? test.skip : test)('symlink support (folder watch)', async function () { const link = join(testDir, 'deep-linked'); const linkTarget = join(testDir, 'deep'); @@ -416,23 +432,23 @@ import { FileAccess } from 'vs/base/common/network'; return basicCrudTest(join(link, 'newFile.txt')); }); - async function basicCrudTest(filePath: string, skipAdd?: boolean): Promise { + async function basicCrudTest(filePath: string, skipAdd?: boolean, correlationId?: number | null, expectedCount?: number): Promise { let changeFuture: Promise; // New file if (!skipAdd) { - changeFuture = awaitEvent(watcher, filePath, FileChangeType.ADDED); + changeFuture = awaitEvent(watcher, filePath, FileChangeType.ADDED, correlationId, expectedCount); await Promises.writeFile(filePath, 'Hello World'); await changeFuture; } // Change file - changeFuture = awaitEvent(watcher, filePath, FileChangeType.UPDATED); + changeFuture = awaitEvent(watcher, filePath, FileChangeType.UPDATED, correlationId, expectedCount); await Promises.writeFile(filePath, 'Hello Change'); await changeFuture; // Delete file - changeFuture = awaitEvent(watcher, filePath, FileChangeType.DELETED); + changeFuture = awaitEvent(watcher, filePath, FileChangeType.DELETED, correlationId, expectedCount); await Promises.unlink(await Promises.realpath(filePath)); // support symlinks await changeFuture; } @@ -448,6 +464,7 @@ import { FileAccess } from 'vs/base/common/network'; }); (!isWindows /* UNC is windows only */ ? test.skip : test)('unc support (folder watch)', async function () { + addUNCHostToAllowlist('localhost'); // Local UNC paths are in the form of: \\localhost\c$\my_dir const uncPath = `\\\\localhost\\${getDriveLetter(testDir)?.toLowerCase()}$\\${ltrim(testDir.substr(testDir.indexOf(':') + 1), '\\')}`; @@ -458,6 +475,7 @@ import { FileAccess } from 'vs/base/common/network'; }); (!isWindows /* UNC is windows only */ ? test.skip : test)('unc support (file watch)', async function () { + addUNCHostToAllowlist('localhost'); // Local UNC paths are in the form of: \\localhost\c$\my_dir const uncPath = `\\\\localhost\\${getDriveLetter(testDir)?.toLowerCase()}$\\${ltrim(testDir.substr(testDir.indexOf(':') + 1), '\\')}\\lorem.txt`; @@ -528,4 +546,17 @@ import { FileAccess } from 'vs/base/common/network'; return watchPromise; }); + + test('watching same or overlapping paths supported when correlation is applied', async () => { + + // same path, same options + await watcher.watch([ + { path: testDir, excludes: [], recursive: false, correlationId: 1 }, + { path: testDir, excludes: [], recursive: false, correlationId: 2, }, + { path: testDir, excludes: [], recursive: false, correlationId: undefined } + ]); + + await basicCrudTest(join(testDir, 'newFile.txt'), undefined, null, 3); + await basicCrudTest(join(testDir, 'otherNewFile.txt'), undefined, null, 3); + }); }); diff --git a/code/src/vs/platform/files/test/node/parcelWatcher.integrationTest.ts b/code/src/vs/platform/files/test/node/parcelWatcher.integrationTest.ts index 67ad2ee3d44..42e8b4ad730 100644 --- a/code/src/vs/platform/files/test/node/parcelWatcher.integrationTest.ts +++ b/code/src/vs/platform/files/test/node/parcelWatcher.integrationTest.ts @@ -7,16 +7,19 @@ import * as assert from 'assert'; import { realpathSync } from 'fs'; import { tmpdir } from 'os'; import { timeout } from 'vs/base/common/async'; -import { join } from 'vs/base/common/path'; +import { dirname, join } from 'vs/base/common/path'; import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { Promises, RimRafMode } from 'vs/base/node/pfs'; import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils'; -import { FileChangeType } from 'vs/platform/files/common/files'; +import { FileChangeType, IFileChange } from 'vs/platform/files/common/files'; import { ParcelWatcher } from 'vs/platform/files/node/watcher/parcel/parcelWatcher'; -import { IDiskFileChange, IRecursiveWatchRequest } from 'vs/platform/files/common/watcher'; +import { IRecursiveWatchRequest } from 'vs/platform/files/common/watcher'; import { getDriveLetter } from 'vs/base/common/extpath'; import { ltrim } from 'vs/base/common/strings'; import { FileAccess } from 'vs/base/common/network'; +import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; +import { addUNCHostToAllowlist } from 'vs/base/node/unc'; // this suite has shown flaky runs in Azure pipelines where // tasks would just hang and timeout after a while (not in @@ -103,16 +106,22 @@ import { FileAccess } from 'vs/base/common/network'; } } - async function awaitEvent(service: TestParcelWatcher, path: string, type: FileChangeType, failOnEventReason?: string): Promise { + async function awaitEvent(watcher: TestParcelWatcher, path: string, type: FileChangeType, failOnEventReason?: string, correlationId?: number | null, expectedCount?: number): Promise { if (loggingEnabled) { console.log(`Awaiting change type '${toMsg(type)}' on file '${path}'`); } // Await the event - const res = await new Promise((resolve, reject) => { - const disposable = service.onDidChangeFile(events => { + const res = await new Promise((resolve, reject) => { + let counter = 0; + const disposable = watcher.onDidChangeFile(events => { for (const event of events) { - if (event.resource.fsPath === path && event.type === type) { + if (extUriBiasedIgnorePathCase.isEqual(event.resource, URI.file(path)) && event.type === type && (correlationId === null || event.cId === correlationId)) { + counter++; + if (typeof expectedCount === 'number' && counter < expectedCount) { + continue; // not yet + } + disposable.dispose(); if (failOnEventReason) { reject(new Error(`Unexpected file event: ${failOnEventReason}`)); @@ -134,14 +143,14 @@ import { FileAccess } from 'vs/base/common/network'; return res; } - function awaitMessage(service: TestParcelWatcher, type: 'trace' | 'warn' | 'error' | 'info' | 'debug'): Promise { + function awaitMessage(watcher: TestParcelWatcher, type: 'trace' | 'warn' | 'error' | 'info' | 'debug'): Promise { if (loggingEnabled) { console.log(`Awaiting message of type ${type}`); } // Await the message return new Promise(resolve => { - const disposable = service.onDidLogMessage(msg => { + const disposable = watcher.onDidLogMessage(msg => { if (msg.type === type) { disposable.dispose(); resolve(); @@ -287,20 +296,20 @@ import { FileAccess } from 'vs/base/common/network'; return basicCrudTest(join(testDir, 'deep', 'newFile.txt')); }); - async function basicCrudTest(filePath: string): Promise { + async function basicCrudTest(filePath: string, correlationId?: number | null, expectedCount?: number): Promise { // New file - let changeFuture = awaitEvent(watcher, filePath, FileChangeType.ADDED); + let changeFuture = awaitEvent(watcher, filePath, FileChangeType.ADDED, undefined, correlationId, expectedCount); await Promises.writeFile(filePath, 'Hello World'); await changeFuture; // Change file - changeFuture = awaitEvent(watcher, filePath, FileChangeType.UPDATED); + changeFuture = awaitEvent(watcher, filePath, FileChangeType.UPDATED, undefined, correlationId, expectedCount); await Promises.writeFile(filePath, 'Hello Change'); await changeFuture; // Delete file - changeFuture = awaitEvent(watcher, filePath, FileChangeType.DELETED); + changeFuture = awaitEvent(watcher, filePath, FileChangeType.DELETED, undefined, correlationId, expectedCount); await Promises.unlink(filePath); await changeFuture; } @@ -509,6 +518,7 @@ import { FileAccess } from 'vs/base/common/network'; }); (!isWindows /* UNC is windows only */ ? test.skip : test)('unc support', async function () { + addUNCHostToAllowlist('localhost'); // Local UNC paths are in the form of: \\localhost\c$\my_dir const uncPath = `\\\\localhost\\${getDriveLetter(testDir)?.toLowerCase()}$\\${ltrim(testDir.substr(testDir.indexOf(':') + 1), '\\')}`; @@ -532,7 +542,7 @@ import { FileAccess } from 'vs/base/common/network'; await watcher.watch([{ path: invalidPath, excludes: [], recursive: true }]); }); - test('deleting watched path is handled properly', async function () { + (isWindows /* flaky on windows */ ? test.skip : test)('deleting watched path is handled properly', async function () { const watchedPath = join(testDir, 'deep'); await watcher.watch([{ path: watchedPath, excludes: [], recursive: true }]); @@ -555,6 +565,13 @@ import { FileAccess } from 'vs/base/common/network'; await changeFuture; }); + test('correlationId is supported', async function () { + const correlationId = Math.random(); + await watcher.watch([{ correlationId, path: testDir, excludes: [], recursive: true }]); + + return basicCrudTest(join(testDir, 'newFile.txt'), correlationId); + }); + test('should not exclude roots that do not overlap', () => { if (isWindows) { assert.deepStrictEqual(watcher.testNormalizePaths(['C:\\a']), ['C:\\a']); @@ -584,4 +601,49 @@ import { FileAccess } from 'vs/base/common/network'; test('should ignore when everything excluded', () => { assert.deepStrictEqual(watcher.testNormalizePaths(['/foo/bar', '/bar'], ['**', 'something']), []); }); + + test('watching same or overlapping paths supported when correlation is applied', async () => { + + // same path, same options + await watcher.watch([ + { path: testDir, excludes: [], recursive: true, correlationId: 1 }, + { path: testDir, excludes: [], recursive: true, correlationId: 2, }, + { path: testDir, excludes: [], recursive: true, correlationId: undefined } + ]); + + await basicCrudTest(join(testDir, 'newFile.txt'), null, 3); + await basicCrudTest(join(testDir, 'otherNewFile.txt'), null, 3); + + // same path, different options + await watcher.watch([ + { path: testDir, excludes: [], recursive: true, correlationId: 1 }, + { path: testDir, excludes: [], recursive: true, correlationId: 2 }, + { path: testDir, excludes: [], recursive: true, correlationId: undefined }, + { path: testDir, excludes: [join(realpathSync(testDir), 'deep')], recursive: true, correlationId: 3 }, + { path: testDir, excludes: [join(realpathSync(testDir), 'other')], recursive: true, correlationId: 4 }, + ]); + + await basicCrudTest(join(testDir, 'newFile.txt'), null, 5); + await basicCrudTest(join(testDir, 'otherNewFile.txt'), null, 5); + + // overlapping paths (same options) + await watcher.watch([ + { path: dirname(testDir), excludes: [], recursive: true, correlationId: 1 }, + { path: testDir, excludes: [], recursive: true, correlationId: 2 }, + { path: join(testDir, 'deep'), excludes: [], recursive: true, correlationId: 3 }, + ]); + + await basicCrudTest(join(testDir, 'deep', 'newFile.txt'), null, 3); + await basicCrudTest(join(testDir, 'deep', 'otherNewFile.txt'), null, 3); + + // overlapping paths (different options) + await watcher.watch([ + { path: dirname(testDir), excludes: [], recursive: true, correlationId: 1 }, + { path: testDir, excludes: [join(realpathSync(testDir), 'some')], recursive: true, correlationId: 2 }, + { path: join(testDir, 'deep'), excludes: [join(realpathSync(testDir), 'other')], recursive: true, correlationId: 3 }, + ]); + + await basicCrudTest(join(testDir, 'deep', 'newFile.txt'), null, 3); + await basicCrudTest(join(testDir, 'deep', 'otherNewFile.txt'), null, 3); + }); }); diff --git a/code/src/vs/platform/native/common/native.ts b/code/src/vs/platform/native/common/native.ts index 539e8daf1d3..cf36862e04f 100644 --- a/code/src/vs/platform/native/common/native.ts +++ b/code/src/vs/platform/native/common/native.ts @@ -75,7 +75,7 @@ export interface ICommonNativeHostService { maximizeWindow(): Promise; unmaximizeWindow(): Promise; minimizeWindow(): Promise; - moveWindowTop(): Promise; + moveWindowTop(options?: { targetWindowId?: number }): Promise; /** * Only supported on Windows and macOS. Updates the window controls to match the title bar size. @@ -96,7 +96,7 @@ export interface ICommonNativeHostService { * should only be used if it is necessary to steal focus from the current * focused application which may not be VSCode. */ - focusWindow(options?: { windowId?: number; force?: boolean }): Promise; + focusWindow(options?: { targetWindowId?: number; force?: boolean }): Promise; // Dialogs showMessageBox(options: MessageBoxOptions): Promise; diff --git a/code/src/vs/platform/native/electron-main/nativeHostMainService.ts b/code/src/vs/platform/native/electron-main/nativeHostMainService.ts index ea1a1daf0ba..b25b5e92ecd 100644 --- a/code/src/vs/platform/native/electron-main/nativeHostMainService.ts +++ b/code/src/vs/platform/native/electron-main/nativeHostMainService.ts @@ -25,7 +25,7 @@ import { ISerializableCommandAction } from 'vs/platform/action/common/action'; import { INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs'; import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILifecycleMainService, IRelaunchOptions } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { ILogService } from 'vs/platform/log/common/log'; import { ICommonNativeHostService, IOSProperties, IOSStatistics } from 'vs/platform/native/common/native'; @@ -34,13 +34,15 @@ import { IPartsSplash } from 'vs/platform/theme/common/themeService'; import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { ICodeWindow } from 'vs/platform/window/electron-main/window'; import { IColorScheme, IOpenedWindow, IOpenEmptyWindowOptions, IOpenWindowOptions, IWindowOpenable } from 'vs/platform/window/common/window'; -import { IWindowsMainService, OpenContext } from 'vs/platform/windows/electron-main/windows'; +import { getFocusedOrLastActiveWindow, IWindowsMainService, OpenContext } from 'vs/platform/windows/electron-main/windows'; import { isWorkspaceIdentifier, toWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; import { VSBuffer } from 'vs/base/common/buffer'; import { hasWSLFeatureInstalled } from 'vs/platform/remote/node/wsl'; import { WindowProfiler } from 'vs/platform/profiling/electron-main/windowProfiling'; import { IV8Profile } from 'vs/platform/profiling/common/profiling'; +import { IAuxiliaryWindowsMainService } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows'; +import { IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow'; export interface INativeHostMainService extends AddFirstParameterToFunctions /* only methods, not events */, number | undefined /* window ID */> { } @@ -52,13 +54,15 @@ export class NativeHostMainService extends Disposable implements INativeHostMain constructor( @IWindowsMainService private readonly windowsMainService: IWindowsMainService, + @IAuxiliaryWindowsMainService private readonly auxiliaryWindowsMainService: IAuxiliaryWindowsMainService, @IDialogMainService private readonly dialogMainService: IDialogMainService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, @ILogService private readonly logService: ILogService, @IProductService private readonly productService: IProductService, @IThemeMainService private readonly themeMainService: IThemeMainService, - @IWorkspacesManagementMainService private readonly workspacesManagementMainService: IWorkspacesManagementMainService + @IWorkspacesManagementMainService private readonly workspacesManagementMainService: IWorkspacesManagementMainService, + @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); } @@ -174,17 +178,17 @@ export class NativeHostMainService extends Disposable implements INativeHostMain } async toggleFullScreen(windowId: number | undefined): Promise { - const window = this.windowById(windowId); + const window = this.codeWindowById(windowId); window?.toggleFullScreen(); } async handleTitleDoubleClick(windowId: number | undefined): Promise { - const window = this.windowById(windowId); + const window = this.codeWindowById(windowId); window?.handleTitleDoubleClick(); } async isMaximized(windowId: number | undefined): Promise { - const window = this.windowById(windowId); + const window = this.codeWindowById(windowId); if (window?.win) { return window.win.isMaximized(); } @@ -193,51 +197,47 @@ export class NativeHostMainService extends Disposable implements INativeHostMain } async maximizeWindow(windowId: number | undefined): Promise { - const window = this.windowById(windowId); + const window = this.codeWindowById(windowId); if (window?.win) { window.win.maximize(); } } async unmaximizeWindow(windowId: number | undefined): Promise { - const window = this.windowById(windowId); + const window = this.codeWindowById(windowId); if (window?.win) { window.win.unmaximize(); } } async minimizeWindow(windowId: number | undefined): Promise { - const window = this.windowById(windowId); + const window = this.codeWindowById(windowId); if (window?.win) { window.win.minimize(); } } - async moveWindowTop(windowId: number | undefined): Promise { - const window = this.windowById(windowId); + async moveWindowTop(windowId: number | undefined, options?: { targetWindowId?: number }): Promise { + const window = this.windowById(options?.targetWindowId) ?? this.codeWindowById(windowId); if (window?.win) { window.win.moveTop(); } } async updateWindowControls(windowId: number | undefined, options: { height?: number; backgroundColor?: string; foregroundColor?: string }): Promise { - const window = this.windowById(windowId); + const window = this.codeWindowById(windowId); if (window) { window.updateWindowControls(options); } } - async focusWindow(windowId: number | undefined, options?: { windowId?: number; force?: boolean }): Promise { - if (options && typeof options.windowId === 'number') { - windowId = options.windowId; - } - - const window = this.windowById(windowId); + async focusWindow(windowId: number | undefined, options?: { targetWindowId?: number; force?: boolean }): Promise { + const window = this.windowById(options?.targetWindowId) ?? this.codeWindowById(windowId); window?.focus({ force: options?.force ?? false }); } async setMinimumSize(windowId: number | undefined, width: number | undefined, height: number | undefined): Promise { - const window = this.windowById(windowId); + const window = this.codeWindowById(windowId); if (window?.win) { const [windowWidth, windowHeight] = window.win.getSize(); const [minWindowWidth, minWindowHeight] = window.win.getMinimumSize(); @@ -361,24 +361,21 @@ export class NativeHostMainService extends Disposable implements INativeHostMain //#region Dialog async showMessageBox(windowId: number | undefined, options: MessageBoxOptions): Promise { - return this.dialogMainService.showMessageBox(options, this.toBrowserWindow(windowId)); + const window = this.getTargetWindow(windowId); + + return this.dialogMainService.showMessageBox(options, window?.win ?? undefined); } async showSaveDialog(windowId: number | undefined, options: SaveDialogOptions): Promise { - return this.dialogMainService.showSaveDialog(options, this.toBrowserWindow(windowId)); - } + const window = this.getTargetWindow(windowId); - async showOpenDialog(windowId: number | undefined, options: OpenDialogOptions): Promise { - return this.dialogMainService.showOpenDialog(options, this.toBrowserWindow(windowId)); + return this.dialogMainService.showSaveDialog(options, window?.win ?? undefined); } - private toBrowserWindow(windowId: number | undefined): BrowserWindow | undefined { - const window = this.windowById(windowId); - if (window?.win) { - return window.win; - } + async showOpenDialog(windowId: number | undefined, options: OpenDialogOptions): Promise { + const window = this.getTargetWindow(windowId); - return undefined; + return this.dialogMainService.showOpenDialog(options, window?.win ?? undefined); } async pickFileFolderAndOpen(windowId: number | undefined, options: INativeOpenDialogOptions): Promise { @@ -430,12 +427,12 @@ export class NativeHostMainService extends Disposable implements INativeHostMain } async setRepresentedFilename(windowId: number | undefined, path: string): Promise { - const window = this.windowById(windowId); + const window = this.codeWindowById(windowId); window?.setRepresentedFilename(path); } async setDocumentEdited(windowId: number | undefined, edited: boolean): Promise { - const window = this.windowById(windowId); + const window = this.codeWindowById(windowId); window?.setDocumentEdited(edited); } @@ -643,7 +640,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain } async updateTouchBar(windowId: number | undefined, items: ISerializableCommandAction[][]): Promise { - const window = this.windowById(windowId); + const window = this.codeWindowById(windowId); window?.updateTouchBar(items); } @@ -653,7 +650,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain //#region Lifecycle async notifyReady(windowId: number | undefined): Promise { - const window = this.windowById(windowId); + const window = this.codeWindowById(windowId); window?.setReady(); } @@ -662,7 +659,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain } async reload(windowId: number | undefined, options?: { disableExtensions?: boolean }): Promise { - const window = this.windowById(windowId); + const window = this.codeWindowById(windowId); if (window) { // Special case: support `transient` workspaces by preventing @@ -689,11 +686,9 @@ export class NativeHostMainService extends Disposable implements INativeHostMain this.closeWindowById(windowId, windowId); } - async closeWindowById(currentWindowId: number | undefined, targetWindowId?: number | undefined): Promise { - const window = this.windowById(targetWindowId); - if (window?.win) { - return window.win.close(); - } + async closeWindowById(windowId: number | undefined, targetWindowId?: number | undefined): Promise { + const window = this.windowById(targetWindowId) ?? this.codeWindowById(targetWindowId); + return window?.win?.close(); } async quit(windowId: number | undefined): Promise { @@ -721,7 +716,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain //#region Connectivity async resolveProxy(windowId: number | undefined, url: string): Promise { - const window = this.windowById(windowId); + const window = this.codeWindowById(windowId); const session = window?.win?.webContents?.session; return session?.resolveProxy(url); @@ -737,28 +732,19 @@ export class NativeHostMainService extends Disposable implements INativeHostMain //#region Development async openDevTools(windowId: number | undefined, options?: OpenDevToolsOptions): Promise { - const window = this.windowById(windowId); - if (window?.win) { - window.win.webContents.openDevTools(options); - } + const window = this.getTargetWindow(windowId); + + window?.win?.webContents.openDevTools(options); } async toggleDevTools(windowId: number | undefined): Promise { - const window = this.windowById(windowId); - if (window?.win) { - const contents = window.win.webContents; - contents.toggleDevTools(); - } + const window = this.getTargetWindow(windowId); - for (const browserWindow of BrowserWindow.getAllWindows()) { - if (browserWindow.webContents.getURL() === 'about:blank') { - browserWindow.webContents.toggleDevTools(); - } - } + window?.win?.webContents.toggleDevTools(); } async sendInputEvent(windowId: number | undefined, event: MouseInputEvent): Promise { - const window = this.windowById(windowId); + const window = this.codeWindowById(windowId); if (window?.win && (event.type === 'mouseDown' || event.type === 'mouseUp')) { window.win.webContents.sendInputEvent(event); } @@ -769,11 +755,12 @@ export class NativeHostMainService extends Disposable implements INativeHostMain // #region Performance async profileRenderer(windowId: number | undefined, session: string, duration: number): Promise { - const win = this.windowById(windowId); - if (!win || !win.win) { + const window = this.codeWindowById(windowId); + if (!window || !window.win) { throw new Error(); } - const profiler = new WindowProfiler(win.win, session, this.logService); + + const profiler = new WindowProfiler(window.win, session, this.logService); const result = await profiler.inspect(duration); return result; } @@ -797,11 +784,32 @@ export class NativeHostMainService extends Disposable implements INativeHostMain //#endregion - private windowById(windowId: number | undefined): ICodeWindow | undefined { + private windowById(windowId: number | undefined): ICodeWindow | IAuxiliaryWindow | undefined { + return this.codeWindowById(windowId) ?? this.auxiliaryWindowById(windowId); + } + + private codeWindowById(windowId: number | undefined): ICodeWindow | undefined { if (typeof windowId !== 'number') { return undefined; } return this.windowsMainService.getWindowById(windowId); } + + private auxiliaryWindowById(windowId: number | undefined): IAuxiliaryWindow | undefined { + if (typeof windowId !== 'number') { + return undefined; + } + + return this.auxiliaryWindowsMainService.getWindowById(windowId); + } + + private getTargetWindow(fallbackWindowId: number | undefined): ICodeWindow | IAuxiliaryWindow | undefined { + let window = this.instantiationService.invokeFunction(getFocusedOrLastActiveWindow); + if (!window) { + window = this.windowById(fallbackWindowId); + } + + return window; + } } diff --git a/code/src/vs/platform/quickinput/browser/commandsQuickAccess.ts b/code/src/vs/platform/quickinput/browser/commandsQuickAccess.ts index aeb7c3878f5..87a211bcfbf 100644 --- a/code/src/vs/platform/quickinput/browser/commandsQuickAccess.ts +++ b/code/src/vs/platform/quickinput/browser/commandsQuickAccess.ts @@ -286,10 +286,10 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc // TF-IDF string to be indexed private getTfIdfChunk({ label, commandAlias, commandDescription }: ICommandQuickPick) { let chunk = label; - if (commandAlias) { + if (commandAlias && commandAlias !== label) { chunk += ` - ${commandAlias}`; } - if (commandDescription) { + if (commandDescription && commandDescription.value !== label) { // If the original is the same as the value, don't add it chunk += ` - ${commandDescription.value === commandDescription.original ? commandDescription.value : `${commandDescription.value} (${commandDescription.original})`}`; } diff --git a/code/src/vs/platform/quickinput/browser/quickInputController.ts b/code/src/vs/platform/quickinput/browser/quickInputController.ts index cd748881d21..8578dc5ce1e 100644 --- a/code/src/vs/platform/quickinput/browser/quickInputController.ts +++ b/code/src/vs/platform/quickinput/browser/quickInputController.ts @@ -52,7 +52,8 @@ export class QuickInputController extends Disposable { private previousFocusElement?: HTMLElement; constructor(private options: IQuickInputOptions, - private readonly themeService: IThemeService) { + private readonly themeService: IThemeService, + private readonly layoutService: ILayoutService) { super(); this.idPrefix = options.idPrefix; this.parentElement = options.container; @@ -72,6 +73,13 @@ export class QuickInputController extends Disposable { private getUI() { if (this.ui) { + // In order to support aux windows, re-parent the controller if the original event is + // from a different document + if (this.parentElement.ownerDocument !== this.layoutService.activeContainer.ownerDocument) { + this.parentElement = this.layoutService.activeContainer; + dom.append(this.parentElement, this.ui.container); + } + return this.ui; } diff --git a/code/src/vs/platform/quickinput/browser/quickInputService.ts b/code/src/vs/platform/quickinput/browser/quickInputService.ts index 924e6b83ef6..a5892ba0281 100644 --- a/code/src/vs/platform/quickinput/browser/quickInputService.ts +++ b/code/src/vs/platform/quickinput/browser/quickInputService.ts @@ -93,7 +93,8 @@ export class QuickInputService extends Themable implements IQuickInputService { ...defaultOptions, ...options }, - this.themeService)); + this.themeService, + this.layoutService)); controller.layout(host.dimension, host.offset.quickPickTop); diff --git a/code/src/vs/platform/quickinput/test/browser/quickinput.test.ts b/code/src/vs/platform/quickinput/test/browser/quickinput.test.ts index abab15421d7..8d54f3cd5af 100644 --- a/code/src/vs/platform/quickinput/test/browser/quickinput.test.ts +++ b/code/src/vs/platform/quickinput/test/browser/quickinput.test.ts @@ -84,7 +84,8 @@ suite('QuickInput', () => { // https://github.com/microsoft/vscode/issues/147543 } } }, - new TestThemeService())); + new TestThemeService(), + { activeContainer: fixture } as any)); // initial layout controller.layout({ height: 20, width: 40 }, 0); diff --git a/code/src/vs/platform/request/node/proxy.ts b/code/src/vs/platform/request/node/proxy.ts index 7bb334a252f..46b49d35f95 100644 --- a/code/src/vs/platform/request/node/proxy.ts +++ b/code/src/vs/platform/request/node/proxy.ts @@ -64,12 +64,12 @@ export async function getProxyAgent(rawRequestURL: string, env: typeof process.e const opts = { host: proxyEndpoint.hostname || '', - port: proxyEndpoint.port || (proxyEndpoint.protocol === 'https' ? '443' : '80'), + port: (proxyEndpoint.port ? +proxyEndpoint.port : 0) || (proxyEndpoint.protocol === 'https' ? 443 : 80), auth: proxyEndpoint.auth, rejectUnauthorized: isBoolean(options.strictSSL) ? options.strictSSL : true, }; return requestURL.protocol === 'http:' - ? new (await import('http-proxy-agent'))(opts as any as Url) - : new (await import('https-proxy-agent'))(opts); + ? new (await import('http-proxy-agent')).HttpProxyAgent(proxyURL, opts) + : new (await import('https-proxy-agent')).HttpsProxyAgent(proxyURL, opts); } diff --git a/code/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts b/code/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts index 70059e1d7fc..7372f366459 100644 --- a/code/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts +++ b/code/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts @@ -29,6 +29,7 @@ export class SharedProcess extends Disposable { constructor( private readonly machineId: string, + private readonly sqmId: string, @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @@ -172,6 +173,7 @@ export class SharedProcess extends Disposable { private createSharedProcessConfiguration(): ISharedProcessConfiguration { return { machineId: this.machineId, + sqmId: this.sqmId, codeCachePath: this.environmentMainService.codeCachePath, profiles: { home: this.userDataProfilesService.profilesHome, diff --git a/code/src/vs/platform/sharedProcess/node/sharedProcess.ts b/code/src/vs/platform/sharedProcess/node/sharedProcess.ts index efdde2ec7c5..f93082d7a2d 100644 --- a/code/src/vs/platform/sharedProcess/node/sharedProcess.ts +++ b/code/src/vs/platform/sharedProcess/node/sharedProcess.ts @@ -13,6 +13,8 @@ import { UriComponents, UriDto } from 'vs/base/common/uri'; export interface ISharedProcessConfiguration { readonly machineId: string; + readonly sqmId: string; + readonly codeCachePath: string | undefined; readonly args: NativeParsedArgs; diff --git a/code/src/vs/platform/telemetry/common/commonProperties.ts b/code/src/vs/platform/telemetry/common/commonProperties.ts index 7ee1e0b7705..587bf334463 100644 --- a/code/src/vs/platform/telemetry/common/commonProperties.ts +++ b/code/src/vs/platform/telemetry/common/commonProperties.ts @@ -23,6 +23,7 @@ export function resolveCommonProperties( commit: string | undefined, version: string | undefined, machineId: string | undefined, + sqmId: string | undefined, isInternalTelemetry: boolean, product?: string ): ICommonProperties { @@ -30,6 +31,8 @@ export function resolveCommonProperties( // __GDPR__COMMON__ "common.machineId" : { "endPoint": "MacAddressHash", "classification": "EndUserPseudonymizedInformation", "purpose": "FeatureInsight" } result['common.machineId'] = machineId; + // __GDPR__COMMON__ "common.sqmId" : { "endPoint": "SQMMachineId", "classification": "EndUserPseudonymizedInformation", "purpose": "BusinessInsight" } + result['common.sqmId'] = sqmId; // __GDPR__COMMON__ "sessionID" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } result['sessionID'] = generateUuid() + Date.now(); // __GDPR__COMMON__ "commitHash" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } diff --git a/code/src/vs/platform/telemetry/common/telemetry.ts b/code/src/vs/platform/telemetry/common/telemetry.ts index f19c01188ee..3c48ecfaf0d 100644 --- a/code/src/vs/platform/telemetry/common/telemetry.ts +++ b/code/src/vs/platform/telemetry/common/telemetry.ts @@ -71,6 +71,7 @@ export const currentSessionDateStorageKey = 'telemetry.currentSessionDate'; export const firstSessionDateStorageKey = 'telemetry.firstSessionDate'; export const lastSessionDateStorageKey = 'telemetry.lastSessionDate'; export const machineIdKey = 'telemetry.machineId'; +export const sqmIdKey = 'telemetry.sqmId'; // Configuration Keys export const TELEMETRY_SECTION_ID = 'telemetry'; diff --git a/code/src/vs/platform/telemetry/electron-main/telemetryUtils.ts b/code/src/vs/platform/telemetry/electron-main/telemetryUtils.ts index db3bb1bcd8b..6dc9a9fa9d6 100644 --- a/code/src/vs/platform/telemetry/electron-main/telemetryUtils.ts +++ b/code/src/vs/platform/telemetry/electron-main/telemetryUtils.ts @@ -5,12 +5,18 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IStateService } from 'vs/platform/state/node/state'; -import { machineIdKey } from 'vs/platform/telemetry/common/telemetry'; -import { resolveMachineId as resolveNodeMachineId } from 'vs/platform/telemetry/node/telemetryUtils'; +import { machineIdKey, sqmIdKey } from 'vs/platform/telemetry/common/telemetry'; +import { resolveMachineId as resolveNodeMachineId, resolveSqmId as resolveNodeSqmId } from 'vs/platform/telemetry/node/telemetryUtils'; -export async function resolveMachineId(stateService: IStateService, logService: ILogService) { +export async function resolveMachineId(stateService: IStateService, logService: ILogService): Promise { // Call the node layers implementation to avoid code duplication const machineId = await resolveNodeMachineId(stateService, logService); stateService.setItem(machineIdKey, machineId); return machineId; } + +export async function resolveSqmId(stateService: IStateService, logService: ILogService): Promise { + const sqmId = await resolveNodeSqmId(stateService, logService); + stateService.setItem(sqmIdKey, sqmId); + return sqmId; +} diff --git a/code/src/vs/platform/telemetry/node/telemetryUtils.ts b/code/src/vs/platform/telemetry/node/telemetryUtils.ts index 4e970ce6afa..cb5a03fd687 100644 --- a/code/src/vs/platform/telemetry/node/telemetryUtils.ts +++ b/code/src/vs/platform/telemetry/node/telemetryUtils.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { isMacintosh } from 'vs/base/common/platform'; -import { getMachineId } from 'vs/base/node/id'; +import { getMachineId, getSqmMachineId } from 'vs/base/node/id'; import { ILogService } from 'vs/platform/log/common/log'; import { IStateReadService } from 'vs/platform/state/node/state'; -import { machineIdKey } from 'vs/platform/telemetry/common/telemetry'; +import { machineIdKey, sqmIdKey } from 'vs/platform/telemetry/common/telemetry'; -export async function resolveMachineId(stateService: IStateReadService, logService: ILogService) { +export async function resolveMachineId(stateService: IStateReadService, logService: ILogService): Promise { // We cache the machineId for faster lookups // and resolve it only once initially if not cached or we need to replace the macOS iBridge device let machineId = stateService.getItem(machineIdKey); @@ -20,3 +20,12 @@ export async function resolveMachineId(stateService: IStateReadService, logServi return machineId; } + +export async function resolveSqmId(stateService: IStateReadService, logService: ILogService): Promise { + let sqmId = stateService.getItem(sqmIdKey); + if (typeof sqmId !== 'string') { + sqmId = await getSqmMachineId(logService.error.bind(logService)); + } + + return sqmId; +} diff --git a/code/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts b/code/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts index e066966a1a4..99f609e2c31 100644 --- a/code/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts +++ b/code/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { timeout } from 'vs/base/common/async'; +import { Barrier, timeout } from 'vs/base/common/async'; import { debounce } from 'vs/base/common/decorators'; import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -69,6 +69,8 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe private _dimensions: ITerminalDimensions; private __isCommandStorageDisabled: boolean = false; private _handleCommandStartOptions?: IHandleCommandOptions; + private _commandStartedWindowsBarrier?: Barrier; + private _windowsPromptPollingInProcess: boolean = false; get commands(): readonly ITerminalCommand[] { return this._commands; } get executingCommand(): string | undefined { return this._currentCommand.command; } @@ -361,35 +363,87 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe this._logService.debug('CommandDetectionCapability#handleCommandStart', this._currentCommand.commandStartX, this._currentCommand.commandStartMarker?.line); } - private _handleCommandStartWindows(): void { + private async _handleCommandStartWindows(): Promise { + if (this._windowsPromptPollingInProcess) { + this._windowsPromptPollingInProcess = false; + } + this._commandStartedWindowsBarrier = new Barrier(); this._currentCommand.commandStartX = this._terminal.buffer.active.cursorX; // On Windows track all cursor movements after the command start sequence this._commandMarkers.length = 0; - // HACK: Fire command started on the following frame on Windows to allow the cursor - // position to update as conpty often prints the sequence on a different line to the - // actual line the command started on. - timeout(0).then(() => { - if (!this._currentCommand.commandExecutedMarker) { - this._onCursorMoveListener = this._terminal.onCursorMove(() => { - if (this._commandMarkers.length === 0 || this._commandMarkers[this._commandMarkers.length - 1].line !== this._terminal.buffer.active.cursorY) { - const marker = this._terminal.registerMarker(0); - if (marker) { - this._commandMarkers.push(marker); - } + + let prompt: string | undefined = this._getWindowsPrompt(); + // Conpty could have the wrong cursor position at this point. + if (!this._cursorOnNextLine() || !prompt) { + this._windowsPromptPollingInProcess = true; + // Poll for 200ms until the cursor position is correct. + let i = 0; + for (; i < 20; i++) { + await timeout(10); + prompt = this._getWindowsPrompt(); + if (this._store.isDisposed || !this._windowsPromptPollingInProcess || this._cursorOnNextLine() && prompt) { + if (!this._windowsPromptPollingInProcess) { + this._logService.debug('CommandDetectionCapability#_handleCommandStartWindows polling cancelled'); } - }); + break; + } } - this._currentCommand.commandStartMarker = this._terminal.registerMarker(0); - if (this._currentCommand.commandStartMarker) { - const line = this._terminal.buffer.active.getLine(this._currentCommand.commandStartMarker.line); - if (line) { - this._currentCommand.commandStartLineContent = line.translateToString(true); + this._windowsPromptPollingInProcess = false; + if (i === 20) { + this._logService.debug('CommandDetectionCapability#_handleCommandStartWindows reached max attempts, ', this._cursorOnNextLine(), this._getWindowsPrompt()); + } else if (prompt) { + // use the regex to set the position as it's possible input has occurred + this._currentCommand.commandStartX = prompt.length; + } + } else { + // HACK: Fire command started on the following frame on Windows to allow the cursor + // position to update as conpty often prints the sequence on a different line to the + // actual line the command started on. + await timeout(0); + } + + if (!this._currentCommand.commandExecutedMarker) { + this._onCursorMoveListener = this._terminal.onCursorMove(() => { + if (this._commandMarkers.length === 0 || this._commandMarkers[this._commandMarkers.length - 1].line !== this._terminal.buffer.active.cursorY) { + const marker = this._terminal.registerMarker(0); + if (marker) { + this._commandMarkers.push(marker); + } } + }); + } + this._currentCommand.commandStartMarker = this._terminal.registerMarker(0); + if (this._currentCommand.commandStartMarker) { + const line = this._terminal.buffer.active.getLine(this._currentCommand.commandStartMarker.line); + if (line) { + this._currentCommand.commandStartLineContent = line.translateToString(true); } - this._onCommandStarted.fire({ marker: this._currentCommand.commandStartMarker } as ITerminalCommand); - this._logService.debug('CommandDetectionCapability#_handleCommandStartWindows', this._currentCommand.commandStartX, this._currentCommand.commandStartMarker?.line); - }); + } + this._onCommandStarted.fire({ marker: this._currentCommand.commandStartMarker } as ITerminalCommand); + this._logService.debug('CommandDetectionCapability#_handleCommandStartWindows', this._currentCommand.commandStartX, this._currentCommand.commandStartMarker?.line); + this._commandStartedWindowsBarrier.open(); + } + + private _cursorOnNextLine(): boolean { + const lastCommand = this.commands.at(-1); + + if (!lastCommand) { + return false; + } + const cursorYAbsolute = this._terminal.buffer.active.baseY + this._terminal.buffer.active.cursorY; + // If the cursor position is within the last command, we should poll. + const lastCommandYAbsolute = (lastCommand.endMarker ? lastCommand.endMarker.line : lastCommand.marker?.line) ?? -1; + return cursorYAbsolute > lastCommandYAbsolute; + } + + private _getWindowsPrompt(): string | undefined { + const line = this._terminal.buffer.active.getLine(this._terminal.buffer.active.baseY + this._terminal.buffer.active.cursorY); + if (!line) { + return; + } + // TODO: fine tune prompt regex to accomodate for unique configurtions. + return line.translateToString(true)?.match(/^(?(?:PS.+>\s)|(?:[A-Z]:\\.*>))/)?.groups?.prompt; } handleGenericCommand(options?: IHandleCommandOptions): void { @@ -438,7 +492,8 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe this._onCommandExecuted.fire(); } - private _handleCommandExecutedWindows(): void { + private async _handleCommandExecutedWindows(): Promise { + await this._commandStartedWindowsBarrier?.wait(); // On Windows, use the gathered cursor move markers to correct the command start and // executed markers this._onCursorMoveListener?.dispose(); @@ -513,7 +568,7 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe this._handleCommandStartOptions = undefined; } - private _preHandleCommandFinishedWindows(): void { + private async _preHandleCommandFinishedWindows(): Promise { if (this._currentCommand.commandExecutedMarker) { return; } diff --git a/code/src/vs/platform/userData/common/fileUserDataProvider.ts b/code/src/vs/platform/userData/common/fileUserDataProvider.ts index 3cddb5514a3..eaded2a0938 100644 --- a/code/src/vs/platform/userData/common/fileUserDataProvider.ts +++ b/code/src/vs/platform/userData/common/fileUserDataProvider.ts @@ -156,7 +156,8 @@ export class FileUserDataProvider extends Disposable implements if (this.watchResources.findSubstr(userDataResource)) { userDataChanges.push({ resource: userDataResource, - type: change.type + type: change.type, + cId: change.cId }); } } diff --git a/code/src/vs/platform/window/common/window.ts b/code/src/vs/platform/window/common/window.ts index 6cec55dc373..5762b3c380b 100644 --- a/code/src/vs/platform/window/common/window.ts +++ b/code/src/vs/platform/window/common/window.ts @@ -280,6 +280,7 @@ export interface INativeWindowConfiguration extends IWindowConfiguration, Native mainPid: number; machineId: string; + sqmId: string; execPath: string; backupPath?: string; diff --git a/code/src/vs/platform/window/electron-sandbox/window.ts b/code/src/vs/platform/window/electron-sandbox/window.ts index 90f1371bdee..b4c27b676f0 100644 --- a/code/src/vs/platform/window/electron-sandbox/window.ts +++ b/code/src/vs/platform/window/electron-sandbox/window.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { getZoomLevel, setZoomFactor, setZoomLevel } from 'vs/base/browser/browser'; -import { webFrame } from 'vs/base/parts/sandbox/electron-sandbox/globals'; +import { getWindows } from 'vs/base/browser/dom'; +import { getGlobals } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import { zoomLevelToZoomFactor } from 'vs/platform/window/common/window'; /** @@ -12,7 +13,9 @@ import { zoomLevelToZoomFactor } from 'vs/platform/window/common/window'; * browser helper so that it can be accessed in non-electron layers. */ export function applyZoom(zoomLevel: number): void { - webFrame.setZoomLevel(zoomLevel); + for (const window of getWindows()) { + getGlobals(window)?.webFrame?.setZoomLevel(zoomLevel); + } setZoomFactor(zoomLevelToZoomFactor(zoomLevel)); setZoomLevel(zoomLevel); } diff --git a/code/src/vs/platform/windows/electron-main/windowImpl.ts b/code/src/vs/platform/windows/electron-main/windowImpl.ts index ed55189686e..da1c92d03b6 100644 --- a/code/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/code/src/vs/platform/windows/electron-main/windowImpl.ts @@ -82,7 +82,29 @@ const enum ReadyState { READY } -export class CodeWindow extends Disposable implements ICodeWindow { +export abstract class BaseWindow extends Disposable { + + protected abstract getWin(): BrowserWindow | null; + + focus(options?: { force: boolean }): void { + if (isMacintosh && options?.force) { + app.focus({ steal: true }); + } + + const win = this.getWin(); + if (!win) { + return; + } + + if (win.isMinimized()) { + win.restore(); + } + + win.focus(); + } +} + +export class CodeWindow extends BaseWindow implements ICodeWindow { private static readonly windowControlHeightStateStorageKey = 'windowControlHeight'; @@ -114,6 +136,10 @@ export class CodeWindow extends Disposable implements ICodeWindow { private _win: BrowserWindow; get win(): BrowserWindow | null { return this._win; } + protected getWin(): BrowserWindow | null { + return this._win; + } + private _lastFocusTime = -1; get lastFocusTime(): number { return this._lastFocusTime; } @@ -418,29 +444,6 @@ export class CodeWindow extends Disposable implements ICodeWindow { return !!this.documentEdited; } - focus(options?: { force: boolean }): void { - // macOS: Electron > 7.x changed its behaviour to not - // bring the application to the foreground when a window - // is focused programmatically. Only via `app.focus` and - // the option `steal: true` can you get the previous - // behaviour back. The only reason to use this option is - // when a window is getting focused while the application - // is not in the foreground. - if (isMacintosh && options?.force) { - app.focus({ steal: true }); - } - - if (!this._win) { - return; - } - - if (this._win.isMinimized()) { - this._win.restore(); - } - - this._win.focus(); - } - private readyState = ReadyState.NONE; setReady(): void { diff --git a/code/src/vs/platform/windows/electron-main/windows.ts b/code/src/vs/platform/windows/electron-main/windows.ts index 41972e7c713..bc30770d0b4 100644 --- a/code/src/vs/platform/windows/electron-main/windows.ts +++ b/code/src/vs/platform/windows/electron-main/windows.ts @@ -16,6 +16,8 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { join } from 'vs/base/common/path'; +import { IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow'; +import { IAuxiliaryWindowsMainService } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows'; export const IWindowsMainService = createDecorator('windowsMainService'); @@ -157,3 +159,40 @@ export function defaultBrowserWindowOptions(accessor: ServicesAccessor, windowSt return options; } + +export function getFocusedOrLastActiveWindow(accessor: ServicesAccessor): ICodeWindow | IAuxiliaryWindow | undefined { + const windowsMainService = accessor.get(IWindowsMainService); + const auxiliaryWindowsMainService = accessor.get(IAuxiliaryWindowsMainService); + + // By: Electron focused window + const focusedWindow = windowsMainService.getFocusedWindow() ?? auxiliaryWindowsMainService.getFocusedWindow(); + if (focusedWindow) { + return focusedWindow; + } + + // By: Last active window + const mainLastActiveWindow = windowsMainService.getLastActiveWindow(); + const auxiliaryLastActiveWindow = auxiliaryWindowsMainService.getLastActiveWindow(); + + if (mainLastActiveWindow && auxiliaryLastActiveWindow) { + return mainLastActiveWindow.lastFocusTime < auxiliaryLastActiveWindow.lastFocusTime ? auxiliaryLastActiveWindow : mainLastActiveWindow; + } + + return mainLastActiveWindow ?? auxiliaryLastActiveWindow; +} + +export function getLastFocused(windows: ICodeWindow[]): ICodeWindow | undefined; +export function getLastFocused(windows: IAuxiliaryWindow[]): IAuxiliaryWindow | undefined; +export function getLastFocused(windows: ICodeWindow[] | IAuxiliaryWindow[]): ICodeWindow | IAuxiliaryWindow | undefined { + let lastFocusedWindow: ICodeWindow | IAuxiliaryWindow | undefined = undefined; + let maxLastFocusTime = Number.MIN_VALUE; + + for (const window of windows) { + if (window.lastFocusTime > maxLastFocusTime) { + maxLastFocusTime = window.lastFocusTime; + lastFocusedWindow = window; + } + } + + return lastFocusedWindow; +} diff --git a/code/src/vs/platform/windows/electron-main/windowsMainService.ts b/code/src/vs/platform/windows/electron-main/windowsMainService.ts index 2c44352ea05..89b2e26c645 100644 --- a/code/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/code/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -7,7 +7,7 @@ import { app, BrowserWindow, WebContents, shell } from 'electron'; import { Promises } from 'vs/base/node/pfs'; import { addUNCHostToAllowlist } from 'vs/base/node/unc'; import { hostname, release, arch } from 'os'; -import { coalesce, distinct, firstOrDefault } from 'vs/base/common/arrays'; +import { coalesce, distinct } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { CharCode } from 'vs/base/common/charCode'; import { Emitter, Event } from 'vs/base/common/event'; @@ -39,7 +39,7 @@ import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; import { IStateService } from 'vs/platform/state/node/state'; import { IAddFoldersRequest, INativeOpenFileRequest, INativeWindowConfiguration, IOpenEmptyWindowOptions, IPath, IPathsToWaitFor, isFileToOpen, isFolderToOpen, isWorkspaceToOpen, IWindowOpenable, IWindowSettings } from 'vs/platform/window/common/window'; import { CodeWindow } from 'vs/platform/windows/electron-main/windowImpl'; -import { IOpenConfiguration, IOpenEmptyConfiguration, IWindowsCountChangedEvent, IWindowsMainService, OpenContext } from 'vs/platform/windows/electron-main/windows'; +import { IOpenConfiguration, IOpenEmptyConfiguration, IWindowsCountChangedEvent, IWindowsMainService, OpenContext, getLastFocused } from 'vs/platform/windows/electron-main/windows'; import { findWindowOnExtensionDevelopmentPath, findWindowOnFile, findWindowOnWorkspaceOrFolder } from 'vs/platform/windows/electron-main/windowsFinder'; import { IWindowState, WindowsStateHandler } from 'vs/platform/windows/electron-main/windowsStateHandler'; import { IRecent } from 'vs/platform/workspaces/common/workspaces'; @@ -178,8 +178,6 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic declare readonly _serviceBrand: undefined; - private static readonly WINDOWS: ICodeWindow[] = []; - private readonly _onDidOpenWindow = this._register(new Emitter()); readonly onDidOpenWindow = this._onDidOpenWindow.event; @@ -195,10 +193,13 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic private readonly _onDidTriggerSystemContextMenu = this._register(new Emitter<{ window: ICodeWindow; x: number; y: number }>()); readonly onDidTriggerSystemContextMenu = this._onDidTriggerSystemContextMenu.event; + private readonly windows = new Map(); + private readonly windowsStateHandler = this._register(new WindowsStateHandler(this, this.stateService, this.lifecycleMainService, this.logService, this.configurationService)); constructor( private readonly machineId: string, + private readonly sqmId: string, private readonly initialUserEnv: IProcessEnvironment, @ILogService private readonly logService: ILogService, @ILoggerMainService private readonly loggerService: ILoggerMainService, @@ -1129,7 +1130,13 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic addUNCHostToAllowlist(uri.authority); if (checkboxChecked) { - this.sendToOpeningWindow('vscode:configureAllowedUNCHost', uri.authority); + // Due to https://github.com/microsoft/vscode/issues/195436, we can only + // update settings from within a window. But we do not know if a window + // is about to open or can already handle the request, so we have to send + // to any current window and any newly opening window. + const request = { channel: 'vscode:configureAllowedUNCHost', args: uri.authority }; + this.sendToFocused(request.channel, request.args); + this.sendToOpeningWindow(request.channel, request.args); } return this.doResolveFilePath(path, options, true /* do not handle UNC error again */); @@ -1375,6 +1382,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic ...options.cli, machineId: this.machineId, + sqmId: this.sqmId, windowId: -1, // Will be filled in by the window once loaded later @@ -1452,7 +1460,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic } // Add to our list of windows - WindowsMainService.WINDOWS.push(createdWindow); + this.windows.set(createdWindow.id, createdWindow); // Indicate new window via event this._onDidOpenWindow.fire(createdWindow); @@ -1585,8 +1593,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic private onWindowClosed(window: ICodeWindow, disposables: IDisposable): void { // Remove from our list so that Electron can clean it up - const index = WindowsMainService.WINDOWS.indexOf(window); - WindowsMainService.WINDOWS.splice(index, 1); + this.windows.delete(window.id); // Emit this._onDidChangeWindowsCount.fire({ oldCount: this.getWindowCount() + 1, newCount: this.getWindowCount() }); @@ -1613,9 +1620,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic } private doGetLastActiveWindow(windows: ICodeWindow[]): ICodeWindow | undefined { - const lastFocusedDate = Math.max.apply(Math, windows.map(window => window.lastFocusTime)); - - return windows.find(window => window.lastFocusTime === lastFocusedDate); + return getLastFocused(windows); } sendToFocused(channel: string, ...args: any[]): void { @@ -1641,17 +1646,15 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic } getWindows(): ICodeWindow[] { - return WindowsMainService.WINDOWS; + return Array.from(this.windows.values()); } getWindowCount(): number { - return WindowsMainService.WINDOWS.length; + return this.windows.size; } getWindowById(windowId: number): ICodeWindow | undefined { - const windows = this.getWindows().filter(window => window.id === windowId); - - return firstOrDefault(windows); + return this.windows.get(windowId); } getWindowByWebContents(webContents: WebContents): ICodeWindow | undefined { diff --git a/code/src/vs/server/node/serverServices.ts b/code/src/vs/server/node/serverServices.ts index 9db61e83f34..019b7d3768a 100644 --- a/code/src/vs/server/node/serverServices.ts +++ b/code/src/vs/server/node/serverServices.ts @@ -9,7 +9,7 @@ import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import * as path from 'vs/base/common/path'; import { IURITransformer } from 'vs/base/common/uriIpc'; -import { getMachineId } from 'vs/base/node/id'; +import { getMachineId, getSqmMachineId } from 'vs/base/node/id'; import { Promises } from 'vs/base/node/pfs'; import { ClientConnectionEvent, IMessagePassingProtocol, IPCServer, StaticRouter } from 'vs/base/parts/ipc/common/ipc'; import { ProtocolConstants } from 'vs/base/parts/ipc/common/ipc.net'; @@ -132,10 +132,11 @@ export async function setupServerServices(connectionToken: ServerConnectionToken socketServer.registerChannel('userDataProfiles', new RemoteUserDataProfilesServiceChannel(userDataProfilesService, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority))); // Initialize - const [, , machineId] = await Promise.all([ + const [, , machineId, sqmId] = await Promise.all([ configurationService.initialize(), userDataProfilesService.init(), - getMachineId(logService.error.bind(logService)) + getMachineId(logService.error.bind(logService)), + getSqmMachineId(logService.error.bind(logService)) ]); const extensionHostStatusService = new ExtensionHostStatusService(); @@ -155,7 +156,7 @@ export async function setupServerServices(connectionToken: ServerConnectionToken const config: ITelemetryServiceConfig = { appenders: [oneDsAppender], - commonProperties: resolveCommonProperties(release(), hostname(), process.arch, productService.commit, productService.version + '-remote', machineId, isInternal, 'remoteAgent'), + commonProperties: resolveCommonProperties(release(), hostname(), process.arch, productService.commit, productService.version + '-remote', machineId, sqmId, isInternal, 'remoteAgent'), piiPaths: getPiiPathsFromEnvironment(environmentService) }; const initialTelemetryLevelArg = environmentService.args['telemetry-level']; diff --git a/code/src/vs/workbench/api/browser/extensionHost.contribution.ts b/code/src/vs/workbench/api/browser/extensionHost.contribution.ts index 4f886cbde2c..b34ef1570cc 100644 --- a/code/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/code/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -21,6 +21,7 @@ import './mainThreadLocalization'; import './mainThreadBulkEdits'; import './mainThreadChatProvider'; import './mainThreadChatAgents'; +import './mainThreadChatAgents2'; import './mainThreadChatVariables'; import './mainThreadCodeInsets'; import './mainThreadCLICommands'; @@ -53,6 +54,7 @@ import './mainThreadQuickDiff'; import './mainThreadQuickOpen'; import './mainThreadRemoteConnectionData'; import './mainThreadSaveParticipant'; +import './mainThreadSpeech'; import './mainThreadEditSessionIdentityParticipant'; import './mainThreadSCM'; import './mainThreadSearch'; diff --git a/code/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/code/src/vs/workbench/api/browser/mainThreadAuthentication.ts index 0a7452bbe27..ec4ae3964cb 100644 --- a/code/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/code/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -243,8 +243,10 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu } let session; - if (sessions?.length && !options.forceNewSession && supportsMultipleAccounts) { - session = await this.authenticationService.selectSession(providerId, extensionId, extensionName, scopes, sessions); + if (sessions?.length && !options.forceNewSession) { + session = supportsMultipleAccounts + ? await this.authenticationService.selectSession(providerId, extensionId, extensionName, scopes, sessions) + : sessions[0]; } else { let sessionToRecreate: AuthenticationSession | undefined; if (typeof options.forceNewSession === 'object' && options.forceNewSession.sessionToRecreate) { diff --git a/code/src/vs/workbench/api/browser/mainThreadChat.ts b/code/src/vs/workbench/api/browser/mainThreadChat.ts index 90116dd2a46..a68c2d1c83d 100644 --- a/code/src/vs/workbench/api/browser/mainThreadChat.ts +++ b/code/src/vs/workbench/api/browser/mainThreadChat.ts @@ -9,7 +9,7 @@ import { IMarkdownString } from 'vs/base/common/htmlContent'; import { Disposable, DisposableMap } from 'vs/base/common/lifecycle'; import { revive } from 'vs/base/common/marshalling'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { ExtHostChatShape, ExtHostContext, IChatRequestDto, IChatResponseProgressDto, MainContext, MainThreadChatShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostChatShape, ExtHostContext, IChatRequestDto, IChatResponseProgressDto, ILocationDto, MainContext, MainThreadChatShape } from 'vs/workbench/api/common/extHost.protocol'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; import { isCompleteInteractiveProgressTreeData } from 'vs/workbench/contrib/chat/common/chatModel'; @@ -38,7 +38,9 @@ export class MainThreadChat extends Disposable implements MainThreadChatShape { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChat); this._register(this._chatService.onDidPerformUserAction(e => { - this._proxy.$onDidPerformUserAction(e); + if (!e.agentId) { + this._proxy.$onDidPerformUserAction(e); + } })); } @@ -109,6 +111,9 @@ export class MainThreadChat extends Disposable implements MainThreadChatShape { provideWelcomeMessage: (token) => { return this._proxy.$provideWelcomeMessage(handle, token); }, + provideSampleQuestions: (token) => { + return this._proxy.$provideSampleQuestions(handle, token); + }, provideSlashCommands: (session, token) => { return this._proxy.$provideSlashCommands(handle, session.id, token); }, @@ -152,19 +157,19 @@ export class MainThreadChat extends Disposable implements MainThreadChatShape { return; } + // TS won't let us change the type of `progress` + let revivedProgress: IChatProgress; if ('documents' in progress) { - const usedContext = { - documents: progress.documents.map(({ uri, version, ranges }) => ({ - uri: URI.revive(uri), - version, - ranges, - })), - }; - this._activeRequestProgressCallbacks.get(id)?.(usedContext); // FIXME@ulugbekna: is this a correct thing to do? - return; + revivedProgress = { documents: revive(progress.documents) }; + } else if ('reference' in progress) { + revivedProgress = revive<{ reference: UriComponents | ILocationDto }>(progress); + } else if ('inlineReference' in progress) { + revivedProgress = revive<{ inlineReference: UriComponents | ILocationDto; name?: string }>(progress); + } else { + revivedProgress = progress; } - this._activeRequestProgressCallbacks.get(id)?.(progress); + this._activeRequestProgressCallbacks.get(id)?.(revivedProgress); } async $acceptChatState(sessionId: number, state: any): Promise { diff --git a/code/src/vs/workbench/api/browser/mainThreadChatAgents.ts b/code/src/vs/workbench/api/browser/mainThreadChatAgents.ts index 52b8106cba8..a5d719829f5 100644 --- a/code/src/vs/workbench/api/browser/mainThreadChatAgents.ts +++ b/code/src/vs/workbench/api/browser/mainThreadChatAgents.ts @@ -7,7 +7,8 @@ import { DisposableMap } from 'vs/base/common/lifecycle'; import { revive } from 'vs/base/common/marshalling'; import { IProgress } from 'vs/platform/progress/common/progress'; import { ExtHostChatAgentsShape, ExtHostContext, MainContext, MainThreadChatAgentsShape } from 'vs/workbench/api/common/extHost.protocol'; -import { IChatAgentMetadata, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatAgentCommand, IChatAgentMetadata, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatSlashFragment } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; @@ -16,7 +17,7 @@ import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/ext export class MainThreadChatAgents implements MainThreadChatAgentsShape { private readonly _agents = new DisposableMap; - private readonly _pendingProgress = new Map>(); + private readonly _pendingProgress = new Map>(); private readonly _proxy: ExtHostChatAgentsShape; constructor( @@ -34,29 +35,38 @@ export class MainThreadChatAgents implements MainThreadChatAgentsShape { this._agents.clearAndDisposeAll(); } - $registerAgent(handle: number, name: string, metadata: IChatAgentMetadata): void { - if (!this._chatAgentService.hasAgent(name)) { - // dynamic! - this._chatAgentService.registerAgentData({ - id: name, - metadata: revive(metadata) - }); - } - - const d = this._chatAgentService.registerAgentCallback(name, async (prompt, progress, history, token) => { - const requestId = Math.random(); - this._pendingProgress.set(requestId, progress); - try { - return await this._proxy.$invokeAgent(handle, requestId, prompt, { history }, token); - } finally { - this._pendingProgress.delete(requestId); - } + $registerAgent(handle: number, name: string, metadata: IChatAgentMetadata & { subCommands: IChatAgentCommand[] }): void { + const d = this._chatAgentService.registerAgent({ + id: name, + metadata: revive(metadata), + invoke: async (request, progress, history, token) => { + const requestId = Math.random(); + this._pendingProgress.set(requestId, progress); + try { + const message = request.command ? `/${request.command} ${request.message}` : request.message; + const result = await this._proxy.$invokeAgent(handle, requestId, message, { history }, token); + return { + followUp: result?.followUp ?? [], + }; + } finally { + this._pendingProgress.delete(requestId); + } + }, + async provideSlashCommands() { + return metadata.subCommands; + }, }); this._agents.set(handle, d); } async $handleProgressChunk(requestId: number, chunk: IChatSlashFragment): Promise { - this._pendingProgress.get(requestId)?.report(revive(chunk)); + // An extra step because TS really struggles with type inference in the Revived generic parameter? + const revived = revive(chunk); + if (typeof revived.content === 'string') { + this._pendingProgress.get(requestId)?.report({ content: revived.content }); + } else { + this._pendingProgress.get(requestId)?.report(revived.content); + } } $unregisterCommand(handle: number): void { diff --git a/code/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/code/src/vs/workbench/api/browser/mainThreadChatAgents2.ts new file mode 100644 index 00000000000..438583607e9 --- /dev/null +++ b/code/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -0,0 +1,102 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable, DisposableMap } from 'vs/base/common/lifecycle'; +import { revive } from 'vs/base/common/marshalling'; +import { IProgress } from 'vs/platform/progress/common/progress'; +import { ExtHostChatAgentsShape2, ExtHostContext, IChatResponseProgressDto, IExtensionChatAgentMetadata, MainContext, MainThreadChatAgentsShape2 } from 'vs/workbench/api/common/extHost.protocol'; +import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatFollowup, IChatProgress, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; + +type AgentData = { + dispose: () => void; + name: string; + hasSlashCommands?: boolean; + hasFollowups?: boolean; +}; + +@extHostNamedCustomer(MainContext.MainThreadChatAgents2) +export class MainThreadChatAgents2 extends Disposable implements MainThreadChatAgentsShape2 { + + private readonly _agents = this._register(new DisposableMap()); + private readonly _pendingProgress = new Map>(); + private readonly _proxy: ExtHostChatAgentsShape2; + + constructor( + extHostContext: IExtHostContext, + @IChatAgentService private readonly _chatAgentService: IChatAgentService, + @IChatService private readonly _chatService: IChatService, + ) { + super(); + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatAgents2); + + this._register(this._chatService.onDidDisposeSession(e => { + this._proxy.$releaseSession(e.sessionId); + })); + this._register(this._chatService.onDidPerformUserAction(e => { + if (e.agentId) { + for (const [handle, agent] of this._agents) { + if (agent.name === e.agentId) { + if (e.action.kind === 'vote') { + this._proxy.$acceptFeedback(handle, e.sessionId, e.action.direction); + } else { + this._proxy.$acceptAction(handle, e.sessionId, e); + } + break; + } + } + } + })); + } + + $unregisterAgent(handle: number): void { + this._agents.deleteAndDispose(handle); + } + + $registerAgent(handle: number, name: string, metadata: IExtensionChatAgentMetadata): void { + const d = this._chatAgentService.registerAgent({ + id: name, + metadata: revive(metadata), + invoke: async (request, progress, history, token) => { + const requestId = Math.random(); // Make this a guid + this._pendingProgress.set(requestId, progress); + try { + return await this._proxy.$invokeAgent(handle, request.sessionId, requestId, request, { history }, token) ?? {}; + } finally { + this._pendingProgress.delete(requestId); + } + }, + provideFollowups: async (sessionId, token): Promise => { + if (!this._agents.get(handle)?.hasSlashCommands) { + return []; + } + + return this._proxy.$provideFollowups(handle, sessionId, token); + }, + provideSlashCommands: async (token) => { + if (!this._agents.get(handle)?.hasSlashCommands) { + return []; // save an IPC call + } + return this._proxy.$provideSlashCommands(handle, token); + } + }); + this._agents.set(handle, { name, dispose: d.dispose, hasSlashCommands: metadata.hasSlashCommands }); + } + + $updateAgent(handle: number, metadataUpdate: IExtensionChatAgentMetadata): void { + const data = this._agents.get(handle); + if (!data) { + throw new Error(`No agent with handle ${handle} registered`); + } + data.hasSlashCommands = metadataUpdate.hasSlashCommands; + this._chatAgentService.updateAgent(data.name, revive(metadataUpdate)); + } + + async $handleProgressChunk(requestId: number, chunk: IChatResponseProgressDto): Promise { + // TODO copy/move $acceptResponseProgress from MainThreadChat + this._pendingProgress.get(requestId)?.report(revive(chunk) as any); + } +} diff --git a/code/src/vs/workbench/api/browser/mainThreadComments.ts b/code/src/vs/workbench/api/browser/mainThreadComments.ts index 68455a45e42..40f5cd9341b 100644 --- a/code/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/code/src/vs/workbench/api/browser/mainThreadComments.ts @@ -15,7 +15,7 @@ import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/ext import { ICommentController, ICommentInfo, ICommentService, INotebookCommentInfo } from 'vs/workbench/contrib/comments/browser/commentService'; import { CommentsPanel } from 'vs/workbench/contrib/comments/browser/commentsView'; import { CommentProviderFeatures, ExtHostCommentsShape, ExtHostContext, MainContext, MainThreadCommentsShape, CommentThreadChanges } from '../common/extHost.protocol'; -import { COMMENTS_VIEW_ID, COMMENTS_VIEW_STORAGE_ID, COMMENTS_VIEW_TITLE, COMMENTS_VIEW_ORIGINAL_TITLE } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer'; +import { COMMENTS_VIEW_ID, COMMENTS_VIEW_STORAGE_ID, COMMENTS_VIEW_TITLE } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer'; import { ViewContainer, IViewContainersRegistry, Extensions as ViewExtensions, ViewContainerLocation, IViewsRegistry, IViewsService, IViewDescriptorService } from 'vs/workbench/common/views'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; @@ -26,7 +26,6 @@ import { MarshalledId } from 'vs/base/common/marshallingIds'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { Schemas } from 'vs/base/common/network'; - export class MainThreadCommentThread implements languages.CommentThread { private _input?: languages.CommentInput; get input(): languages.CommentInput | undefined { @@ -451,8 +450,8 @@ export class MainThreadCommentController implements ICommentController { return ret; } - createCommentThreadTemplate(resource: UriComponents, range: IRange | undefined): void { - this._proxy.$createCommentThreadTemplate(this.handle, resource, range); + createCommentThreadTemplate(resource: UriComponents, range: IRange | undefined): Promise { + return this._proxy.$createCommentThreadTemplate(this.handle, resource, range); } async updateCommentThreadTemplate(threadHandle: number, range: IRange) { @@ -601,7 +600,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments if (!commentsViewAlreadyRegistered) { const VIEW_CONTAINER: ViewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ id: COMMENTS_VIEW_ID, - title: { value: COMMENTS_VIEW_TITLE, original: COMMENTS_VIEW_ORIGINAL_TITLE }, + title: COMMENTS_VIEW_TITLE, ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [COMMENTS_VIEW_ID, { mergeViewWithContainerWhenSingleView: true }]), storageId: COMMENTS_VIEW_STORAGE_ID, hideIfEmpty: true, diff --git a/code/src/vs/workbench/api/browser/mainThreadFileSystem.ts b/code/src/vs/workbench/api/browser/mainThreadFileSystem.ts index c7a5c6cc661..e0f9359f0e9 100644 --- a/code/src/vs/workbench/api/browser/mainThreadFileSystem.ts +++ b/code/src/vs/workbench/api/browser/mainThreadFileSystem.ts @@ -6,17 +6,10 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IDisposable, toDisposable, DisposableStore, DisposableMap } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { IFileWriteOptions, FileSystemProviderCapabilities, IFileChange, IFileService, IStat, IWatchOptions, FileType, IFileOverwriteOptions, IFileDeleteOptions, IFileOpenOptions, FileOperationError, FileOperationResult, FileSystemProviderErrorCode, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithFileFolderCopyCapability, FilePermission, toFileSystemProviderErrorCode, IFilesConfiguration, IFileStatWithPartialMetadata, IFileStat } from 'vs/platform/files/common/files'; +import { IFileWriteOptions, FileSystemProviderCapabilities, IFileChange, IFileService, IStat, IWatchOptions, FileType, IFileOverwriteOptions, IFileDeleteOptions, IFileOpenOptions, FileOperationError, FileOperationResult, FileSystemProviderErrorCode, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithFileFolderCopyCapability, FilePermission, toFileSystemProviderErrorCode, IFileStatWithPartialMetadata, IFileStat } from 'vs/platform/files/common/files'; import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; import { ExtHostContext, ExtHostFileSystemShape, IFileChangeDto, MainContext, MainThreadFileSystemShape } from '../common/extHost.protocol'; import { VSBuffer } from 'vs/base/common/buffer'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { ILogService } from 'vs/platform/log/common/log'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IWorkbenchFileService } from 'vs/workbench/services/files/common/files'; -import { normalizeWatcherPattern } from 'vs/platform/files/common/watcher'; -import { GLOBSTAR } from 'vs/base/common/glob'; -import { rtrim } from 'vs/base/common/strings'; import { IMarkdownString } from 'vs/base/common/htmlContent'; @extHostNamedCustomer(MainContext.MainThreadFileSystem) @@ -25,14 +18,10 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape { private readonly _proxy: ExtHostFileSystemShape; private readonly _fileProvider = new DisposableMap(); private readonly _disposables = new DisposableStore(); - private readonly _watches = new DisposableMap(); constructor( extHostContext: IExtHostContext, - @IWorkbenchFileService private readonly _fileService: IWorkbenchFileService, - @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, - @ILogService private readonly _logService: ILogService, - @IConfigurationService private readonly _configurationService: IConfigurationService + @IFileService private readonly _fileService: IFileService ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostFileSystem); @@ -48,7 +37,6 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape { dispose(): void { this._disposables.dispose(); this._fileProvider.dispose(); - this._watches.dispose(); } async $registerFileSystemProvider(handle: number, scheme: string, capabilities: FileSystemProviderCapabilities, readonlyMessage?: IMarkdownString): Promise { @@ -165,99 +153,7 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape { return this._fileService.activateProvider(scheme); } - async $watch(extensionId: string, session: number, resource: UriComponents, unvalidatedOpts: IWatchOptions): Promise { - const uri = URI.revive(resource); - const workspaceFolder = this._contextService.getWorkspaceFolder(uri); - - const opts = { ...unvalidatedOpts }; - - // Convert a recursive watcher to a flat watcher if the path - // turns out to not be a folder. Recursive watching is only - // possible on folders, so we help all file watchers by checking - // early. - if (opts.recursive) { - try { - const stat = await this._fileService.stat(uri); - if (!stat.isDirectory) { - opts.recursive = false; - } - } catch (error) { - this._logService.error(`MainThreadFileSystem#$watch(): failed to stat a resource for file watching (extension: ${extensionId}, path: ${uri.toString(true)}, recursive: ${opts.recursive}, session: ${session}): ${error}`); - } - } - - // Refuse to watch anything that is already watched via - // our workspace watchers in case the request is a - // recursive file watcher. - // Still allow for non-recursive watch requests as a way - // to bypass configured exclude rules though - // (see https://github.com/microsoft/vscode/issues/146066) - if (workspaceFolder && opts.recursive) { - this._logService.trace(`MainThreadFileSystem#$watch(): ignoring request to start watching because path is inside workspace (extension: ${extensionId}, path: ${uri.toString(true)}, recursive: ${opts.recursive}, session: ${session})`); - return; - } - - this._logService.trace(`MainThreadFileSystem#$watch(): request to start watching (extension: ${extensionId}, path: ${uri.toString(true)}, recursive: ${opts.recursive}, session: ${session})`); - - // Automatically add `files.watcherExclude` patterns when watching - // recursively to give users a chance to configure exclude rules - // for reducing the overhead of watching recursively - if (opts.recursive) { - const config = this._configurationService.getValue(); - if (config.files?.watcherExclude) { - for (const key in config.files.watcherExclude) { - if (config.files.watcherExclude[key] === true) { - opts.excludes.push(key); - } - } - } - } - - // Non-recursive watching inside the workspace will overlap with - // our standard workspace watchers. To prevent duplicate events, - // we only want to include events for files that are otherwise - // excluded via `files.watcherExclude`. As such, we configure - // to include each configured exclude pattern so that only those - // events are reported that are otherwise excluded. - // However, we cannot just use the pattern as is, because a pattern - // such as `bar` for a exclude, will work to exclude any of - // `/bar` but will not work as include for files within - // `bar` unless a suffix of `/**` if added. - // (https://github.com/microsoft/vscode/issues/148245) - else if (workspaceFolder) { - const config = this._configurationService.getValue(); - if (config.files?.watcherExclude) { - for (const key in config.files.watcherExclude) { - if (config.files.watcherExclude[key] === true) { - if (!opts.includes) { - opts.includes = []; - } - - const includePattern = `${rtrim(key, '/')}/${GLOBSTAR}`; - opts.includes.push(normalizeWatcherPattern(workspaceFolder.uri.fsPath, includePattern)); - } - } - } - - // Still ignore watch request if there are actually no configured - // exclude rules, because in that case our default recursive watcher - // should be able to take care of all events. - if (!opts.includes || opts.includes.length === 0) { - this._logService.trace(`MainThreadFileSystem#$watch(): ignoring request to start watching because path is inside workspace and no excludes are configured (extension: ${extensionId}, path: ${uri.toString(true)}, recursive: ${opts.recursive}, session: ${session})`); - return; - } - } - const subscription = this._fileService.watch(uri, opts); - this._watches.set(session, subscription); - } - - $unwatch(session: number): void { - if (this._watches.has(session)) { - this._logService.trace(`MainThreadFileSystem#$unwatch(): request to stop watching (session: ${session})`); - this._watches.deleteAndDispose(session); - } - } } class RemoteFileSystemProvider implements IFileSystemProviderWithFileReadWriteCapability, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileFolderCopyCapability { diff --git a/code/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts b/code/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts index b9d1f467a19..8ddb730dc16 100644 --- a/code/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts +++ b/code/src/vs/workbench/api/browser/mainThreadFileSystemEventService.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DisposableStore } from 'vs/base/common/lifecycle'; -import { FileOperation, IFileService } from 'vs/platform/files/common/files'; -import { extHostCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; -import { ExtHostContext } from '../common/extHost.protocol'; +import { DisposableMap, DisposableStore } from 'vs/base/common/lifecycle'; +import { FileOperation, IFileService, IFilesConfiguration, IWatchOptions } from 'vs/platform/files/common/files'; +import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; +import { ExtHostContext, ExtHostFileSystemEventServiceShape, MainContext, MainThreadFileSystemEventServiceShape } from '../common/extHost.protocol'; import { localize } from 'vs/nls'; import { IWorkingCopyFileOperationParticipant, IWorkingCopyFileService, SourceTargetPair, IFileOperationUndoRedoInfo } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; @@ -22,17 +22,26 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { reviveWorkspaceEditDto } from 'vs/workbench/api/browser/mainThreadBulkEdits'; +import { GLOBSTAR } from 'vs/base/common/glob'; +import { rtrim } from 'vs/base/common/strings'; +import { UriComponents, URI } from 'vs/base/common/uri'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { normalizeWatcherPattern } from 'vs/platform/files/common/watcher'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -@extHostCustomer -export class MainThreadFileSystemEventService { +@extHostNamedCustomer(MainContext.MainThreadFileSystemEventService) +export class MainThreadFileSystemEventService implements MainThreadFileSystemEventServiceShape { static readonly MementoKeyAdditionalEdits = `file.particpants.additionalEdits`; + private readonly _proxy: ExtHostFileSystemEventServiceShape; + private readonly _listener = new DisposableStore(); + private readonly _watches = new DisposableMap(); constructor( extHostContext: IExtHostContext, - @IFileService fileService: IFileService, + @IFileService private readonly _fileService: IFileService, @IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService, @IBulkEditService bulkEditService: IBulkEditService, @IProgressService progressService: IProgressService, @@ -40,19 +49,22 @@ export class MainThreadFileSystemEventService { @IStorageService storageService: IStorageService, @ILogService logService: ILogService, @IEnvironmentService envService: IEnvironmentService, - @IUriIdentityService uriIdentService: IUriIdentityService + @IUriIdentityService uriIdentService: IUriIdentityService, + @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, + @ILogService private readonly _logService: ILogService, + @IConfigurationService private readonly _configurationService: IConfigurationService ) { + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostFileSystemEventService); - const proxy = extHostContext.getProxy(ExtHostContext.ExtHostFileSystemEventService); - - this._listener.add(fileService.onDidFilesChange(event => { - proxy.$onFileEvent({ + this._listener.add(_fileService.onDidFilesChange(event => { + this._proxy.$onFileEvent({ created: event.rawAdded, changed: event.rawUpdated, deleted: event.rawDeleted }); })); + const that = this; const fileOperationParticipant = new class implements IWorkingCopyFileOperationParticipant { async participate(files: SourceTargetPair[], operation: FileOperation, undoInfo: IFileOperationUndoRedoInfo | undefined, timeout: number, token: CancellationToken) { if (undoInfo?.isUndoing) { @@ -69,7 +81,7 @@ export class MainThreadFileSystemEventService { delay: Math.min(timeout / 2, 3000) }, () => { // race extension host event delivery against timeout AND user-cancel - const onWillEvent = proxy.$onWillRunFileOperation(operation, files, timeout, cts.token); + const onWillEvent = that._proxy.$onWillRunFileOperation(operation, files, timeout, cts.token); return raceCancellation(onWillEvent, cts.token); }, () => { // user-cancel @@ -197,11 +209,132 @@ export class MainThreadFileSystemEventService { this._listener.add(workingCopyFileService.addFileOperationParticipant(fileOperationParticipant)); // AFTER file operation - this._listener.add(workingCopyFileService.onDidRunWorkingCopyFileOperation(e => proxy.$onDidRunFileOperation(e.operation, e.files))); + this._listener.add(workingCopyFileService.onDidRunWorkingCopyFileOperation(e => this._proxy.$onDidRunFileOperation(e.operation, e.files))); + } + + async $watch(extensionId: string, session: number, resource: UriComponents, unvalidatedOpts: IWatchOptions): Promise { + const uri = URI.revive(resource); + const correlate = Array.isArray(unvalidatedOpts?.excludes) && unvalidatedOpts.excludes.length > 0; // TODO@bpasero for now only correlate proposed new file system watcher API with excludes + + const opts: IWatchOptions = { + ...unvalidatedOpts + }; + + // Convert a recursive watcher to a flat watcher if the path + // turns out to not be a folder. Recursive watching is only + // possible on folders, so we help all file watchers by checking + // early. + if (opts.recursive) { + try { + const stat = await this._fileService.stat(uri); + if (!stat.isDirectory) { + opts.recursive = false; + } + } catch (error) { + this._logService.error(`MainThreadFileSystemEventService#$watch(): failed to stat a resource for file watching (extension: ${extensionId}, path: ${uri.toString(true)}, recursive: ${opts.recursive}, session: ${session}): ${error}`); + } + } + + // Correlated file watching is taken as is + if (correlate) { + this._logService.trace(`MainThreadFileSystemEventService#$watch(): request to start watching correlated (extension: ${extensionId}, path: ${uri.toString(true)}, recursive: ${opts.recursive}, session: ${session})`); + + const watcherDisposables = new DisposableStore(); + const subscription = watcherDisposables.add(this._fileService.createWatcher(uri, opts)); + watcherDisposables.add(subscription.onDidChange(event => { + this._proxy.$onFileEvent({ + session, + created: event.rawAdded, + changed: event.rawUpdated, + deleted: event.rawDeleted + }); + })); + + this._watches.set(session, watcherDisposables); + } + + // Uncorrelated file watching gets special treatment + else { + + // Refuse to watch anything that is already watched via + // our workspace watchers in case the request is a + // recursive file watcher and does not opt-in to event + // correlation via specific exclude rules. + // Still allow for non-recursive watch requests as a way + // to bypass configured exclude rules though + // (see https://github.com/microsoft/vscode/issues/146066) + const workspaceFolder = this._contextService.getWorkspaceFolder(uri); + if (workspaceFolder && opts.recursive) { + this._logService.trace(`MainThreadFileSystemEventService#$watch(): ignoring request to start watching because path is inside workspace (extension: ${extensionId}, path: ${uri.toString(true)}, recursive: ${opts.recursive}, session: ${session})`); + return; + } + + this._logService.trace(`MainThreadFileSystemEventService#$watch(): request to start watching uncorrelated (extension: ${extensionId}, path: ${uri.toString(true)}, recursive: ${opts.recursive}, session: ${session})`); + + // Automatically add `files.watcherExclude` patterns when watching + // recursively to give users a chance to configure exclude rules + // for reducing the overhead of watching recursively + if (opts.recursive && opts.excludes.length === 0) { + const config = this._configurationService.getValue(); + if (config.files?.watcherExclude) { + for (const key in config.files.watcherExclude) { + if (config.files.watcherExclude[key] === true) { + opts.excludes.push(key); + } + } + } + } + + // Non-recursive watching inside the workspace will overlap with + // our standard workspace watchers. To prevent duplicate events, + // we only want to include events for files that are otherwise + // excluded via `files.watcherExclude`. As such, we configure + // to include each configured exclude pattern so that only those + // events are reported that are otherwise excluded. + // However, we cannot just use the pattern as is, because a pattern + // such as `bar` for a exclude, will work to exclude any of + // `/bar` but will not work as include for files within + // `bar` unless a suffix of `/**` if added. + // (https://github.com/microsoft/vscode/issues/148245) + else if (workspaceFolder) { + const config = this._configurationService.getValue(); + if (config.files?.watcherExclude) { + for (const key in config.files.watcherExclude) { + if (config.files.watcherExclude[key] === true) { + if (!opts.includes) { + opts.includes = []; + } + + const includePattern = `${rtrim(key, '/')}/${GLOBSTAR}`; + opts.includes.push(normalizeWatcherPattern(workspaceFolder.uri.fsPath, includePattern)); + } + } + } + + // Still ignore watch request if there are actually no configured + // exclude rules, because in that case our default recursive watcher + // should be able to take care of all events. + if (!opts.includes || opts.includes.length === 0) { + this._logService.trace(`MainThreadFileSystemEventService#$watch(): ignoring request to start watching because path is inside workspace and no excludes are configured (extension: ${extensionId}, path: ${uri.toString(true)}, recursive: ${opts.recursive}, session: ${session})`); + return; + } + } + + const subscription = this._fileService.watch(uri, opts); + this._watches.set(session, subscription); + } + } + + $unwatch(session: number): void { + if (this._watches.has(session)) { + this._logService.trace(`MainThreadFileSystemEventService#$unwatch(): request to stop watching (session: ${session})`); + this._watches.deleteAndDispose(session); + } } dispose(): void { this._listener.dispose(); + this._watches.dispose(); } } diff --git a/code/src/vs/workbench/api/browser/mainThreadInlineChat.ts b/code/src/vs/workbench/api/browser/mainThreadInlineChat.ts index 8cefa7a83a7..27e8deeb72b 100644 --- a/code/src/vs/workbench/api/browser/mainThreadInlineChat.ts +++ b/code/src/vs/workbench/api/browser/mainThreadInlineChat.ts @@ -4,12 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { DisposableMap } from 'vs/base/common/lifecycle'; -import { IInlineChatBulkEditResponse, IInlineChatResponse, IInlineChatService } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { IInlineChatBulkEditResponse, IInlineChatProgressItem, IInlineChatResponse, IInlineChatService } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { reviveWorkspaceEditDto } from 'vs/workbench/api/browser/mainThreadBulkEdits'; import { ExtHostContext, ExtHostInlineChatShape, MainContext, MainThreadInlineChatShape as MainThreadInlineChatShape } from 'vs/workbench/api/common/extHost.protocol'; import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; -import { TextEdit } from 'vs/editor/common/languages'; import { IProgress } from 'vs/platform/progress/common/progress'; @extHostNamedCustomer(MainContext.MainThreadInlineChat) @@ -18,7 +17,7 @@ export class MainThreadInlineChat implements MainThreadInlineChatShape { private readonly _registrations = new DisposableMap(); private readonly _proxy: ExtHostInlineChatShape; - private readonly _progresses = new Map>(); + private readonly _progresses = new Map>(); constructor( extHostContext: IExtHostContext, @@ -68,7 +67,7 @@ export class MainThreadInlineChat implements MainThreadInlineChatShape { this._registrations.set(handle, unreg); } - async $handleProgressChunk(requestId: string, chunk: { message?: string | undefined; edits?: TextEdit[] | undefined }): Promise { + async $handleProgressChunk(requestId: string, chunk: IInlineChatProgressItem): Promise { await Promise.resolve(this._progresses.get(requestId)?.report(chunk)); } diff --git a/code/src/vs/workbench/api/browser/mainThreadSpeech.ts b/code/src/vs/workbench/api/browser/mainThreadSpeech.ts new file mode 100644 index 00000000000..d0c7acdc2b6 --- /dev/null +++ b/code/src/vs/workbench/api/browser/mainThreadSpeech.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 { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { Emitter } from 'vs/base/common/event'; +import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { ILogService } from 'vs/platform/log/common/log'; +import { ExtHostContext, ExtHostSpeechShape, MainContext, MainThreadSpeechShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ISpeechProviderMetadata, ISpeechService, ISpeechToTextEvent } from 'vs/workbench/contrib/speech/common/speechService'; +import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; + +type SpeechToTextSession = { + readonly onDidChange: Emitter; +}; + +@extHostNamedCustomer(MainContext.MainThreadSpeech) +export class MainThreadSpeech extends Disposable implements MainThreadSpeechShape { + + private readonly proxy: ExtHostSpeechShape; + + private readonly providerRegistrations = new Map(); + private readonly providerSessions = new Map(); + + constructor( + extHostContext: IExtHostContext, + @ISpeechService private readonly speechService: ISpeechService, + @ILogService private readonly logService: ILogService + ) { + super(); + + this.proxy = extHostContext.getProxy(ExtHostContext.ExtHostSpeech); + } + + $registerProvider(handle: number, identifier: string, metadata: ISpeechProviderMetadata): void { + this.logService.trace('[Speech] extension registered provider', metadata.extension.value); + + const registration = this.speechService.registerSpeechProvider(identifier, { + metadata, + createSpeechToTextSession: token => { + const disposables = new DisposableStore(); + const cts = new CancellationTokenSource(token); + const session = Math.random(); + + this.proxy.$createSpeechToTextSession(handle, session); + disposables.add(token.onCancellationRequested(() => this.proxy.$cancelSpeechToTextSession(session))); + + const onDidChange = disposables.add(new Emitter()); + this.providerSessions.set(session, { onDidChange }); + + return { + onDidChange: onDidChange.event, + dispose: () => { + cts.dispose(true); + this.providerSessions.delete(session); + disposables.dispose(); + } + }; + } + }); + this.providerRegistrations.set(handle, { + dispose: () => { + registration.dispose(); + } + }); + } + + $unregisterProvider(handle: number): void { + const registration = this.providerRegistrations.get(handle); + if (registration) { + registration.dispose(); + this.providerRegistrations.delete(handle); + } + } + + $emitSpeechToTextEvent(session: number, event: ISpeechToTextEvent): void { + const providerSession = this.providerSessions.get(session); + if (providerSession) { + providerSession.onDidChange.fire(event); + } + } +} diff --git a/code/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/code/src/vs/workbench/api/browser/mainThreadTerminalService.ts index a01feeb439a..9ac623649ac 100644 --- a/code/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/code/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -231,7 +231,6 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } public $startSendingCommandEvents(): void { - this._logService.info('$startSendingCommandEvents'); if (this._sendCommandEventListener.value) { return; } @@ -250,7 +249,6 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } public $stopSendingCommandEvents(): void { - this._logService.info('$stopSendingCommandEvents'); this._sendCommandEventListener.clear(); } diff --git a/code/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/code/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 23d64b26b76..c17a247e22c 100644 --- a/code/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/code/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -535,7 +535,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { type: type, ctorDescriptor: type === ViewType.Tree ? new SyncDescriptor(TreeViewPane) : new SyncDescriptor(WebviewViewPane), id: item.id, - name: item.name, + name: { value: item.name, original: item.name }, when: ContextKeyExpr.deserialize(item.when), containerIcon: icon || viewContainer?.icon, containerTitle: item.contextualTitle || (viewContainer && (typeof viewContainer.title === 'string' ? viewContainer.title : viewContainer.title.value)), diff --git a/code/src/vs/workbench/api/common/extHost.api.impl.ts b/code/src/vs/workbench/api/common/extHost.api.impl.ts index 5765ab2eed0..cd983bdc650 100644 --- a/code/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/code/src/vs/workbench/api/common/extHost.api.impl.ts @@ -104,10 +104,12 @@ import { ExtHostIssueReporter } from 'vs/workbench/api/common/extHostIssueReport import { IExtHostManagedSockets } from 'vs/workbench/api/common/extHostManagedSockets'; import { ExtHostShare } from 'vs/workbench/api/common/extHostShare'; import { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider'; +import { ExtHostSpeech } from 'vs/workbench/api/common/extHostSpeech'; import { ExtHostChatVariables } from 'vs/workbench/api/common/extHostChatVariables'; import { ExtHostRelatedInformation } from 'vs/workbench/api/common/extHostAiRelatedInformation'; import { ExtHostAiEmbeddingVector } from 'vs/workbench/api/common/extHostEmbeddingVector'; import { ExtHostChatAgents } from 'vs/workbench/api/common/extHostChatAgents'; +import { ExtHostChatAgents2 } from 'vs/workbench/api/common/extHostChatAgents2'; export interface IExtensionRegistries { mine: ExtensionDescriptionRegistry; @@ -209,12 +211,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostInteractiveEditor = rpcProtocol.set(ExtHostContext.ExtHostInlineChat, new ExtHostInteractiveEditor(rpcProtocol, extHostCommands, extHostDocuments, extHostLogService)); const extHostChatProvider = rpcProtocol.set(ExtHostContext.ExtHostChatProvider, new ExtHostChatProvider(rpcProtocol, extHostLogService)); const extHostChatAgents = rpcProtocol.set(ExtHostContext.ExtHostChatAgents, new ExtHostChatAgents(rpcProtocol, extHostChatProvider, extHostLogService)); + const extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostChatProvider, extHostLogService)); const extHostChatVariables = rpcProtocol.set(ExtHostContext.ExtHostChatVariables, new ExtHostChatVariables(rpcProtocol)); const extHostChat = rpcProtocol.set(ExtHostContext.ExtHostChat, new ExtHostChat(rpcProtocol, extHostLogService)); const extHostAiRelatedInformation = rpcProtocol.set(ExtHostContext.ExtHostAiRelatedInformation, new ExtHostRelatedInformation(rpcProtocol)); const extHostAiEmbeddingVector = rpcProtocol.set(ExtHostContext.ExtHostAiEmbeddingVector, new ExtHostAiEmbeddingVector(rpcProtocol)); const extHostIssueReporter = rpcProtocol.set(ExtHostContext.ExtHostIssueReporter, new ExtHostIssueReporter(rpcProtocol)); const extHostStatusBar = rpcProtocol.set(ExtHostContext.ExtHostStatusBar, new ExtHostStatusBar(rpcProtocol, extHostCommands.converter)); + const extHostSpeech = rpcProtocol.set(ExtHostContext.ExtHostSpeech, new ExtHostSpeech(rpcProtocol)); // Check that no named customers are missing const expected = Object.values>(ExtHostContext); @@ -938,8 +942,21 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I applyEdit(edit: vscode.WorkspaceEdit, metadata?: vscode.WorkspaceEditMetadata): Thenable { return extHostBulkEdits.applyWorkspaceEdit(edit, extension, metadata); }, - createFileSystemWatcher: (pattern, ignoreCreate, ignoreChange, ignoreDelete): vscode.FileSystemWatcher => { - return extHostFileSystemEvent.createFileSystemWatcher(extHostWorkspace, extension, pattern, ignoreCreate, ignoreChange, ignoreDelete); + createFileSystemWatcher: (pattern, optionsOrIgnoreCreate, ignoreChange?, ignoreDelete?): vscode.FileSystemWatcher => { + let options: vscode.FileSystemWatcherOptions | undefined = undefined; + + if (typeof optionsOrIgnoreCreate === 'boolean') { + options = { + ignoreCreateEvents: Boolean(optionsOrIgnoreCreate), + ignoreChangeEvents: Boolean(ignoreChange), + ignoreDeleteEvents: Boolean(ignoreDelete) + }; + } else if (optionsOrIgnoreCreate) { + checkProposedApiEnabled(extension, 'createFileSystemWatcher'); + options = optionsOrIgnoreCreate; + } + + return extHostFileSystemEvent.createFileSystemWatcher(extHostWorkspace, extension, pattern, options); }, get textDocuments() { return extHostDocuments.getAllDocumentData().map(data => data.document); @@ -1353,11 +1370,22 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'mappedEditsProvider'); return extHostLanguageFeatures.registerMappedEditsProvider(extension, selector, provider); }, + createChatAgent(name: string, handler: vscode.ChatAgentHandler) { + checkProposedApiEnabled(extension, 'chatAgents2'); + return extHostChatAgents2.createChatAgent(extension, name, handler); + }, registerAgent(name: string, agent: vscode.ChatAgent, metadata: vscode.ChatAgentMetadata) { checkProposedApiEnabled(extension, 'chatAgents'); return extHostChatAgents.registerAgent(extension.identifier, name, agent, metadata); } + }; + // namespace: speech + const speech: typeof vscode.speech = { + registerSpeechProvider(id: string, provider: vscode.SpeechProvider) { + checkProposedApiEnabled(extension, 'speech'); + return extHostSpeech.registerProvider(extension.identifier, id, provider); + } }; return { @@ -1376,6 +1404,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I languages, notebooks, scm, + speech, tasks, tests, window, @@ -1383,6 +1412,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // types Breakpoint: extHostTypes.Breakpoint, TerminalOutputAnchor: extHostTypes.TerminalOutputAnchor, + ChatAgentResultFeedbackKind: extHostTypes.ChatAgentResultFeedbackKind, ChatMessage: extHostTypes.ChatMessage, ChatMessageRole: extHostTypes.ChatMessageRole, ChatVariableLevel: extHostTypes.ChatVariableLevel, @@ -1575,7 +1605,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I InteractiveEditorResponseFeedbackKind: extHostTypes.InteractiveEditorResponseFeedbackKind, StackFrameFocus: extHostTypes.StackFrameFocus, ThreadFocus: extHostTypes.ThreadFocus, - RelatedInformationType: extHostTypes.RelatedInformationType + RelatedInformationType: extHostTypes.RelatedInformationType, + SpeechToTextStatus: extHostTypes.SpeechToTextStatus }; }; } diff --git a/code/src/vs/workbench/api/common/extHost.protocol.ts b/code/src/vs/workbench/api/common/extHost.protocol.ts index 11cff5f8fdb..160a072b71a 100644 --- a/code/src/vs/workbench/api/common/extHost.protocol.ts +++ b/code/src/vs/workbench/api/common/extHost.protocol.ts @@ -50,19 +50,20 @@ import * as tasks from 'vs/workbench/api/common/shared/tasks'; import { SaveReason } from 'vs/workbench/common/editor'; import { IRevealOptions, ITreeItem, IViewBadge } from 'vs/workbench/common/views'; import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; -import { IChatAgentMetadata } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatAgentCommand, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatMessage, IChatResponseFragment, IChatResponseProviderMetadata } from 'vs/workbench/contrib/chat/common/chatProvider'; -import { IChatDynamicRequest, IChatFollowup, IChatReplyFollowup, IChatResponseErrorDetails, IChatUserActionEvent, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatDynamicRequest, IChatFollowup, IChatReplyFollowup, IChatResponseErrorDetails, IChatUserActionEvent, ISlashCommand, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatSlashFragment } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatRequestVariableValue, IChatVariableData } from 'vs/workbench/contrib/chat/common/chatVariables'; import { DebugConfigurationProviderTriggerKind, IAdapterDescriptor, IConfig, IDebugSessionReplMode } from 'vs/workbench/contrib/debug/common/debug'; -import { IInlineChatBulkEditResponse, IInlineChatEditResponse, IInlineChatMessageResponse, IInlineChatRequest, IInlineChatSession, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { IInlineChatBulkEditResponse, IInlineChatEditResponse, IInlineChatMessageResponse, IInlineChatProgressItem, IInlineChatRequest, IInlineChatSession, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import * as notebookCommon from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CellExecutionUpdateType } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; import { ICellExecutionComplete, ICellExecutionStateUpdate } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { InputValidationType } from 'vs/workbench/contrib/scm/common/scm'; import { IWorkspaceSymbol } from 'vs/workbench/contrib/search/common/search'; +import { ISpeechProviderMetadata, ISpeechToTextEvent } from 'vs/workbench/contrib/speech/common/speechService'; import { CoverageDetails, ExtensionRunTestsRequest, ICallProfileRunHandler, IFileCoverage, ISerializedTestResults, IStartControllerTests, ITestItem, ITestMessage, ITestRunProfile, ITestRunTask, ResolvedTestRunRequest, TestResultState, TestsDiffOp } from 'vs/workbench/contrib/testing/common/testTypes'; import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline'; import { TypeHierarchyItem } from 'vs/workbench/contrib/typeHierarchy/common/typeHierarchy'; @@ -1133,6 +1134,18 @@ export interface MainThreadNotebookRenderersShape extends IDisposable { export interface MainThreadInteractiveShape extends IDisposable { } +export interface MainThreadSpeechShape extends IDisposable { + $registerProvider(handle: number, identifier: string, metadata: ISpeechProviderMetadata): void; + $unregisterProvider(handle: number): void; + + $emitSpeechToTextEvent(session: number, event: ISpeechToTextEvent): void; +} + +export interface ExtHostSpeechShape { + $createSpeechToTextSession(handle: number, session: number): Promise; + $cancelSpeechToTextSession(session: number): Promise; +} + export interface MainThreadChatProviderShape extends IDisposable { $registerProvider(handle: number, identifier: string, metadata: IChatResponseProviderMetadata): void; $unregisterProvider(handle: number): void; @@ -1147,15 +1160,36 @@ export interface ExtHostChatProviderShape { } export interface MainThreadChatAgentsShape extends IDisposable { - $registerAgent(handle: number, name: string, metadata: IChatAgentMetadata): void; + $registerAgent(handle: number, name: string, metadata: IChatAgentMetadata & { subCommands: IChatAgentCommand[] }): void; $unregisterAgent(handle: number): void; $handleProgressChunk(requestId: number, chunk: IChatSlashFragment): Promise; } +export interface IExtensionChatAgentMetadata extends Dto { + hasSlashCommands?: boolean; + hasFollowup?: boolean; +} + +export interface MainThreadChatAgentsShape2 extends IDisposable { + $registerAgent(handle: number, name: string, metadata: IExtensionChatAgentMetadata): void; + $updateAgent(handle: number, metadataUpdate: IExtensionChatAgentMetadata): void; + $unregisterAgent(handle: number): void; + $handleProgressChunk(requestId: number, chunk: IChatResponseProgressDto): Promise; +} + export interface ExtHostChatAgentsShape { $invokeAgent(handle: number, requestId: number, prompt: string, context: { history: IChatMessage[] }, token: CancellationToken): Promise; } +export interface ExtHostChatAgentsShape2 { + $invokeAgent(handle: number, sessionId: string, requestId: number, request: IChatAgentRequest, context: { history: IChatMessage[] }, token: CancellationToken): Promise; + $provideSlashCommands(handle: number, token: CancellationToken): Promise; + $provideFollowups(handle: number, sessionId: string, token: CancellationToken): Promise; + $acceptFeedback(handle: number, sessionId: string, vote: InteractiveSessionVoteDirection): void; + $acceptAction(handle: number, sessionId: string, action: IChatUserActionEvent): void; + $releaseSession(sessionId: string): void; +} + export interface MainThreadChatVariablesShape extends IDisposable { $registerVariable(handle: number, data: IChatVariableData): void; $unregisterVariable(handle: number): void; @@ -1167,7 +1201,7 @@ export interface ExtHostChatVariablesShape { export interface MainThreadInlineChatShape extends IDisposable { $registerInteractiveEditorProvider(handle: number, label: string, debugName: string, supportsFeedback: boolean): Promise; - $handleProgressChunk(requestId: string, chunk: { message?: string; edits?: languages.TextEdit[] }): Promise; + $handleProgressChunk(requestId: string, chunk: Dto): Promise; $unregisterInteractiveEditorProvider(handle: number): Promise; } @@ -1196,7 +1230,7 @@ export interface IChatDto { } export interface IChatRequestDto { - message: string | IChatReplyFollowup; + message: string; variables?: Record; } @@ -1225,7 +1259,9 @@ export type IChatResponseProgressDto = | { requestId: string } | { placeholder: string } | { treeData: IChatResponseProgressFileTreeData } - | { documents: IDocumentContextDto[] }; + | { documents: IDocumentContextDto[] } + | { reference: UriComponents | ILocationDto } + | { inlineReference: UriComponents | ILocationDto; title?: string }; export interface MainThreadChatShape extends IDisposable { $registerChatProvider(handle: number, id: string): Promise; @@ -1239,6 +1275,7 @@ export interface MainThreadChatShape extends IDisposable { export interface ExtHostChatShape { $prepareChat(handle: number, initialState: any, token: CancellationToken): Promise; $provideWelcomeMessage(handle: number, token: CancellationToken): Promise<(string | IChatReplyFollowup[])[] | undefined>; + $provideSampleQuestions(handle: number, token: CancellationToken): Promise; $provideFollowups(handle: number, sessionId: number, token: CancellationToken): Promise; $provideReply(handle: number, sessionId: number, request: IChatRequestDto, token: CancellationToken): Promise; $removeRequest(handle: number, sessionId: number, requestId: string): void; @@ -1309,10 +1346,12 @@ export interface MainThreadFileSystemShape extends IDisposable { $mkdir(resource: UriComponents): Promise; $delete(resource: UriComponents, opts: files.IFileDeleteOptions): Promise; + $ensureActivation(scheme: string): Promise; +} + +export interface MainThreadFileSystemEventServiceShape extends IDisposable { $watch(extensionId: string, session: number, resource: UriComponents, opts: files.IWatchOptions): void; $unwatch(session: number): void; - - $ensureActivation(scheme: string): Promise; } export interface MainThreadLabelServiceShape extends IDisposable { @@ -1761,6 +1800,7 @@ export interface ExtHostExtensionServiceShape { } export interface FileSystemEvents { + session?: number; created: UriComponents[]; changed: UriComponents[]; deleted: UriComponents[]; @@ -2325,7 +2365,7 @@ export interface ExtHostProgressShape { } export interface ExtHostCommentsShape { - $createCommentThreadTemplate(commentControllerHandle: number, uriComponents: UriComponents, range: IRange | undefined): void; + $createCommentThreadTemplate(commentControllerHandle: number, uriComponents: UriComponents, range: IRange | undefined): Promise; $updateCommentThreadTemplate(commentControllerHandle: number, threadHandle: number, range: IRange): Promise; $deleteCommentThread(commentControllerHandle: number, commentThreadHandle: number): void; $provideCommentingRanges(commentControllerHandle: number, uriComponents: UriComponents, token: CancellationToken): Promise<{ ranges: IRange[]; fileComments: boolean } | undefined>; @@ -2659,6 +2699,7 @@ export const MainContext = { MainThreadBulkEdits: createProxyIdentifier('MainThreadBulkEdits'), MainThreadChatProvider: createProxyIdentifier('MainThreadChatProvider'), MainThreadChatAgents: createProxyIdentifier('MainThreadChatAgents'), + MainThreadChatAgents2: createProxyIdentifier('MainThreadChatAgents2'), MainThreadChatVariables: createProxyIdentifier('MainThreadChatVariables'), MainThreadClipboard: createProxyIdentifier('MainThreadClipboard'), MainThreadCommands: createProxyIdentifier('MainThreadCommands'), @@ -2688,6 +2729,7 @@ export const MainContext = { MainThreadStatusBar: createProxyIdentifier('MainThreadStatusBar'), MainThreadSecretState: createProxyIdentifier('MainThreadSecretState'), MainThreadStorage: createProxyIdentifier('MainThreadStorage'), + MainThreadSpeech: createProxyIdentifier('MainThreadSpeechProvider'), MainThreadTelemetry: createProxyIdentifier('MainThreadTelemetry'), MainThreadTerminalService: createProxyIdentifier('MainThreadTerminalService'), MainThreadWebviews: createProxyIdentifier('MainThreadWebviews'), @@ -2699,6 +2741,7 @@ export const MainContext = { MainThreadProfileContentHandlers: createProxyIdentifier('MainThreadProfileContentHandlers'), MainThreadWorkspace: createProxyIdentifier('MainThreadWorkspace'), MainThreadFileSystem: createProxyIdentifier('MainThreadFileSystem'), + MainThreadFileSystemEventService: createProxyIdentifier('MainThreadFileSystemEventService'), MainThreadExtensionService: createProxyIdentifier('MainThreadExtensionService'), MainThreadSCM: createProxyIdentifier('MainThreadSCM'), MainThreadSearch: createProxyIdentifier('MainThreadSearch'), @@ -2779,8 +2822,10 @@ export const ExtHostContext = { ExtHostInlineChat: createProxyIdentifier('ExtHostInlineChatShape'), ExtHostChat: createProxyIdentifier('ExtHostChat'), ExtHostChatAgents: createProxyIdentifier('ExtHostChatAgents'), + ExtHostChatAgents2: createProxyIdentifier('ExtHostChatAgents'), ExtHostChatVariables: createProxyIdentifier('ExtHostChatVariables'), ExtHostChatProvider: createProxyIdentifier('ExtHostChatProvider'), + ExtHostSpeech: createProxyIdentifier('ExtHostSpeech'), ExtHostAiRelatedInformation: createProxyIdentifier('ExtHostAiRelatedInformation'), ExtHostAiEmbeddingVector: createProxyIdentifier('ExtHostAiEmbeddingVector'), ExtHostTheming: createProxyIdentifier('ExtHostTheming'), diff --git a/code/src/vs/workbench/api/common/extHostApiCommands.ts b/code/src/vs/workbench/api/common/extHostApiCommands.ts index f4f7c8ee4a7..e79c39ceee6 100644 --- a/code/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/code/src/vs/workbench/api/common/extHostApiCommands.ts @@ -411,7 +411,7 @@ const newCommands: ApiCommand[] = [ 'vscode.openWith', '_workbench.openWith', 'Opens the provided resource with a specific editor.', [ ApiCommandArgument.Uri.with('resource', 'Resource to open'), - ApiCommandArgument.String.with('viewId', 'Custom editor view id or \'default\' to use VS Code\'s default editor'), + ApiCommandArgument.String.with('viewId', 'Custom editor view id. This should be the viewType string for custom editors or the notebookType string for notebooks. Use \'default\' to use VS Code\'s default text editor'), new ApiCommandArgument('columnOrOptions', 'Either the column in which to open or editor options, see vscode.TextDocumentShowOptions', v => v === undefined || typeof v === 'number' || typeof v === 'object', v => !v ? v : typeof v === 'number' ? [typeConverters.ViewColumn.from(v), undefined] : [typeConverters.ViewColumn.from(v.viewColumn), typeConverters.TextEditorOpenOptions.from(v)], diff --git a/code/src/vs/workbench/api/common/extHostChat.ts b/code/src/vs/workbench/api/common/extHostChat.ts index 792f4ae7c71..f90aff58189 100644 --- a/code/src/vs/workbench/api/common/extHostChat.ts +++ b/code/src/vs/workbench/api/common/extHostChat.ts @@ -140,6 +140,24 @@ export class ExtHostChat implements ExtHostChatShape { return rawFollowups?.map(f => typeConvert.ChatFollowup.from(f)); } + async $provideSampleQuestions(handle: number, token: CancellationToken): Promise { + const entry = this._chatProvider.get(handle); + if (!entry) { + return undefined; + } + + if (!entry.provider.provideSampleQuestions) { + return undefined; + } + + const rawFollowups = await entry.provider.provideSampleQuestions(token); + if (!rawFollowups) { + return undefined; + } + + return rawFollowups?.map(f => typeConvert.ChatReplyFollowup.from(f)); + } + $removeRequest(handle: number, sessionId: number, requestId: string): void { const entry = this._chatProvider.get(handle); if (!entry) { @@ -171,7 +189,7 @@ export class ExtHostChat implements ExtHostChatShape { const requestObj: vscode.InteractiveRequest = { session: realSession, - message: typeof request.message === 'string' ? request.message : typeConvert.ChatReplyFollowup.to(request.message), + message: request.message, variables: {} }; @@ -193,10 +211,9 @@ export class ExtHostChat implements ExtHostChatShape { firstProgress = stopWatch.elapsed(); } - if ('responseId' in progress) { - this._proxy.$acceptResponseProgress(handle, sessionId, { requestId: progress.responseId }); - } else if ('placeholder' in progress && 'resolvedContent' in progress) { - const resolvedContent = Promise.all([this._proxy.$acceptResponseProgress(handle, sessionId, { placeholder: progress.placeholder }), progress.resolvedContent]); + const convertedProgress = typeConvert.ChatResponseProgress.from(progress); + if ('placeholder' in progress && 'resolvedContent' in progress) { + const resolvedContent = Promise.all([this._proxy.$acceptResponseProgress(handle, sessionId, convertedProgress), progress.resolvedContent]); raceCancellation(resolvedContent, token).then((res) => { if (!res) { return; /* Cancelled */ @@ -204,20 +221,8 @@ export class ExtHostChat implements ExtHostChatShape { const [progressHandle, progressContent] = res; this._proxy.$acceptResponseProgress(handle, sessionId, progressContent, progressHandle ?? undefined); }); - } else if ('content' in progress) { - this._proxy.$acceptResponseProgress(handle, sessionId, { - content: typeof progress.content === 'string' ? progress.content : typeConvert.MarkdownString.from(progress.content) - }); - } else if ('documents' in progress) { - this._proxy.$acceptResponseProgress(handle, sessionId, { - documents: progress.documents.map(d => ({ - uri: d.uri, - version: d.version, - ranges: d.ranges.map(r => typeConvert.Range.from(r)) - })) - }); } else { - this._proxy.$acceptResponseProgress(handle, sessionId, progress); + this._proxy.$acceptResponseProgress(handle, sessionId, convertedProgress); } } }; @@ -273,7 +278,7 @@ export class ExtHostChat implements ExtHostChatShape { } async $onDidPerformUserAction(event: IChatUserActionEvent): Promise { - this._onDidPerformUserAction.fire(event); + this._onDidPerformUserAction.fire(event as any); } //#endregion diff --git a/code/src/vs/workbench/api/common/extHostChatAgents.ts b/code/src/vs/workbench/api/common/extHostChatAgents.ts index 1fc42c5a393..eca6e389255 100644 --- a/code/src/vs/workbench/api/common/extHostChatAgents.ts +++ b/code/src/vs/workbench/api/common/extHostChatAgents.ts @@ -58,7 +58,7 @@ export class ExtHostChatAgents implements ExtHostChatAgentsShape { const commandExecution = new DeferredPromise(); token.onCancellationRequested(() => commandExecution.complete()); - setTimeout(() => commandExecution.complete(), 3 * 1000); + setTimeout(() => commandExecution.complete(), 10 * 1000); this._extHostChatProvider.allowListExtensionWhile(data.extension, commandExecution.p); const task = data.agent( diff --git a/code/src/vs/workbench/api/common/extHostChatAgents2.ts b/code/src/vs/workbench/api/common/extHostChatAgents2.ts new file mode 100644 index 00000000000..22849d610cf --- /dev/null +++ b/code/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -0,0 +1,338 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DeferredPromise, raceCancellation } from 'vs/base/common/async'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { toErrorMessage } from 'vs/base/common/errorMessage'; +import { Emitter } from 'vs/base/common/event'; +import { assertType } from 'vs/base/common/types'; +import { URI } from 'vs/base/common/uri'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ILogService } from 'vs/platform/log/common/log'; +import { Progress } from 'vs/platform/progress/common/progress'; +import { ExtHostChatAgentsShape2, IMainContext, MainContext, MainThreadChatAgentsShape2 } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider'; +import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; +import { ChatAgentResultFeedbackKind } from 'vs/workbench/api/common/extHostTypes'; +import { IChatAgentCommand, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; +import { IChatFollowup, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import type * as vscode from 'vscode'; + +export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { + + private static _idPool = 0; + + private readonly _agents = new Map(); + private readonly _proxy: MainThreadChatAgentsShape2; + + private readonly _previousResultMap: Map = new Map(); + + constructor( + mainContext: IMainContext, + private readonly _extHostChatProvider: ExtHostChatProvider, + private readonly _logService: ILogService, + ) { + this._proxy = mainContext.getProxy(MainContext.MainThreadChatAgents2); + } + + createChatAgent(extension: IExtensionDescription, name: string, handler: vscode.ChatAgentHandler): vscode.ChatAgent2 { + const handle = ExtHostChatAgents2._idPool++; + const agent = new ExtHostChatAgent(extension, name, this._proxy, handle, handler); + this._agents.set(handle, agent); + + this._proxy.$registerAgent(handle, name, {}); + return agent.apiAgent; + } + + async $invokeAgent(handle: number, sessionId: string, requestId: number, request: IChatAgentRequest, context: { history: IChatMessage[] }, token: CancellationToken): Promise { + const agent = this._agents.get(handle); + if (!agent) { + throw new Error(`[CHAT](${handle}) CANNOT invoke agent because the agent is not registered`); + } + + let done = false; + function throwIfDone() { + if (done) { + throw new Error('Only valid while executing the command'); + } + } + + const commandExecution = new DeferredPromise(); + token.onCancellationRequested(() => commandExecution.complete()); + setTimeout(() => commandExecution.complete(), 10 * 1000); + this._extHostChatProvider.allowListExtensionWhile(agent.extension.identifier, commandExecution.p); + + const slashCommand = request.command + ? await agent.validateSlashCommand(request.command) + : undefined; + + try { + const task = agent.invoke( + { + prompt: request.message, + variables: typeConvert.ChatVariable.objectTo(request.variables), + slashCommand + }, + { history: context.history.map(typeConvert.ChatMessage.to) }, + new Progress(p => { + throwIfDone(); + const convertedProgress = typeConvert.ChatResponseProgress.from(p); + this._proxy.$handleProgressChunk(requestId, convertedProgress); + }), + token + ); + + return await raceCancellation(Promise.resolve(task).then((result) => { + if (result) { + this._previousResultMap.set(sessionId, result); + return { errorDetails: result.errorDetails }; // TODO timings here + } else { + this._previousResultMap.delete(sessionId); + } + + return undefined; + }), token); + + } catch (e) { + this._logService.error(e, agent.extension); + return { + errorDetails: { + message: toErrorMessage(e) + } + }; + + } finally { + done = true; + commandExecution.complete(); + } + } + + $releaseSession(sessionId: string): void { + this._previousResultMap.delete(sessionId); + } + + async $provideSlashCommands(handle: number, token: CancellationToken): Promise { + const agent = this._agents.get(handle); + if (!agent) { + // this is OK, the agent might have disposed while the request was in flight + return []; + } + return agent.provideSlashCommand(token); + } + + $provideFollowups(handle: number, sessionId: string, token: CancellationToken): Promise { + const agent = this._agents.get(handle); + if (!agent) { + return Promise.resolve([]); + } + + const result = this._previousResultMap.get(sessionId); + if (!result) { + return Promise.resolve([]); + } + + return agent.provideFollowups(result, token); + } + + $acceptFeedback(handle: number, sessionId: string, vote: InteractiveSessionVoteDirection): void { + const agent = this._agents.get(handle); + if (!agent) { + return; + } + const result = this._previousResultMap.get(sessionId); + if (!result) { + return; + } + + let kind: ChatAgentResultFeedbackKind; + switch (vote) { + case InteractiveSessionVoteDirection.Down: + kind = ChatAgentResultFeedbackKind.Unhelpful; + break; + case InteractiveSessionVoteDirection.Up: + kind = ChatAgentResultFeedbackKind.Helpful; + break; + } + agent.acceptFeedback(Object.freeze({ result, kind })); + } + + $acceptAction(handle: number, sessionId: string, action: IChatUserActionEvent): void { + const agent = this._agents.get(handle); + if (!agent) { + return; + } + const result = this._previousResultMap.get(sessionId); + if (!result) { + return; + } + if (action.action.kind === 'vote') { + // handled by $acceptFeedback + return; + } + agent.acceptAction(Object.freeze({ action: action.action, result })); + } +} + +class ExtHostChatAgent { + + private _slashCommandProvider: vscode.ChatAgentSlashCommandProvider | undefined; + private _lastSlashCommands: vscode.ChatAgentSlashCommand[] | undefined; + private _followupProvider: vscode.FollowupProvider | undefined; + private _description: string | undefined; + private _fullName: string | undefined; + private _iconPath: URI | undefined; + private _isDefault: boolean | undefined; + private _onDidReceiveFeedback = new Emitter(); + private _onDidPerformAction = new Emitter(); + + constructor( + public readonly extension: IExtensionDescription, + private readonly _id: string, + private readonly _proxy: MainThreadChatAgentsShape2, + private readonly _handle: number, + private readonly _callback: vscode.ChatAgentHandler, + ) { } + + acceptFeedback(feedback: vscode.ChatAgentResult2Feedback) { + this._onDidReceiveFeedback.fire(feedback); + } + + acceptAction(event: vscode.ChatAgentUserActionEvent) { + this._onDidPerformAction.fire(event); + } + + async validateSlashCommand(command: string) { + if (!this._lastSlashCommands) { + await this.provideSlashCommand(CancellationToken.None); + assertType(this._lastSlashCommands); + } + const result = this._lastSlashCommands.find(candidate => candidate.name === command); + if (!result) { + throw new Error(`Unknown slashCommand: ${command}`); + + } + return result; + } + + async provideSlashCommand(token: CancellationToken): Promise { + if (!this._slashCommandProvider) { + return []; + } + const result = await this._slashCommandProvider.provideSlashCommands(token); + if (!result) { + return []; + } + this._lastSlashCommands = result; + return result.map(c => ({ name: c.name, description: c.description })); + } + + async provideFollowups(result: vscode.ChatAgentResult2, token: CancellationToken): Promise { + if (!this._followupProvider) { + return []; + } + const followups = await this._followupProvider.provideFollowups(result, token); + if (!followups) { + return []; + } + return followups.map(f => typeConvert.ChatFollowup.from(f)); + } + + get apiAgent(): vscode.ChatAgent2 { + let disposed = false; + let updateScheduled = false; + const updateMetadataSoon = () => { + if (disposed) { + return; + } + if (updateScheduled) { + return; + } + updateScheduled = true; + queueMicrotask(() => { + this._proxy.$updateAgent(this._handle, { + description: this._description ?? '', + fullName: this._fullName, + icon: this._iconPath, + hasSlashCommands: this._slashCommandProvider !== undefined, + hasFollowup: this._followupProvider !== undefined, + isDefault: this._isDefault + }); + updateScheduled = false; + }); + }; + + const that = this; + return { + get name() { + return that._id; + }, + get description() { + return that._description ?? ''; + }, + set description(v) { + that._description = v; + updateMetadataSoon(); + }, + get fullName() { + return that._fullName ?? that.extension.displayName ?? that.extension.name; + }, + set fullName(v) { + that._fullName = v; + updateMetadataSoon(); + }, + get iconPath() { + return that._iconPath; + }, + set iconPath(v) { + that._iconPath = v; + updateMetadataSoon(); + }, + // onDidPerformAction + get slashCommandProvider() { + return that._slashCommandProvider; + }, + set slashCommandProvider(v) { + that._slashCommandProvider = v; + updateMetadataSoon(); + }, + get followupProvider() { + return that._followupProvider; + }, + set followupProvider(v) { + that._followupProvider = v; + updateMetadataSoon(); + }, + get isDefault() { + checkProposedApiEnabled(that.extension, 'defaultChatAgent'); + return that._isDefault; + }, + set isDefault(v) { + checkProposedApiEnabled(that.extension, 'defaultChatAgent'); + that._isDefault = v; + updateMetadataSoon(); + }, + get onDidReceiveFeedback() { + return that._onDidReceiveFeedback.event; + }, + onDidPerformAction: !isProposedApiEnabled(this.extension, 'chatAgents2Additions') + ? undefined! + : this._onDidPerformAction.event + , + dispose() { + disposed = true; + that._slashCommandProvider = undefined; + that._followupProvider = undefined; + that._onDidReceiveFeedback.dispose(); + that._proxy.$unregisterAgent(that._handle); + }, + } satisfies vscode.ChatAgent2; + } + + invoke(request: vscode.ChatAgentRequest, context: vscode.ChatAgentContext, progress: Progress, token: CancellationToken): vscode.ProviderResult { + return this._callback(request, context, progress, token); + } +} diff --git a/code/src/vs/workbench/api/common/extHostChatProvider.ts b/code/src/vs/workbench/api/common/extHostChatProvider.ts index e7679f4bfd4..fa819c6bec3 100644 --- a/code/src/vs/workbench/api/common/extHostChatProvider.ts +++ b/code/src/vs/workbench/api/common/extHostChatProvider.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationToken } from 'vs/base/common/cancellation'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostChatProviderShape, IMainContext, MainContext, MainThreadChatProviderShape } from 'vs/workbench/api/common/extHost.protocol'; @@ -12,12 +12,82 @@ import type * as vscode from 'vscode'; import { Progress } from 'vs/platform/progress/common/progress'; import { IChatMessage, IChatResponseFragment } from 'vs/workbench/contrib/chat/common/chatProvider'; import { ExtensionIdentifier, ExtensionIdentifierMap } from 'vs/platform/extensions/common/extensions'; +import { DeferredAsyncIterableObject } from 'vs/base/common/async'; +import { Emitter } from 'vs/base/common/event'; type ProviderData = { readonly extension: ExtensionIdentifier; readonly provider: vscode.ChatResponseProvider; }; +class ChatResponseStream { + + readonly apiObj: vscode.ChatResponseStream; + readonly stream = new DeferredAsyncIterableObject(); + + constructor(option: number, stream?: DeferredAsyncIterableObject) { + this.stream = stream ?? new DeferredAsyncIterableObject(); + const that = this; + this.apiObj = { + option: option, + response: that.stream.asyncIterable + }; + } +} + +class ChatRequest { + + readonly apiObject: vscode.ChatRequest; + + private readonly _onDidStart = new Emitter(); + private readonly _responseStreams = new Map(); + private readonly _defaultStream = new DeferredAsyncIterableObject(); + private _isDone: boolean = false; + + constructor( + promise: Promise, + cts: CancellationTokenSource + ) { + const that = this; + this.apiObject = { + result: promise, + response: that._defaultStream.asyncIterable, + onDidStartResponseStream: that._onDidStart.event, + cancel() { cts.cancel(); }, + }; + + promise.finally(() => { + this._isDone = true; + if (this._responseStreams.size > 0) { + for (const [, value] of this._responseStreams) { + value.stream.complete(); + } + } else { + this._defaultStream.complete(); + } + }); + } + + handleFragment(fragment: IChatResponseFragment): void { + if (this._isDone) { + return; + } + let res = this._responseStreams.get(fragment.index); + if (!res) { + if (this._responseStreams.size === 0) { + // the first response claims the default response + res = new ChatResponseStream(fragment.index, this._defaultStream); + } else { + res = new ChatResponseStream(fragment.index); + } + this._responseStreams.set(fragment.index, res); + this._onDidStart.fire(res.apiObj); + } + res.stream.emit(fragment.part); + } + +} + export class ExtHostChatProvider implements ExtHostChatProviderShape { private static _idPool = 1; @@ -62,7 +132,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { //#region --- making request - private readonly _pendingRequest = new Map>(); + private readonly _pendingRequest = new Map(); private readonly _chatAccessAllowList = new ExtensionIdentifierMap>(); @@ -84,24 +154,31 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape { get isRevoked() { return !that._chatAccessAllowList.has(from); }, - async makeRequest(messages, options, progress, token) { + makeRequest(messages, options, token) { if (!that._chatAccessAllowList.has(from)) { throw new Error('Access to chat has been revoked'); } + const cts = new CancellationTokenSource(token); const requestId = (Math.random() * 1e6) | 0; - that._pendingRequest.set(requestId, progress); - try { - await that._proxy.$fetchResponse(from, identifier, requestId, messages.map(typeConvert.ChatMessage.from), options, token); - } finally { + const requestPromise = that._proxy.$fetchResponse(from, identifier, requestId, messages.map(typeConvert.ChatMessage.from), options ?? {}, cts.token); + const res = new ChatRequest(requestPromise, cts); + that._pendingRequest.set(requestId, { res }); + + requestPromise.finally(() => { that._pendingRequest.delete(requestId); - } + }); + + return res.apiObject; }, }; } async $handleResponseFragment(requestId: number, chunk: IChatResponseFragment): Promise { - this._pendingRequest.get(requestId)?.report(chunk); + const data = this._pendingRequest.get(requestId);//.report(chunk); + if (data) { + data.res.handleFragment(chunk); + } } } diff --git a/code/src/vs/workbench/api/common/extHostComments.ts b/code/src/vs/workbench/api/common/extHostComments.ts index f621a1d123c..cf8d72d05bd 100644 --- a/code/src/vs/workbench/api/common/extHostComments.ts +++ b/code/src/vs/workbench/api/common/extHostComments.ts @@ -158,7 +158,7 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo return commentController.value; } - $createCommentThreadTemplate(commentControllerHandle: number, uriComponents: UriComponents, range: IRange | undefined): void { + async $createCommentThreadTemplate(commentControllerHandle: number, uriComponents: UriComponents, range: IRange | undefined): Promise { const commentController = this._commentControllers.get(commentControllerHandle); if (!commentController) { diff --git a/code/src/vs/workbench/api/common/extHostFileSystemEventService.ts b/code/src/vs/workbench/api/common/extHostFileSystemEventService.ts index fe4a6796312..c8c657977ba 100644 --- a/code/src/vs/workbench/api/common/extHostFileSystemEventService.ts +++ b/code/src/vs/workbench/api/common/extHostFileSystemEventService.ts @@ -18,8 +18,18 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { Lazy } from 'vs/base/common/lazy'; +interface FileSystemWatcherCreateOptions { + readonly ignoreCreateEvents?: boolean; + readonly ignoreChangeEvents?: boolean; + readonly ignoreDeleteEvents?: boolean; + + readonly excludes?: string[]; +} + class FileSystemWatcher implements vscode.FileSystemWatcher { + private readonly session = Math.random(); + private readonly _onDidCreate = new Emitter(); private readonly _onDidChange = new Emitter(); private readonly _onDidDelete = new Emitter(); @@ -39,17 +49,15 @@ class FileSystemWatcher implements vscode.FileSystemWatcher { return Boolean(this._config & 0b100); } - constructor(mainContext: IMainContext, workspace: IExtHostWorkspace, extension: IExtensionDescription, dispatcher: Event, globPattern: string | IRelativePatternDto, ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean) { - const watcherDisposable = this.ensureWatching(mainContext, extension, globPattern); - + constructor(mainContext: IMainContext, workspace: IExtHostWorkspace, extension: IExtensionDescription, dispatcher: Event, globPattern: string | IRelativePatternDto, options?: FileSystemWatcherCreateOptions) { this._config = 0; - if (ignoreCreateEvents) { + if (options?.ignoreCreateEvents) { this._config += 0b001; } - if (ignoreChangeEvents) { + if (options?.ignoreChangeEvents) { this._config += 0b010; } - if (ignoreDeleteEvents) { + if (options?.ignoreDeleteEvents) { this._config += 0b100; } @@ -63,7 +71,11 @@ class FileSystemWatcher implements vscode.FileSystemWatcher { const excludeOutOfWorkspaceEvents = typeof globPattern === 'string'; const subscription = dispatcher(events => { - if (!ignoreCreateEvents) { + if (typeof events.session === 'number' && events.session !== this.session) { + return; // ignore events from other file watchers + } + + if (!options?.ignoreCreateEvents) { for (const created of events.created) { const uri = URI.revive(created); if (parsedPattern(uri.fsPath) && (!excludeOutOfWorkspaceEvents || workspace.getWorkspaceFolder(uri))) { @@ -71,7 +83,7 @@ class FileSystemWatcher implements vscode.FileSystemWatcher { } } } - if (!ignoreChangeEvents) { + if (!options?.ignoreChangeEvents) { for (const changed of events.changed) { const uri = URI.revive(changed); if (parsedPattern(uri.fsPath) && (!excludeOutOfWorkspaceEvents || workspace.getWorkspaceFolder(uri))) { @@ -79,7 +91,7 @@ class FileSystemWatcher implements vscode.FileSystemWatcher { } } } - if (!ignoreDeleteEvents) { + if (!options?.ignoreDeleteEvents) { for (const deleted of events.deleted) { const uri = URI.revive(deleted); if (parsedPattern(uri.fsPath) && (!excludeOutOfWorkspaceEvents || workspace.getWorkspaceFolder(uri))) { @@ -89,27 +101,26 @@ class FileSystemWatcher implements vscode.FileSystemWatcher { } }); - this._disposable = Disposable.from(watcherDisposable, this._onDidCreate, this._onDidChange, this._onDidDelete, subscription); + this._disposable = Disposable.from(this.ensureWatching(mainContext, extension, globPattern, options), this._onDidCreate, this._onDidChange, this._onDidDelete, subscription); } - private ensureWatching(mainContext: IMainContext, extension: IExtensionDescription, globPattern: string | IRelativePatternDto): Disposable { + private ensureWatching(mainContext: IMainContext, extension: IExtensionDescription, globPattern: string | IRelativePatternDto, options?: FileSystemWatcherCreateOptions): Disposable { const disposable = Disposable.from(); if (typeof globPattern === 'string') { - return disposable; // a pattern alone does not carry sufficient information to start watching anything + return disposable; // workspace is already watched by default, no need to watch again! } - const proxy = mainContext.getProxy(MainContext.MainThreadFileSystem); + const proxy = mainContext.getProxy(MainContext.MainThreadFileSystemEventService); let recursive = false; if (globPattern.pattern.includes(GLOBSTAR) || globPattern.pattern.includes(GLOB_SPLIT)) { recursive = true; // only watch recursively if pattern indicates the need for it } - const session = Math.random(); - proxy.$watch(extension.identifier.value, session, globPattern.baseUri, { recursive, excludes: [] /* excludes are not yet surfaced in the API */ }); + proxy.$watch(extension.identifier.value, this.session, globPattern.baseUri, { recursive, excludes: options?.excludes ?? [] }); - return Disposable.from({ dispose: () => proxy.$unwatch(session) }); + return Disposable.from({ dispose: () => proxy.$unwatch(this.session) }); } dispose() { @@ -138,6 +149,8 @@ class LazyRevivedFileSystemEvents implements FileSystemEvents { constructor(private readonly _events: FileSystemEvents) { } + readonly session = this._events.session; + private _created = new Lazy(() => this._events.created.map(URI.revive) as URI[]); get created(): URI[] { return this._created.value; } @@ -173,15 +186,14 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ //--- file events - createFileSystemWatcher(workspace: IExtHostWorkspace, extension: IExtensionDescription, globPattern: vscode.GlobPattern, ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean): vscode.FileSystemWatcher { - return new FileSystemWatcher(this._mainContext, workspace, extension, this._onFileSystemEvent.event, typeConverter.GlobPattern.from(globPattern), ignoreCreateEvents, ignoreChangeEvents, ignoreDeleteEvents); + createFileSystemWatcher(workspace: IExtHostWorkspace, extension: IExtensionDescription, globPattern: vscode.GlobPattern, options?: FileSystemWatcherCreateOptions): vscode.FileSystemWatcher { + return new FileSystemWatcher(this._mainContext, workspace, extension, this._onFileSystemEvent.event, typeConverter.GlobPattern.from(globPattern), options); } $onFileEvent(events: FileSystemEvents) { this._onFileSystemEvent.fire(new LazyRevivedFileSystemEvents(events)); } - //--- file operations $onDidRunFileOperation(operation: FileOperation, files: SourceTargetPair[]): void { diff --git a/code/src/vs/workbench/api/common/extHostInlineChat.ts b/code/src/vs/workbench/api/common/extHostInlineChat.ts index 7e341e06a5f..3699d824d2f 100644 --- a/code/src/vs/workbench/api/common/extHostInlineChat.ts +++ b/code/src/vs/workbench/api/common/extHostInlineChat.ts @@ -18,6 +18,7 @@ import type * as vscode from 'vscode'; import { ApiCommand, ApiCommandArgument, ApiCommandResult, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { IRange } from 'vs/editor/common/core/range'; import { IPosition } from 'vs/editor/common/core/position'; +import { raceCancellation } from 'vs/base/common/async'; class ProviderWrapper { @@ -152,7 +153,7 @@ export class ExtHostInteractiveEditor implements ExtHostInlineChatShape { let done = false; - const progress: vscode.Progress<{ message?: string; edits?: vscode.TextEdit[] }> = { + const progress: vscode.Progress<{ message?: string; edits?: vscode.TextEdit[]; slashCommand?: vscode.InteractiveEditorSlashCommand }> = { report: async value => { if (!request.live) { throw new Error('Progress reporting is only supported for live sessions'); @@ -160,60 +161,61 @@ export class ExtHostInteractiveEditor implements ExtHostInlineChatShape { if (done || token.isCancellationRequested) { return; } - if (!value.message && !value.edits) { - return; - } await this._proxy.$handleProgressChunk(request.requestId, { message: value.message, - edits: value.edits?.map(typeConvert.TextEdit.from) + edits: value.edits?.map(typeConvert.TextEdit.from), + slashCommand: value.slashCommand?.command }); } }; - const task = entry.provider.provideInteractiveEditorResponse(sessionData.session, apiRequest, progress, token); + const task = Promise.resolve(entry.provider.provideInteractiveEditorResponse(sessionData.session, apiRequest, progress, token)); - Promise.resolve(task).finally(() => done = true); + let res: vscode.InteractiveEditorResponse | vscode.InteractiveEditorMessageResponse | null | undefined; + try { + res = await raceCancellation(task, token); + } finally { + done = true; + } - const res = await task; + if (!res) { + return undefined; + } - if (res) { - const id = sessionData.responses.push(res) - 1; + const id = sessionData.responses.push(res) - 1; - const stub: Partial = { - wholeRange: typeConvert.Range.from(res.wholeRange), - placeholder: res.placeholder, - }; + const stub: Partial = { + wholeRange: typeConvert.Range.from(res.wholeRange), + placeholder: res.placeholder, + }; - if (ExtHostInteractiveEditor._isMessageResponse(res)) { - return { - ...stub, - id, - type: InlineChatResponseType.Message, - message: typeConvert.MarkdownString.from(res.contents), - }; - } + if (ExtHostInteractiveEditor._isMessageResponse(res)) { + return { + ...stub, + id, + type: InlineChatResponseType.Message, + message: typeConvert.MarkdownString.from(res.contents), + }; + } - const { edits } = res; - if (edits instanceof extHostTypes.WorkspaceEdit) { - return { - ...stub, - id, - type: InlineChatResponseType.BulkEdit, - edits: typeConvert.WorkspaceEdit.from(edits), - }; + const { edits } = res; + if (edits instanceof extHostTypes.WorkspaceEdit) { + return { + ...stub, + id, + type: InlineChatResponseType.BulkEdit, + edits: typeConvert.WorkspaceEdit.from(edits), + }; - } else if (Array.isArray(edits)) { - return { - ...stub, - id, - type: InlineChatResponseType.EditorEdit, - edits: edits.map(typeConvert.TextEdit.from), - }; - } + } else { + return { + ...stub, + id, + type: InlineChatResponseType.EditorEdit, + edits: (edits).map(typeConvert.TextEdit.from), + }; } - - return undefined; } $handleFeedback(handle: number, sessionId: number, responseId: number, kind: InlineChatResponseFeedbackKind): void { diff --git a/code/src/vs/workbench/api/common/extHostLanguages.ts b/code/src/vs/workbench/api/common/extHostLanguages.ts index c56e3340760..78c5be8415e 100644 --- a/code/src/vs/workbench/api/common/extHostLanguages.ts +++ b/code/src/vs/workbench/api/common/extHostLanguages.ts @@ -14,6 +14,7 @@ import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; import { IURITransformer } from 'vs/base/common/uriIpc'; +import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; export class ExtHostLanguages implements ExtHostLanguagesShape { @@ -90,7 +91,7 @@ export class ExtHostLanguages implements ExtHostLanguagesShape { } ids.add(fullyQualifiedId); - const data: Omit = { + const data: Omit = { selector, id, name: extension.displayName ?? extension.name, @@ -160,6 +161,15 @@ export class ExtHostLanguages implements ExtHostLanguagesShape { data.text = value; updateAsync(); }, + set text2(value) { + checkProposedApiEnabled(extension, 'languageStatusText'); + data.text = value; + updateAsync(); + }, + get text2() { + checkProposedApiEnabled(extension, 'languageStatusText'); + return data.text; + }, get detail() { return data.detail; }, diff --git a/code/src/vs/workbench/api/common/extHostSpeech.ts b/code/src/vs/workbench/api/common/extHostSpeech.ts new file mode 100644 index 00000000000..8207ab47a47 --- /dev/null +++ b/code/src/vs/workbench/api/common/extHostSpeech.ts @@ -0,0 +1,66 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { ExtHostSpeechShape, IMainContext, MainContext, MainThreadSpeechShape } from 'vs/workbench/api/common/extHost.protocol'; +import type * as vscode from 'vscode'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; + +export class ExtHostSpeech implements ExtHostSpeechShape { + + private static ID_POOL = 1; + + private readonly proxy: MainThreadSpeechShape; + + private readonly providers = new Map(); + private readonly sessions = new Map(); + + constructor( + mainContext: IMainContext + ) { + this.proxy = mainContext.getProxy(MainContext.MainThreadSpeech); + } + + async $createSpeechToTextSession(handle: number, session: number): Promise { + const provider = this.providers.get(handle); + if (!provider) { + return; + } + + const disposables = new DisposableStore(); + + const cts = new CancellationTokenSource(); + this.sessions.set(session, cts); + + const speechToTextSession = disposables.add(provider.provideSpeechToTextSession(cts.token)); + disposables.add(speechToTextSession.onDidChange(e => { + if (cts.token.isCancellationRequested) { + return; + } + + this.proxy.$emitSpeechToTextEvent(session, e); + })); + + disposables.add(cts.token.onCancellationRequested(() => disposables.dispose())); + } + + async $cancelSpeechToTextSession(session: number): Promise { + this.sessions.get(session)?.dispose(true); + this.sessions.delete(session); + } + + registerProvider(extension: ExtensionIdentifier, identifier: string, provider: vscode.SpeechProvider): IDisposable { + const handle = ExtHostSpeech.ID_POOL++; + + this.providers.set(handle, provider); + this.proxy.$registerProvider(handle, identifier, { extension, displayName: extension.value }); + + return toDisposable(() => { + this.proxy.$unregisterProvider(handle); + this.providers.delete(handle); + }); + } +} diff --git a/code/src/vs/workbench/api/common/extHostTypeConverters.ts b/code/src/vs/workbench/api/common/extHostTypeConverters.ts index 48159f1a07e..b3ba3306276 100644 --- a/code/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/code/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2236,6 +2236,15 @@ export namespace ChatMessageRole { } export namespace ChatVariable { + export function objectTo(variableObject: Record): Record { + const result: Record = {}; + for (const key of Object.keys(variableObject)) { + result[key] = variableObject[key].map(ChatVariable.to); + } + + return result; + } + export function to(variable: IChatRequestVariableValue): vscode.ChatVariableValue { return { level: ChatVariableLevel.to(variable.level), @@ -2292,6 +2301,48 @@ export namespace InteractiveEditorResponseFeedbackKind { } } +export namespace ChatResponseProgress { + export function from(progress: vscode.InteractiveProgress | vscode.ChatAgentProgress): extHostProtocol.IChatResponseProgressDto { + if ('placeholder' in progress && 'resolvedContent' in progress) { + return { placeholder: progress.placeholder }; + } else if ('responseId' in progress) { + return { requestId: progress.responseId }; + } else if ('content' in progress) { + return { + content: 'markdownContent' in progress ? progress.markdownContent : + (typeof progress.content === 'string' ? progress.content : MarkdownString.from(progress.content)) + }; + } else if ('documents' in progress) { + return { + documents: progress.documents.map(d => ({ + uri: d.uri, + version: d.version, + ranges: d.ranges.map(r => Range.from(r)) + })) + }; + } else if ('reference' in progress) { + return { + reference: 'uri' in progress.reference ? + { + uri: progress.reference.uri, + range: Range.from(progress.reference.range) + } : progress.reference + }; + } else if ('inlineReference' in progress) { + return { + inlineReference: 'uri' in progress.inlineReference ? + { + uri: progress.inlineReference.uri, + range: Range.from(progress.inlineReference.range) + } : progress.inlineReference, + title: progress.title, + }; + } else { + return progress; + } + } +} + export namespace TerminalQuickFix { export function from(quickFix: vscode.TerminalQuickFixExecuteTerminalCommand | vscode.TerminalQuickFixOpener | vscode.Command, converter: Command.ICommandsConverter, disposables: DisposableStore): extHostProtocol.ITerminalQuickFixExecuteTerminalCommandDto | extHostProtocol.ITerminalQuickFixOpenerDto | extHostProtocol.ICommandDto | undefined { diff --git a/code/src/vs/workbench/api/common/extHostTypes.ts b/code/src/vs/workbench/api/common/extHostTypes.ts index 670dffdcbb4..850cbec1dbd 100644 --- a/code/src/vs/workbench/api/common/extHostTypes.ts +++ b/code/src/vs/workbench/api/common/extHostTypes.ts @@ -4083,8 +4083,8 @@ export class InteractiveWindowInput { //#region Interactive session export enum InteractiveSessionVoteDirection { - Up = 1, - Down = 2 + Down = 0, + Up = 1 } export enum InteractiveSessionCopyKind { @@ -4128,6 +4128,11 @@ export class ChatMessage implements vscode.ChatMessage { } } +export enum ChatAgentResultFeedbackKind { + Unhelpful = 0, + Helpful = 1, +} + //#endregion //#region ai @@ -4140,3 +4145,14 @@ export enum RelatedInformationType { } //#endregion + +//#region Speech + +export enum SpeechToTextStatus { + Started = 1, + Recognizing = 2, + Recognized = 3, + Stopped = 4 +} + +//#endregion diff --git a/code/src/vs/workbench/api/test/browser/extHostFileSystemEventService.test.ts b/code/src/vs/workbench/api/test/browser/extHostFileSystemEventService.test.ts index 5063354ee59..7c42f7e90ee 100644 --- a/code/src/vs/workbench/api/test/browser/extHostFileSystemEventService.test.ts +++ b/code/src/vs/workbench/api/test/browser/extHostFileSystemEventService.test.ts @@ -22,13 +22,13 @@ suite('ExtHostFileSystemEventService', () => { drain: undefined! }; - const watcher1 = new ExtHostFileSystemEventService(protocol, new NullLogService(), undefined!).createFileSystemWatcher(undefined!, undefined!, '**/somethingInteresting', false, false, false); + const watcher1 = new ExtHostFileSystemEventService(protocol, new NullLogService(), undefined!).createFileSystemWatcher(undefined!, undefined!, '**/somethingInteresting', {}); assert.strictEqual(watcher1.ignoreChangeEvents, false); assert.strictEqual(watcher1.ignoreCreateEvents, false); assert.strictEqual(watcher1.ignoreDeleteEvents, false); watcher1.dispose(); - const watcher2 = new ExtHostFileSystemEventService(protocol, new NullLogService(), undefined!).createFileSystemWatcher(undefined!, undefined!, '**/somethingBoring', true, true, true); + const watcher2 = new ExtHostFileSystemEventService(protocol, new NullLogService(), undefined!).createFileSystemWatcher(undefined!, undefined!, '**/somethingBoring', { ignoreCreateEvents: true, ignoreChangeEvents: true, ignoreDeleteEvents: true }); assert.strictEqual(watcher2.ignoreChangeEvents, true); assert.strictEqual(watcher2.ignoreCreateEvents, true); assert.strictEqual(watcher2.ignoreDeleteEvents, true); diff --git a/code/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts b/code/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts index 249e6316dba..ada06633cf8 100644 --- a/code/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts +++ b/code/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as nls from 'vs/nls'; import * as assert from 'assert'; import { mock } from 'vs/base/test/common/mock'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; @@ -56,11 +57,11 @@ suite('MainThreadHostTreeView', function () { const instantiationService: TestInstantiationService = workbenchInstantiationService(undefined, disposables); const viewDescriptorService = disposables.add(instantiationService.createInstance(ViewDescriptorService)); instantiationService.stub(IViewDescriptorService, viewDescriptorService); - container = Registry.as(Extensions.ViewContainersRegistry).registerViewContainer({ id: 'testContainer', title: { value: 'test', original: 'test' }, ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + container = Registry.as(Extensions.ViewContainersRegistry).registerViewContainer({ id: 'testContainer', title: nls.localize2('test', 'test'), ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const viewDescriptor: ITreeViewDescriptor = { id: testTreeViewId, ctorDescriptor: null!, - name: 'Test View 1', + name: nls.localize2('Test View 1', 'Test View 1'), treeView: disposables.add(instantiationService.createInstance(CustomTreeView, 'testTree', 'Test Title', 'extension.id')), }; ViewsRegistry.registerViews([viewDescriptor], container); diff --git a/code/src/vs/workbench/browser/actions/layoutActions.ts b/code/src/vs/workbench/browser/actions/layoutActions.ts index ff2c95c7187..c39144529d6 100644 --- a/code/src/vs/workbench/browser/actions/layoutActions.ts +++ b/code/src/vs/workbench/browser/actions/layoutActions.ts @@ -7,7 +7,7 @@ import { localize } from 'vs/nls'; import { MenuId, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IWorkbenchLayoutService, Parts, Position, positionToString } from 'vs/workbench/services/layout/browser/layoutService'; +import { ActivityBarPosition, IWorkbenchLayoutService, LayoutSettings, Parts, Position, positionToString } from 'vs/workbench/services/layout/browser/layoutService'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; import { isWindows, isLinux, isWeb, isMacintosh, isNative } from 'vs/base/common/platform'; @@ -73,35 +73,20 @@ export class ToggleActivityBarVisibilityAction extends Action2 { static readonly ID = 'workbench.action.toggleActivityBarVisibility'; - private static readonly activityBarVisibleKey = 'workbench.activityBar.visible'; - constructor() { super({ id: ToggleActivityBarVisibilityAction.ID, title: { value: localize('toggleActivityBar', "Toggle Activity Bar Visibility"), - mnemonicTitle: localize({ key: 'miActivityBar', comment: ['&& denotes a mnemonic'] }, "&&Activity Bar"), original: 'Toggle Activity Bar Visibility' }, - category: Categories.View, - f1: true, - toggled: ContextKeyExpr.equals('config.workbench.activityBar.visible', true), - menu: [{ - id: MenuId.MenubarAppearanceMenu, - group: '2_workbench_layout', - order: 4 - }] }); } run(accessor: ServicesAccessor): void { - const layoutService = accessor.get(IWorkbenchLayoutService); const configurationService = accessor.get(IConfigurationService); - - const visibility = layoutService.isVisible(Parts.ACTIVITYBAR_PART); - const newVisibilityValue = !visibility; - - configurationService.updateValue(ToggleActivityBarVisibilityAction.activityBarVisibleKey, newVisibilityValue); + const value = configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION); + configurationService.updateValue(LayoutSettings.ACTIVITY_BAR_LOCATION, value === ActivityBarPosition.HIDDEN ? undefined : ActivityBarPosition.HIDDEN); } } @@ -719,7 +704,7 @@ registerAction2(class extends Action2 { results.push({ id: viewDescriptor.id, - label: viewDescriptor.name + label: viewDescriptor.name.value }); } }); @@ -743,7 +728,7 @@ registerAction2(class extends Action2 { results.push({ id: viewDescriptor.id, - label: viewDescriptor.name + label: viewDescriptor.name.value }); } }); @@ -768,7 +753,7 @@ registerAction2(class extends Action2 { results.push({ id: viewDescriptor.id, - label: viewDescriptor.name + label: viewDescriptor.name.value }); } }); @@ -842,7 +827,7 @@ class MoveFocusedViewAction extends Action2 { const quickPick = quickInputService.createQuickPick(); quickPick.placeholder = localize('moveFocusedView.selectDestination', "Select a Destination for the View"); - quickPick.title = localize({ key: 'moveFocusedView.title', comment: ['{0} indicates the title of the view the user has selected to move.'] }, "View: Move {0}", viewDescriptor.name); + quickPick.title = localize({ key: 'moveFocusedView.title', comment: ['{0} indicates the title of the view the user has selected to move.'] }, "View: Move {0}", viewDescriptor.name.value); const items: Array = []; const currentContainer = viewDescriptorService.getViewContainerByViewId(focusedViewId)!; @@ -939,16 +924,16 @@ class MoveFocusedViewAction extends Action2 { const destination = quickPick.selectedItems[0]; if (destination.id === '_.panel.newcontainer') { - viewDescriptorService.moveViewToLocation(viewDescriptor!, ViewContainerLocation.Panel); + viewDescriptorService.moveViewToLocation(viewDescriptor!, ViewContainerLocation.Panel, this.desc.id); viewsService.openView(focusedViewId, true); } else if (destination.id === '_.sidebar.newcontainer') { - viewDescriptorService.moveViewToLocation(viewDescriptor!, ViewContainerLocation.Sidebar); + viewDescriptorService.moveViewToLocation(viewDescriptor!, ViewContainerLocation.Sidebar, this.desc.id); viewsService.openView(focusedViewId, true); } else if (destination.id === '_.auxiliarybar.newcontainer') { - viewDescriptorService.moveViewToLocation(viewDescriptor!, ViewContainerLocation.AuxiliaryBar); + viewDescriptorService.moveViewToLocation(viewDescriptor!, ViewContainerLocation.AuxiliaryBar, this.desc.id); viewsService.openView(focusedViewId, true); } else if (destination.id) { - viewDescriptorService.moveViewsToContainer([viewDescriptor], viewDescriptorService.getViewContainerById(destination.id)!); + viewDescriptorService.moveViewsToContainer([viewDescriptor], viewDescriptorService.getViewContainerById(destination.id)!, undefined, this.desc.id); viewsService.openView(focusedViewId, true); } @@ -1001,7 +986,7 @@ registerAction2(class extends Action2 { return; } - viewDescriptorService.moveViewsToContainer([viewDescriptor], defaultContainer); + viewDescriptorService.moveViewsToContainer([viewDescriptor], defaultContainer, undefined, this.desc.id); viewsService.openView(viewDescriptor.id, true); } }); diff --git a/code/src/vs/workbench/browser/actions/workspaceActions.ts b/code/src/vs/workbench/browser/actions/workspaceActions.ts index cbb08e8ff09..3f2e2799fa5 100644 --- a/code/src/vs/workbench/browser/actions/workspaceActions.ts +++ b/code/src/vs/workbench/browser/actions/workspaceActions.ts @@ -36,8 +36,8 @@ export class OpenFileAction extends Action2 { title: { value: localize('openFile', "Open File..."), original: 'Open File...' }, category: Categories.File, f1: true, - precondition: IsMacNativeContext.toNegated(), keybinding: { + when: IsMacNativeContext.toNegated(), weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.KeyO } diff --git a/code/src/vs/workbench/browser/composite.ts b/code/src/vs/workbench/browser/composite.ts index 6072689b476..88cdf8e08d4 100644 --- a/code/src/vs/workbench/browser/composite.ts +++ b/code/src/vs/workbench/browser/composite.ts @@ -152,7 +152,7 @@ export abstract class Composite extends Component implements IComposite { if (container) { // Make sure to focus the window of the container // because it is possible that the composite is - // opened in a auxiliary window that is not focussed. + // opened in a auxiliary window that is not focused. focusWindow(container); } } diff --git a/code/src/vs/workbench/browser/contextkeys.ts b/code/src/vs/workbench/browser/contextkeys.ts index feca5c8da58..c77d98a5758 100644 --- a/code/src/vs/workbench/browser/contextkeys.ts +++ b/code/src/vs/workbench/browser/contextkeys.ts @@ -7,7 +7,7 @@ import { Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IContextKeyService, IContextKey, setConstant as setConstantContextKey } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext, ProductQualityContext, IsMobileContext } from 'vs/platform/contextkey/common/contextkeys'; -import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, ActiveEditorCanToggleReadonlyContext, applyAvailableEditorIds } from 'vs/workbench/common/contextkeys'; +import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, ActiveEditorCanToggleReadonlyContext, applyAvailableEditorIds, TitleBarVisibleContext } from 'vs/workbench/common/contextkeys'; import { TEXT_DIFF_EDITOR_ID, EditorInputCapabilities, SIDE_BY_SIDE_EDITOR_ID, EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; import { trackFocus, addDisposableListener, EventType } from 'vs/base/browser/dom'; import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -74,6 +74,7 @@ export class WorkbenchContextKeysHandler extends Disposable { private panelMaximizedContext: IContextKey; private auxiliaryBarVisibleContext: IContextKey; private editorTabsVisibleContext: IContextKey; + private titleAreaVisibleContext: IContextKey; constructor( @IContextKeyService private readonly contextKeyService: IContextKeyService, @@ -195,6 +196,10 @@ export class WorkbenchContextKeysHandler extends Disposable { // Sidebar this.sideBarVisibleContext = SideBarVisibleContext.bindTo(this.contextKeyService); + // Title Bar + this.titleAreaVisibleContext = TitleBarVisibleContext.bindTo(this.contextKeyService); + this.updateTitleBarContextKeys(); + // Panel this.panelPositionContext = PanelPositionContext.bindTo(this.contextKeyService); this.panelPositionContext.set(positionToString(this.layoutService.getPanelPosition())); @@ -259,6 +264,7 @@ export class WorkbenchContextKeysHandler extends Disposable { this.panelVisibleContext.set(this.layoutService.isVisible(Parts.PANEL_PART)); this.panelMaximizedContext.set(this.layoutService.isPanelMaximized()); this.auxiliaryBarVisibleContext.set(this.layoutService.isVisible(Parts.AUXILIARYBAR_PART)); + this.updateTitleBarContextKeys(); })); this._register(this.workingCopyService.onDidChangeDirty(workingCopy => this.dirtyWorkingCopiesContext.set(workingCopy.isDirty() || this.workingCopyService.hasDirty))); @@ -367,6 +373,10 @@ export class WorkbenchContextKeysHandler extends Disposable { this.sideBarVisibleContext.set(this.layoutService.isVisible(Parts.SIDEBAR_PART)); } + private updateTitleBarContextKeys(): void { + this.titleAreaVisibleContext.set(this.layoutService.isVisible(Parts.TITLEBAR_PART)); + } + private updateWorkspaceContextKeys(): void { this.virtualWorkspaceContext.set(getVirtualWorkspaceScheme(this.contextService.getWorkspace()) || ''); this.temporaryWorkspaceContext.set(isTemporaryWorkspace(this.contextService.getWorkspace())); diff --git a/code/src/vs/workbench/browser/layout.ts b/code/src/vs/workbench/browser/layout.ts index 1ccffa5770e..4f33c7fc37d 100644 --- a/code/src/vs/workbench/browser/layout.ts +++ b/code/src/vs/workbench/browser/layout.ts @@ -12,7 +12,7 @@ import { isWindows, isLinux, isMacintosh, isWeb, isNative, isIOS } from 'vs/base import { EditorInputCapabilities, GroupIdentifier, isResourceEditorInput, IUntypedEditorInput, pathsToEditors } from 'vs/workbench/common/editor'; import { SidebarPart } from 'vs/workbench/browser/parts/sidebar/sidebarPart'; import { PanelPart } from 'vs/workbench/browser/parts/panel/panelPart'; -import { Position, Parts, PanelOpensMaximizedOptions, IWorkbenchLayoutService, positionFromString, positionToString, panelOpensMaximizedFromString, PanelAlignment } from 'vs/workbench/services/layout/browser/layoutService'; +import { Position, Parts, PanelOpensMaximizedOptions, IWorkbenchLayoutService, positionFromString, positionToString, panelOpensMaximizedFromString, PanelAlignment, ActivityBarPosition, LayoutSettings } from 'vs/workbench/services/layout/browser/layoutService'; import { isTemporaryWorkspace, IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IStorageService, StorageScope, StorageTarget, WillSaveStateReason } from 'vs/platform/storage/common/storage'; import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -45,7 +45,6 @@ import { ILogService } from 'vs/platform/log/common/log'; import { DeferredPromise, Promises } from 'vs/base/common/async'; import { IBannerService } from 'vs/workbench/services/banner/browser/bannerService'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; -import { ActivitybarPart } from 'vs/workbench/browser/parts/activitybar/activitybarPart'; import { AuxiliaryBarPart } from 'vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -166,9 +165,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private getContainerFromDocument(document: Document): HTMLElement { if (document === this.container.ownerDocument) { + // main window return this.container; } else { - return document.body.children[0] as HTMLElement; // TODO@bpasero a bit of a hack + // auxiliary window + return document.body.getElementsByClassName('monaco-workbench')[0] as HTMLElement; } } @@ -293,7 +294,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Configuration changes this._register(this.configurationService.onDidChangeConfiguration((e) => { if ([ - LegacyWorkbenchLayoutSettings.ACTIVITYBAR_VISIBLE, + LayoutSettings.ACTIVITY_BAR_LOCATION, LegacyWorkbenchLayoutSettings.SIDEBAR_POSITION, LegacyWorkbenchLayoutSettings.STATUSBAR_VISIBLE, 'window.menuBarVisibility', @@ -592,6 +593,13 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } } + // Activity bar cannot be hidden + // This check must be called after state is set + // because canActivityBarBeHidden calls isVisible + if (this.stateModel.getRuntimeValue(LayoutStateKeys.ACTIVITYBAR_HIDDEN) && !this.canActivityBarBeHidden()) { + this.stateModel.setRuntimeValue(LayoutStateKeys.ACTIVITYBAR_HIDDEN, false); + } + // Window border this.updateWindowBorder(true); } @@ -1019,7 +1027,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi break; } case Parts.ACTIVITYBAR_PART: - (this.getPart(Parts.ACTIVITYBAR_PART) as ActivitybarPart).focus(); + (this.getPart(Parts.SIDEBAR_PART) as SidebarPart).focusActivityBar(); break; case Parts.STATUSBAR_PART: this.statusBarService.focus(); @@ -1521,11 +1529,18 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } private setActivityBarHidden(hidden: boolean, skipLayout?: boolean): void { - // Propagate to grid + if (hidden && !this.canActivityBarBeHidden()) { + return; + } this.stateModel.setRuntimeValue(LayoutStateKeys.ACTIVITYBAR_HIDDEN, hidden); + // Propagate to grid this.workbenchGrid.setViewVisible(this.activityBarPartView, !hidden); } + private canActivityBarBeHidden(): boolean { + return !(this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.TOP && !this.isVisible(Parts.TITLEBAR_PART)); + } + private setBannerHidden(hidden: boolean): void { this.workbenchGrid.setViewVisible(this.bannerPartView, !hidden); } @@ -2394,7 +2409,6 @@ enum WorkbenchLayoutSettings { } enum LegacyWorkbenchLayoutSettings { - ACTIVITYBAR_VISIBLE = 'workbench.activityBar.visible', // Deprecated to UI State STATUSBAR_VISIBLE = 'workbench.statusBar.visible', // Deprecated to UI State SIDEBAR_POSITION = 'workbench.sideBar.location', // Deprecated to UI State } @@ -2422,8 +2436,8 @@ class LayoutStateModel extends Disposable { private updateStateFromLegacySettings(configurationChangeEvent: IConfigurationChangeEvent): void { const isZenMode = this.getRuntimeValue(LayoutStateKeys.ZEN_MODE_ACTIVE); - if (configurationChangeEvent.affectsConfiguration(LegacyWorkbenchLayoutSettings.ACTIVITYBAR_VISIBLE) && !isZenMode) { - this.setRuntimeValueAndFire(LayoutStateKeys.ACTIVITYBAR_HIDDEN, !this.configurationService.getValue(LegacyWorkbenchLayoutSettings.ACTIVITYBAR_VISIBLE)); + if (configurationChangeEvent.affectsConfiguration(LayoutSettings.ACTIVITY_BAR_LOCATION) && !isZenMode) { + this.setRuntimeValueAndFire(LayoutStateKeys.ACTIVITYBAR_HIDDEN, this.isActivityBarHidden()); } if (configurationChangeEvent.affectsConfiguration(LegacyWorkbenchLayoutSettings.STATUSBAR_VISIBLE) && !isZenMode) { @@ -2442,7 +2456,7 @@ class LayoutStateModel extends Disposable { } if (key === LayoutStateKeys.ACTIVITYBAR_HIDDEN) { - this.configurationService.updateValue(LegacyWorkbenchLayoutSettings.ACTIVITYBAR_VISIBLE, !value); + this.configurationService.updateValue(LayoutSettings.ACTIVITY_BAR_LOCATION, value ? ActivityBarPosition.HIDDEN : undefined); } else if (key === LayoutStateKeys.STATUSBAR_HIDDEN) { this.configurationService.updateValue(LegacyWorkbenchLayoutSettings.STATUSBAR_VISIBLE, !value); } else if (key === LayoutStateKeys.SIDEBAR_POSITON) { @@ -2464,7 +2478,7 @@ class LayoutStateModel extends Disposable { } // Apply legacy settings - this.stateCache.set(LayoutStateKeys.ACTIVITYBAR_HIDDEN.name, !this.configurationService.getValue(LegacyWorkbenchLayoutSettings.ACTIVITYBAR_VISIBLE)); + this.stateCache.set(LayoutStateKeys.ACTIVITYBAR_HIDDEN.name, this.isActivityBarHidden()); this.stateCache.set(LayoutStateKeys.STATUSBAR_HIDDEN.name, !this.configurationService.getValue(LegacyWorkbenchLayoutSettings.STATUSBAR_VISIBLE)); this.stateCache.set(LayoutStateKeys.SIDEBAR_POSITON.name, positionFromString(this.configurationService.getValue(LegacyWorkbenchLayoutSettings.SIDEBAR_POSITION) ?? 'left')); @@ -2533,7 +2547,7 @@ class LayoutStateModel extends Disposable { if (fallbackToSetting) { switch (key) { case LayoutStateKeys.ACTIVITYBAR_HIDDEN: - this.stateCache.set(key.name, !this.configurationService.getValue(LegacyWorkbenchLayoutSettings.ACTIVITYBAR_VISIBLE)); + this.stateCache.set(key.name, this.isActivityBarHidden()); break; case LayoutStateKeys.STATUSBAR_HIDDEN: this.stateCache.set(key.name, !this.configurationService.getValue(LegacyWorkbenchLayoutSettings.STATUSBAR_VISIBLE)); @@ -2559,6 +2573,14 @@ class LayoutStateModel extends Disposable { } } + private isActivityBarHidden(): boolean { + const oldValue = this.configurationService.getValue('workbench.activityBar.visible'); + if (oldValue !== undefined) { + return !oldValue; + } + return this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) !== ActivityBarPosition.SIDE; + } + private setRuntimeValueAndFire(key: RuntimeStateKey, value: T): void { const previousValue = this.stateCache.get(key.name); if (previousValue === value) { diff --git a/code/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/code/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts deleted file mode 100644 index a193f290747..00000000000 --- a/code/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ /dev/null @@ -1,632 +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 'vs/css!./media/activityaction'; -import { localize } from 'vs/nls'; -import { EventType, addDisposableListener, EventHelper, append, $, clearNode, hide, show } from 'vs/base/browser/dom'; -import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch'; -import { Action, IAction, Separator, SubmenuAction, toAction } from 'vs/base/common/actions'; -import { KeyCode } from 'vs/base/common/keyCodes'; -import { DisposableStore } from 'vs/base/common/lifecycle'; -import { IMenuService, MenuId, IMenu, registerAction2, Action2, IAction2Options } from 'vs/platform/actions/common/actions'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { activeContrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry'; -import { IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { ActivityAction, ActivityActionViewItem, IActivityActionViewItemOptions, IActivityHoverOptions, ICompositeBarColors } from 'vs/workbench/browser/parts/compositeBarActions'; -import { Categories } from 'vs/platform/action/common/actionCommonCategories'; -import { IActivity } from 'vs/workbench/common/activity'; -import { ACTIVITY_BAR_ACTIVE_FOCUS_BORDER, ACTIVITY_BAR_ACTIVE_BACKGROUND, ACTIVITY_BAR_ACTIVE_BORDER } from 'vs/workbench/common/theme'; -import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { AuthenticationSessionInfo, getCurrentAuthenticationSessionInfo } from 'vs/workbench/services/authentication/browser/authenticationService'; -import { AuthenticationSessionAccount, IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IProductService } from 'vs/platform/product/common/productService'; -import { AnchorAlignment, AnchorAxisAlignment } from 'vs/base/browser/ui/contextview/contextview'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; -import { ViewContainerLocation } from 'vs/workbench/common/views'; -import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; -import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { ILogService } from 'vs/platform/log/common/log'; -import { ISecretStorageService } from 'vs/platform/secrets/common/secrets'; -import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { runWhenIdle } from 'vs/base/common/async'; -import { Lazy } from 'vs/base/common/lazy'; -import { DEFAULT_ICON } from 'vs/workbench/services/userDataProfile/common/userDataProfileIcons'; - -abstract class AbstractGlobalActivityActionViewItem extends ActivityActionViewItem { - - constructor( - action: ActivityAction, - private contextMenuActionsProvider: () => IAction[], - options: IActivityActionViewItemOptions, - @IThemeService themeService: IThemeService, - @IHoverService hoverService: IHoverService, - @IMenuService protected readonly menuService: IMenuService, - @IContextMenuService protected readonly contextMenuService: IContextMenuService, - @IContextKeyService protected readonly contextKeyService: IContextKeyService, - @IConfigurationService configurationService: IConfigurationService, - @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService, - @IKeybindingService keybindingService: IKeybindingService, - ) { - super(action, options, () => true, themeService, hoverService, configurationService, keybindingService); - } - - override render(container: HTMLElement): void { - super.render(container); - - this._register(addDisposableListener(this.container, EventType.MOUSE_DOWN, async (e: MouseEvent) => { - EventHelper.stop(e, true); - const isLeftClick = e?.button !== 2; - // Left-click run - if (isLeftClick) { - this.run(); - } - })); - - // The rest of the activity bar uses context menu event for the context menu, so we match this - this._register(addDisposableListener(this.container, EventType.CONTEXT_MENU, async (e: MouseEvent) => { - const disposables = new DisposableStore(); - const actions = await this.resolveContextMenuActions(disposables); - - const event = new StandardMouseEvent(e); - - this.contextMenuService.showContextMenu({ - getAnchor: () => event, - getActions: () => actions, - onHide: () => disposables.dispose() - }); - })); - - this._register(addDisposableListener(this.container, EventType.KEY_UP, (e: KeyboardEvent) => { - const event = new StandardKeyboardEvent(e); - if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) { - EventHelper.stop(e, true); - this.run(); - } - })); - - this._register(addDisposableListener(this.container, TouchEventType.Tap, (e: GestureEvent) => { - EventHelper.stop(e, true); - this.run(); - })); - } - - protected async resolveContextMenuActions(disposables: DisposableStore): Promise { - return this.contextMenuActionsProvider(); - } - - protected abstract run(): Promise; -} - -class MenuActivityActionViewItem extends AbstractGlobalActivityActionViewItem { - - constructor( - private readonly menuId: MenuId, - action: ActivityAction, - contextMenuActionsProvider: () => IAction[], - icon: boolean, - colors: (theme: IColorTheme) => ICompositeBarColors, - hoverOptions: IActivityHoverOptions, - @IThemeService themeService: IThemeService, - @IHoverService hoverService: IHoverService, - @IMenuService menuService: IMenuService, - @IContextMenuService contextMenuService: IContextMenuService, - @IContextKeyService contextKeyService: IContextKeyService, - @IConfigurationService configurationService: IConfigurationService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, - @IKeybindingService keybindingService: IKeybindingService, - ) { - super(action, contextMenuActionsProvider, { draggable: false, colors, icon, hasPopup: true, hoverOptions }, themeService, hoverService, menuService, contextMenuService, contextKeyService, configurationService, environmentService, keybindingService); - } - - protected async run(): Promise { - const disposables = new DisposableStore(); - const menu = disposables.add(this.menuService.createMenu(this.menuId, this.contextKeyService)); - const actions = await this.resolveMainMenuActions(menu, disposables); - - this.contextMenuService.showContextMenu({ - getAnchor: () => this.container, - anchorAlignment: this.configurationService.getValue('workbench.sideBar.location') === 'left' ? AnchorAlignment.RIGHT : AnchorAlignment.LEFT, - anchorAxisAlignment: AnchorAxisAlignment.HORIZONTAL, - getActions: () => actions, - onHide: () => disposables.dispose(), - menuActionOptions: { renderShortTitle: true }, - }); - - } - - protected async resolveMainMenuActions(menu: IMenu, _disposable: DisposableStore): Promise { - const actions: IAction[] = []; - createAndFillInActionBarActions(menu, { renderShortTitle: true }, { primary: [], secondary: actions }); - return actions; - } - -} - -export class AccountsActivityActionViewItem extends MenuActivityActionViewItem { - - static readonly ACCOUNTS_VISIBILITY_PREFERENCE_KEY = 'workbench.activity.showAccounts'; - - private readonly groupedAccounts: Map = new Map(); - private readonly problematicProviders: Set = new Set(); - - private initialized = false; - private sessionFromEmbedder = new Lazy>(() => getCurrentAuthenticationSessionInfo(this.secretStorageService, this.productService)); - - constructor( - action: ActivityAction, - contextMenuActionsProvider: () => IAction[], - colors: (theme: IColorTheme) => ICompositeBarColors, - activityHoverOptions: IActivityHoverOptions, - @IThemeService themeService: IThemeService, - @ILifecycleService private readonly lifecycleService: ILifecycleService, - @IHoverService hoverService: IHoverService, - @IContextMenuService contextMenuService: IContextMenuService, - @IMenuService menuService: IMenuService, - @IContextKeyService contextKeyService: IContextKeyService, - @IAuthenticationService private readonly authenticationService: IAuthenticationService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, - @IProductService private readonly productService: IProductService, - @IConfigurationService configurationService: IConfigurationService, - @IStorageService private readonly storageService: IStorageService, - @IKeybindingService keybindingService: IKeybindingService, - @ISecretStorageService private readonly secretStorageService: ISecretStorageService, - @ILogService private readonly logService: ILogService - ) { - super(MenuId.AccountsContext, action, contextMenuActionsProvider, true, colors, activityHoverOptions, themeService, hoverService, menuService, contextMenuService, contextKeyService, configurationService, environmentService, keybindingService); - this.registerListeners(); - this.initialize(); - } - - private registerListeners(): void { - this._register(this.authenticationService.onDidRegisterAuthenticationProvider(async (e) => { - await this.addAccountsFromProvider(e.id); - })); - - this._register(this.authenticationService.onDidUnregisterAuthenticationProvider((e) => { - this.groupedAccounts.delete(e.id); - this.problematicProviders.delete(e.id); - })); - - this._register(this.authenticationService.onDidChangeSessions(async e => { - for (const changed of [...e.event.changed, ...e.event.added]) { - try { - await this.addOrUpdateAccount(e.providerId, changed.account); - } catch (e) { - this.logService.error(e); - } - } - for (const removed of e.event.removed) { - this.removeAccount(e.providerId, removed.account); - } - })); - } - - // This function exists to ensure that the accounts are added for auth providers that had already been registered - // before the menu was created. - private async initialize(): Promise { - // Resolving the menu doesn't need to happen immediately, so we can wait until after the workbench has been restored - // and only run this when the system is idle. - await this.lifecycleService.when(LifecyclePhase.Restored); - const disposable = this._register(runWhenIdle(async () => { - await this.doInitialize(); - disposable.dispose(); - })); - } - - private async doInitialize(): Promise { - const providerIds = this.authenticationService.getProviderIds(); - const results = await Promise.allSettled(providerIds.map(providerId => this.addAccountsFromProvider(providerId))); - - // Log any errors that occurred while initializing. We try to be best effort here to show the most amount of accounts - for (const result of results) { - if (result.status === 'rejected') { - this.logService.error(result.reason); - } - } - - this.initialized = true; - } - - //#region overrides - - protected override async resolveMainMenuActions(accountsMenu: IMenu, disposables: DisposableStore): Promise { - await super.resolveMainMenuActions(accountsMenu, disposables); - - const providers = this.authenticationService.getProviderIds(); - const otherCommands = accountsMenu.getActions(); - let menus: IAction[] = []; - - for (const providerId of providers) { - if (!this.initialized) { - const noAccountsAvailableAction = disposables.add(new Action('noAccountsAvailable', localize('loading', "Loading..."), undefined, false)); - menus.push(noAccountsAvailableAction); - break; - } - const providerLabel = this.authenticationService.getLabel(providerId); - const accounts = this.groupedAccounts.get(providerId); - if (!accounts) { - if (this.problematicProviders.has(providerId)) { - const providerUnavailableAction = disposables.add(new Action('providerUnavailable', localize('authProviderUnavailable', '{0} is currently unavailable', providerLabel), undefined, false)); - menus.push(providerUnavailableAction); - // try again in the background so that if the failure was intermittent, we can resolve it on the next showing of the menu - try { - await this.addAccountsFromProvider(providerId); - } catch (e) { - this.logService.error(e); - } - } - continue; - } - - for (const account of accounts) { - const manageExtensionsAction = disposables.add(new Action(`configureSessions${account.label}`, localize('manageTrustedExtensions', "Manage Trusted Extensions"), undefined, true, () => { - return this.authenticationService.manageTrustedExtensionsForAccount(providerId, account.label); - })); - - const providerSubMenuActions: Action[] = [manageExtensionsAction]; - - if (account.canSignOut) { - const signOutAction = disposables.add(new Action('signOut', localize('signOut', "Sign Out"), undefined, true, async () => { - const allSessions = await this.authenticationService.getSessions(providerId); - const sessionsForAccount = allSessions.filter(s => s.account.label === account.label); - return await this.authenticationService.removeAccountSessions(providerId, account.label, sessionsForAccount); - })); - providerSubMenuActions.push(signOutAction); - } - - const providerSubMenu = new SubmenuAction('activitybar.submenu', `${account.label} (${providerLabel})`, providerSubMenuActions); - menus.push(providerSubMenu); - } - } - - if (providers.length && !menus.length) { - const noAccountsAvailableAction = disposables.add(new Action('noAccountsAvailable', localize('noAccounts', "You are not signed in to any accounts"), undefined, false)); - menus.push(noAccountsAvailableAction); - } - - if (menus.length && otherCommands.length) { - menus.push(new Separator()); - } - - otherCommands.forEach((group, i) => { - const actions = group[1]; - menus = menus.concat(actions); - if (i !== otherCommands.length - 1) { - menus.push(new Separator()); - } - }); - - return menus; - } - - protected override async resolveContextMenuActions(disposables: DisposableStore): Promise { - const actions = await super.resolveContextMenuActions(disposables); - - actions.unshift(...[ - toAction({ id: 'hideAccounts', label: localize('hideAccounts', "Hide Accounts"), run: () => this.storageService.store(AccountsActivityActionViewItem.ACCOUNTS_VISIBILITY_PREFERENCE_KEY, false, StorageScope.PROFILE, StorageTarget.USER) }), - new Separator() - ]); - - return actions; - } - - //#endregion - - //#region groupedAccounts helpers - - private async addOrUpdateAccount(providerId: string, account: AuthenticationSessionAccount): Promise { - let accounts = this.groupedAccounts.get(providerId); - if (!accounts) { - accounts = []; - this.groupedAccounts.set(providerId, accounts); - } - - const sessionFromEmbedder = await this.sessionFromEmbedder.value; - let canSignOut = true; - if ( - sessionFromEmbedder // if we have a session from the embedder - && !sessionFromEmbedder.canSignOut // and that session says we can't sign out - && (await this.authenticationService.getSessions(providerId)) // and that session is associated with the account we are adding/updating - .some(s => - s.id === sessionFromEmbedder.id - && s.account.id === account.id - ) - ) { - canSignOut = false; - } - - const existingAccount = accounts.find(a => a.label === account.label); - if (existingAccount) { - // if we have an existing account and we discover that we - // can't sign out of it, update the account to mark it as "can't sign out" - if (!canSignOut) { - existingAccount.canSignOut = canSignOut; - } - } else { - accounts.push({ ...account, canSignOut }); - } - } - - private removeAccount(providerId: string, account: AuthenticationSessionAccount): void { - const accounts = this.groupedAccounts.get(providerId); - if (!accounts) { - return; - } - - const index = accounts.findIndex(a => a.id === account.id); - if (index === -1) { - return; - } - - accounts.splice(index, 1); - if (accounts.length === 0) { - this.groupedAccounts.delete(providerId); - } - } - - private async addAccountsFromProvider(providerId: string): Promise { - try { - const sessions = await this.authenticationService.getSessions(providerId); - this.problematicProviders.delete(providerId); - - for (const session of sessions) { - try { - await this.addOrUpdateAccount(providerId, session.account); - } catch (e) { - this.logService.error(e); - } - } - } catch (e) { - this.logService.error(e); - this.problematicProviders.add(providerId); - } - } - - //#endregion -} - -export interface IProfileActivity extends IActivity { - readonly icon: boolean; -} - -export class GlobalActivityActionViewItem extends MenuActivityActionViewItem { - - private profileBadge: HTMLElement | undefined; - private profileBadgeContent: HTMLElement | undefined; - - constructor( - action: ActivityAction, - contextMenuActionsProvider: () => IAction[], - colors: (theme: IColorTheme) => ICompositeBarColors, - activityHoverOptions: IActivityHoverOptions, - @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, - @IThemeService themeService: IThemeService, - @IHoverService hoverService: IHoverService, - @IMenuService menuService: IMenuService, - @IContextMenuService contextMenuService: IContextMenuService, - @IContextKeyService contextKeyService: IContextKeyService, - @IConfigurationService configurationService: IConfigurationService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, - @IKeybindingService keybindingService: IKeybindingService, - ) { - super(MenuId.GlobalActivity, action, contextMenuActionsProvider, true, colors, activityHoverOptions, themeService, hoverService, menuService, contextMenuService, contextKeyService, configurationService, environmentService, keybindingService); - } - - override render(container: HTMLElement): void { - super.render(container); - - this.profileBadge = append(container, $('.profile-badge')); - this.profileBadgeContent = append(this.profileBadge, $('.profile-badge-content')); - this.updateProfileBadge(); - } - - private updateProfileBadge(): void { - if (!this.profileBadge || !this.profileBadgeContent) { - return; - } - - clearNode(this.profileBadgeContent); - hide(this.profileBadge); - - if (this.userDataProfileService.currentProfile.isDefault) { - return; - } - - if ((this.action as ActivityAction).getBadge()) { - return; - } - - if (!this.userDataProfileService.currentProfile.icon || this.userDataProfileService.currentProfile.icon === DEFAULT_ICON.id) { - this.profileBadgeContent.classList.toggle('profile-text-overlay', true); - this.profileBadgeContent.classList.toggle('profile-icon-overlay', false); - this.profileBadgeContent.textContent = this.userDataProfileService.currentProfile.name.substring(0, 2).toUpperCase(); - } - - show(this.profileBadge); - } - - protected override updateBadge(): void { - super.updateBadge(); - this.updateProfileBadge(); - } - - protected override computeTitle(): string { - return this.userDataProfileService.currentProfile.isDefault ? super.computeTitle() : localize('manage', "Manage {0} (Profile)", this.userDataProfileService.currentProfile.name); - } -} - -class SwitchSideBarViewAction extends Action2 { - - constructor( - desc: Readonly, - private readonly offset: number - ) { - super(desc); - } - - async run(accessor: ServicesAccessor): Promise { - const paneCompositeService = accessor.get(IPaneCompositePartService); - - const visibleViewletIds = paneCompositeService.getVisiblePaneCompositeIds(ViewContainerLocation.Sidebar); - - const activeViewlet = paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar); - if (!activeViewlet) { - return; - } - let targetViewletId: string | undefined; - for (let i = 0; i < visibleViewletIds.length; i++) { - if (visibleViewletIds[i] === activeViewlet.getId()) { - targetViewletId = visibleViewletIds[(i + visibleViewletIds.length + this.offset) % visibleViewletIds.length]; - break; - } - } - - await paneCompositeService.openPaneComposite(targetViewletId, ViewContainerLocation.Sidebar, true); - } -} - -registerAction2( - class PreviousSideBarViewAction extends SwitchSideBarViewAction { - constructor() { - super({ - id: 'workbench.action.previousSideBarView', - title: { value: localize('previousSideBarView', "Previous Primary Side Bar View"), original: 'Previous Primary Side Bar View' }, - category: Categories.View, - f1: true - }, -1); - } - } -); - -registerAction2( - class NextSideBarViewAction extends SwitchSideBarViewAction { - constructor() { - super({ - id: 'workbench.action.nextSideBarView', - title: { value: localize('nextSideBarView', "Next Primary Side Bar View"), original: 'Next Primary Side Bar View' }, - category: Categories.View, - f1: true - }, 1); - } - } -); - -registerAction2( - class FocusActivityBarAction extends Action2 { - constructor() { - super({ - id: 'workbench.action.focusActivityBar', - title: { value: localize('focusActivityBar', "Focus Activity Bar"), original: 'Focus Activity Bar' }, - category: Categories.View, - f1: true - }); - } - - async run(accessor: ServicesAccessor): Promise { - const layoutService = accessor.get(IWorkbenchLayoutService); - layoutService.setPartHidden(false, Parts.ACTIVITYBAR_PART); - layoutService.focusPart(Parts.ACTIVITYBAR_PART); - } - }); - -registerThemingParticipant((theme, collector) => { - - const activityBarActiveBorderColor = theme.getColor(ACTIVITY_BAR_ACTIVE_BORDER); - if (activityBarActiveBorderColor) { - collector.addRule(` - .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before { - border-left-color: ${activityBarActiveBorderColor}; - } - `); - } - - const activityBarActiveFocusBorderColor = theme.getColor(ACTIVITY_BAR_ACTIVE_FOCUS_BORDER); - if (activityBarActiveFocusBorderColor) { - collector.addRule(` - .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:focus::before { - visibility: hidden; - } - - .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:focus .active-item-indicator:before { - visibility: visible; - border-left-color: ${activityBarActiveFocusBorderColor}; - } - `); - } - - const activityBarActiveBackgroundColor = theme.getColor(ACTIVITY_BAR_ACTIVE_BACKGROUND); - if (activityBarActiveBackgroundColor) { - collector.addRule(` - .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator { - z-index: 0; - background-color: ${activityBarActiveBackgroundColor}; - } - `); - } - - // Styling with Outline color (e.g. high contrast theme) - const outline = theme.getColor(activeContrastBorder); - if (outline) { - collector.addRule(` - .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:before { - content: ""; - position: absolute; - top: 8px; - left: 8px; - height: 32px; - width: 32px; - z-index: 1; - } - - .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.profile-activity-item:before { - top: -6px; - } - - .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.active:before, - .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.active:hover:before, - .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:before, - .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:hover:before { - outline: 1px solid; - } - - .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:hover:before { - outline: 1px dashed; - } - - .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus .active-item-indicator:before { - border-left-color: ${outline}; - } - - .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.active:before, - .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.active:hover:before, - .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:before, - .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:hover:before, - .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:hover:before { - outline-color: ${outline}; - } - `); - } - - // Styling without outline color - else { - const focusBorderColor = theme.getColor(focusBorder); - if (focusBorderColor) { - collector.addRule(` - .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus .active-item-indicator:before { - border-left-color: ${focusBorderColor}; - } - `); - } - } -}); diff --git a/code/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/code/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index fed35f314b5..45597e813ba 100644 --- a/code/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/code/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -4,36 +4,46 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/activitybarpart'; +import 'vs/css!./media/activityaction'; import { localize } from 'vs/nls'; import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { Part } from 'vs/workbench/browser/part'; -import { IBadge } from 'vs/workbench/services/activity/common/activity'; -import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { ToggleActivityBarVisibilityAction, ToggleSidebarPositionAction } from 'vs/workbench/browser/actions/layoutActions'; -import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; -import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER, ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_INACTIVE_FOREGROUND, ACTIVITY_BAR_ACTIVE_BACKGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BORDER } from 'vs/workbench/common/theme'; -import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; -import { addDisposableListener, EventType, isAncestor } from 'vs/base/browser/dom'; +import { ActivityBarPosition, IWorkbenchLayoutService, LayoutSettings, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ToggleSidebarPositionAction } from 'vs/workbench/browser/actions/layoutActions'; +import { IThemeService, IColorTheme, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER, ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_INACTIVE_FOREGROUND, ACTIVITY_BAR_ACTIVE_BACKGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BORDER, ACTIVITY_BAR_ACTIVE_FOCUS_BORDER } from 'vs/workbench/common/theme'; +import { activeContrastBorder, contrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry'; +import { addDisposableListener, append, EventType, isAncestor, $ } from 'vs/base/browser/dom'; import { ICompositeBarColors, IActivityHoverOptions } from 'vs/workbench/browser/parts/compositeBarActions'; import { assertIsDefined } from 'vs/base/common/types'; import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { getMenuBarVisibility } from 'vs/platform/window/common/window'; -import { Separator, toAction } from 'vs/base/common/actions'; +import { IAction, Separator, SubmenuAction, toAction } from 'vs/base/common/actions'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; import { GestureEvent } from 'vs/base/browser/touch'; import { IPaneCompositePart } from 'vs/workbench/browser/parts/paneCompositePart'; -import { PaneCompositeBar } from 'vs/workbench/browser/parts/paneCompositeBar'; +import { IPaneCompositeBarOptions, PaneCompositeBar } from 'vs/workbench/browser/parts/paneCompositeBar'; import { GlobalCompositeBar } from 'vs/workbench/browser/parts/globalCompositeBar'; import { IStorageService } from 'vs/platform/storage/common/storage'; +import { Action2, IAction2Options, IMenuService, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; +import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { Categories } from 'vs/platform/action/common/actionCommonCategories'; +import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IViewDescriptorService, ViewContainerLocation, ViewContainerLocationToString } from 'vs/workbench/common/views'; +import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; +import { TitleBarVisibleContext } from 'vs/workbench/common/contextkeys'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IExtensionBisectService } from 'vs/workbench/services/extensionManagement/browser/extensionBisect'; export class ActivitybarPart extends Part { - private static readonly ACTION_HEIGHT = 48; + static readonly ACTION_HEIGHT = 48; static readonly pinnedViewContainersKey = 'workbench.activity.pinnedViewlets2'; static readonly placeholderViewContainersKey = 'workbench.activity.placeholderViewlets'; @@ -47,16 +57,7 @@ export class ActivitybarPart extends Part { //#endregion - private content: HTMLElement | undefined; - - private menuBar: CustomMenubarControl | undefined; - private menuBarContainer: HTMLElement | undefined; - - private compositeBarContainer: HTMLElement | undefined; private readonly compositeBar: PaneCompositeBar; - private readonly globalCompositeBar: GlobalCompositeBar; - - private readonly keyboardNavigationDisposables = this._register(new DisposableStore()); constructor( private readonly paneCompositePart: IPaneCompositePart, @@ -64,16 +65,13 @@ export class ActivitybarPart extends Part { @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IThemeService themeService: IThemeService, @IStorageService storageService: IStorageService, - @IConfigurationService private readonly configurationService: IConfigurationService, ) { super(Parts.ACTIVITYBAR_PART, { hasTitle: false }, themeService, storageService, layoutService); this.compositeBar = this.createCompositeBar(); - this.globalCompositeBar = this._register(instantiationService.createInstance(GlobalCompositeBar, () => this.compositeBar.getContextMenuActions(), (theme: IColorTheme) => this.getActivitybarItemColors(theme), this.getActivityHoverOptions())); - this.registerListeners(); } private createCompositeBar(): PaneCompositeBar { - return this._register(this.instantiationService.createInstance(PaneCompositeBar, { + return this._register(this.instantiationService.createInstance(ActivityBarCompositeBar, { partContainerClass: 'activitybar', pinnedViewContainersKey: ActivitybarPart.pinnedViewContainersKey, placeholderViewContainersKey: ActivitybarPart.placeholderViewContainersKey, @@ -83,34 +81,11 @@ export class ActivitybarPart extends Part { activityHoverOptions: this.getActivityHoverOptions(), preventLoopNavigation: true, recomputeSizes: false, - fillExtraContextMenuActions: (actions, e?: MouseEvent | GestureEvent) => { - // Menu - const menuBarVisibility = getMenuBarVisibility(this.configurationService); - if (menuBarVisibility === 'compact' || menuBarVisibility === 'hidden' || menuBarVisibility === 'toggle') { - actions.unshift(...[toAction({ id: 'toggleMenuVisibility', label: localize('menu', "Menu"), checked: menuBarVisibility === 'compact', run: () => this.configurationService.updateValue('window.menuBarVisibility', menuBarVisibility === 'compact' ? 'toggle' : 'compact') }), new Separator()]); - } - - if (menuBarVisibility === 'compact' && this.menuBarContainer && e?.target) { - if (isAncestor(e.target as Node, this.menuBarContainer)) { - actions.unshift(...[toAction({ id: 'hideCompactMenu', label: localize('hideMenu', "Hide Menu"), run: () => this.configurationService.updateValue('window.menuBarVisibility', 'toggle') }), new Separator()]); - } - } - - // Global Composite Bar - actions.push(new Separator()); - actions.push(...this.globalCompositeBar.getContextMenuActions()); - actions.push(new Separator()); - - // Toggle Sidebar - actions.push(toAction({ id: ToggleSidebarPositionAction.ID, label: ToggleSidebarPositionAction.getLabel(this.layoutService), run: () => this.instantiationService.invokeFunction(accessor => new ToggleSidebarPositionAction().run(accessor)) })); - - // Toggle Activity Bar - actions.push(toAction({ id: ToggleActivityBarVisibilityAction.ID, label: localize('hideActivitBar', "Hide Activity Bar"), run: () => this.instantiationService.invokeFunction(accessor => new ToggleActivityBarVisibilityAction().run(accessor)) })); - }, + fillExtraContextMenuActions: (actions, e?: MouseEvent | GestureEvent) => { }, compositeSize: 52, colors: (theme: IColorTheme) => this.getActivitybarItemColors(theme), overflowActionSize: ActivitybarPart.ACTION_HEIGHT, - }, Parts.ACTIVITYBAR_PART, this.paneCompositePart)); + }, Parts.ACTIVITYBAR_PART, this.paneCompositePart, true)); } private getActivityHoverOptions(): IActivityHoverOptions { @@ -119,7 +94,108 @@ export class ActivitybarPart extends Part { }; } - private registerListeners(): void { + protected override createContentArea(parent: HTMLElement): HTMLElement { + this.element = parent; + const content = append(this.element, $('.content')); + this.compositeBar.create(content); + return content; + } + + getPinnedPaneCompositeIds(): string[] { + return this.compositeBar.getPinnedPaneCompositeIds(); + } + + getVisiblePaneCompositeIds(): string[] { + return this.compositeBar.getVisiblePaneCompositeIds(); + } + + focus(): void { + this.compositeBar.focus(); + } + + override updateStyles(): void { + super.updateStyles(); + + const container = assertIsDefined(this.getContainer()); + const background = this.getColor(ACTIVITY_BAR_BACKGROUND) || ''; + container.style.backgroundColor = background; + + const borderColor = this.getColor(ACTIVITY_BAR_BORDER) || this.getColor(contrastBorder) || ''; + container.classList.toggle('bordered', !!borderColor); + container.style.borderColor = borderColor ? borderColor : ''; + } + + private getActivitybarItemColors(theme: IColorTheme): ICompositeBarColors { + return { + activeForegroundColor: theme.getColor(ACTIVITY_BAR_FOREGROUND), + inactiveForegroundColor: theme.getColor(ACTIVITY_BAR_INACTIVE_FOREGROUND), + activeBorderColor: theme.getColor(ACTIVITY_BAR_ACTIVE_BORDER), + activeBackground: theme.getColor(ACTIVITY_BAR_ACTIVE_BACKGROUND), + badgeBackground: theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND), + badgeForeground: theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND), + dragAndDropBorder: theme.getColor(ACTIVITY_BAR_DRAG_AND_DROP_BORDER), + activeBackgroundColor: undefined, inactiveBackgroundColor: undefined, activeBorderBottomColor: undefined, + }; + } + + override layout(width: number, height: number): void { + if (!this.layoutService.isVisible(Parts.ACTIVITYBAR_PART)) { + return; + } + + // Layout contents + const contentAreaSize = super.layoutContents(width, height).contentSize; + + // Layout composite bar + this.compositeBar.layout(width, contentAreaSize.height); + } + + toJSON(): object { + return { + type: Parts.ACTIVITYBAR_PART + }; + } +} + +export class ActivityBarCompositeBar extends PaneCompositeBar { + + private element: HTMLElement | undefined; + + private menuBar: CustomMenubarControl | undefined; + private menuBarContainer: HTMLElement | undefined; + private compositeBarContainer: HTMLElement | undefined; + private readonly globalCompositeBar: GlobalCompositeBar | undefined; + + private readonly keyboardNavigationDisposables = this._register(new DisposableStore()); + + constructor( + options: IPaneCompositeBarOptions, + part: Parts, + paneCompositePart: IPaneCompositePart, + showGlobalActivities: boolean, + @IInstantiationService instantiationService: IInstantiationService, + @IStorageService storageService: IStorageService, + @IExtensionService extensionService: IExtensionService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IContextKeyService contextKeyService: IContextKeyService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IExtensionBisectService extensionBisectService: IExtensionBisectService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IMenuService private readonly menuService: IMenuService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + ) { + super({ + ...options, + fillExtraContextMenuActions: (actions, e) => { + this.fillContextMenuActions(actions, e); + options.fillExtraContextMenuActions(actions, e); + } + }, part, paneCompositePart, instantiationService, storageService, extensionService, extensionBisectService, viewDescriptorService, contextKeyService, environmentService); + + if (showGlobalActivities) { + this.globalCompositeBar = this._register(instantiationService.createInstance(GlobalCompositeBar, () => this.getContextMenuActions(), (theme: IColorTheme) => this.options.colors(theme), this.options.activityHoverOptions)); + } + // Register for configuration changes this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('window.menuBarVisibility')) { @@ -132,12 +208,26 @@ export class ActivitybarPart extends Part { })); } - showActivity(viewContainerOrActionId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable { - const disposable = this.globalCompositeBar.showActivity(viewContainerOrActionId, badge, clazz, priority); - if (disposable) { - return disposable; + private fillContextMenuActions(actions: IAction[], e?: MouseEvent | GestureEvent) { + // Menu + const menuBarVisibility = getMenuBarVisibility(this.configurationService); + if (menuBarVisibility === 'compact' || menuBarVisibility === 'hidden' || menuBarVisibility === 'toggle') { + actions.unshift(...[toAction({ id: 'toggleMenuVisibility', label: localize('menu', "Menu"), checked: menuBarVisibility === 'compact', run: () => this.configurationService.updateValue('window.menuBarVisibility', menuBarVisibility === 'compact' ? 'toggle' : 'compact') }), new Separator()]); + } + + if (menuBarVisibility === 'compact' && this.menuBarContainer && e?.target) { + if (isAncestor(e.target as Node, this.menuBarContainer)) { + actions.unshift(...[toAction({ id: 'hideCompactMenu', label: localize('hideMenu', "Hide Menu"), run: () => this.configurationService.updateValue('window.menuBarVisibility', 'toggle') }), new Separator()]); + } + } + + // Global Composite Bar + if (this.globalCompositeBar) { + actions.push(new Separator()); + actions.push(...this.globalCompositeBar.getContextMenuActions()); } - return this.compositeBar.showActivity(viewContainerOrActionId, badge, clazz, priority); + actions.push(new Separator()); + actions.push(...this.getActivityBarContextMenuActions()); } private uninstallMenubar() { @@ -149,7 +239,6 @@ export class ActivitybarPart extends Part { if (this.menuBarContainer) { this.menuBarContainer.remove(); this.menuBarContainer = undefined; - this.registerKeyboardNavigationListeners(); } } @@ -161,49 +250,24 @@ export class ActivitybarPart extends Part { this.menuBarContainer = document.createElement('div'); this.menuBarContainer.classList.add('menubar'); - const content = assertIsDefined(this.content); + const content = assertIsDefined(this.element); content.prepend(this.menuBarContainer); // Menubar: install a custom menu bar depending on configuration this.menuBar = this._register(this.instantiationService.createInstance(CustomMenubarControl)); this.menuBar.create(this.menuBarContainer); - this.registerKeyboardNavigationListeners(); - } - - protected override createContentArea(parent: HTMLElement): HTMLElement { - this.element = parent; - - this.content = document.createElement('div'); - this.content.classList.add('content'); - parent.appendChild(this.content); - - // Install menubar if compact - if (getMenuBarVisibility(this.configurationService) === 'compact') { - this.installMenubar(); - } - - // View Containers action bar - this.compositeBarContainer = this.compositeBar.create(this.content); - - // Global action bar - this.globalCompositeBar.create(this.content); - - // Keyboard Navigation - this.registerKeyboardNavigationListeners(); - - return this.content; } private registerKeyboardNavigationListeners(): void { this.keyboardNavigationDisposables.clear(); - // Up/Down arrow on compact menu + // Up/Down or Left/Right arrow on compact menu if (this.menuBarContainer) { this.keyboardNavigationDisposables.add(addDisposableListener(this.menuBarContainer, EventType.KEY_DOWN, e => { const kbEvent = new StandardKeyboardEvent(e); if (kbEvent.equals(KeyCode.DownArrow) || kbEvent.equals(KeyCode.RightArrow)) { - this.compositeBar?.focus(); + this.focus(); } })); } @@ -213,7 +277,7 @@ export class ActivitybarPart extends Part { this.keyboardNavigationDisposables.add(addDisposableListener(this.compositeBarContainer, EventType.KEY_DOWN, e => { const kbEvent = new StandardKeyboardEvent(e); if (kbEvent.equals(KeyCode.DownArrow) || kbEvent.equals(KeyCode.RightArrow)) { - this.globalCompositeBar.focus(); + this.globalCompositeBar?.focus(); } else if (kbEvent.equals(KeyCode.UpArrow) || kbEvent.equals(KeyCode.LeftArrow)) { this.menuBar?.toggleFocus(); } @@ -221,71 +285,334 @@ export class ActivitybarPart extends Part { } // Up arrow on global icons - this.keyboardNavigationDisposables.add(addDisposableListener(this.globalCompositeBar.element, EventType.KEY_DOWN, e => { - const kbEvent = new StandardKeyboardEvent(e); - if (kbEvent.equals(KeyCode.UpArrow) || kbEvent.equals(KeyCode.LeftArrow)) { - this.compositeBar?.focus(this.getVisiblePaneCompositeIds().length - 1); - } - })); + if (this.globalCompositeBar) { + this.keyboardNavigationDisposables.add(addDisposableListener(this.globalCompositeBar.element, EventType.KEY_DOWN, e => { + const kbEvent = new StandardKeyboardEvent(e); + if (kbEvent.equals(KeyCode.UpArrow) || kbEvent.equals(KeyCode.LeftArrow)) { + this.focus(this.getVisiblePaneCompositeIds().length - 1); + } + })); + } } - getPinnedPaneCompositeIds(): string[] { - return this.compositeBar.getPinnedPaneCompositeIds(); - } + override create(parent: HTMLElement): HTMLElement { + this.element = parent; - getVisiblePaneCompositeIds(): string[] { - return this.compositeBar.getVisiblePaneCompositeIds(); + // Install menubar if compact + if (getMenuBarVisibility(this.configurationService) === 'compact') { + this.installMenubar(); + } + + // View Containers action bar + this.compositeBarContainer = super.create(this.element); + + // Global action bar + if (this.globalCompositeBar) { + this.globalCompositeBar.create(this.element); + } + + // Keyboard Navigation + this.registerKeyboardNavigationListeners(); + + return this.compositeBarContainer; } - focus(): void { - this.compositeBar.focus(); + override layout(width: number, height: number): void { + if (this.menuBarContainer) { + if (this.options.orientation === ActionsOrientation.VERTICAL) { + height -= this.menuBarContainer.clientHeight; + } else { + width -= this.menuBarContainer.clientWidth; + } + } + if (this.globalCompositeBar) { + if (this.options.orientation === ActionsOrientation.VERTICAL) { + height -= (this.globalCompositeBar.size() * ActivitybarPart.ACTION_HEIGHT); + } else { + width -= this.globalCompositeBar.element.clientWidth; + } + } + super.layout(width, height); } - override updateStyles(): void { - super.updateStyles(); + getActivityBarContextMenuActions(): IAction[] { + const activityBarPositionMenu = this.menuService.createMenu(MenuId.ActivityBarPositionMenu, this.contextKeyService); + const positionActions: IAction[] = []; + createAndFillInContextMenuActions(activityBarPositionMenu, { shouldForwardArgs: true, renderShortTitle: true }, { primary: [], secondary: positionActions }); + activityBarPositionMenu.dispose(); + return [ + new SubmenuAction('workbench.action.panel.position', localize('activity bar position', "Activity Bar Position"), positionActions), + toAction({ id: ToggleSidebarPositionAction.ID, label: ToggleSidebarPositionAction.getLabel(this.layoutService), run: () => this.instantiationService.invokeFunction(accessor => new ToggleSidebarPositionAction().run(accessor)) }) + ]; + } - const container = assertIsDefined(this.getContainer()); - const background = this.getColor(ACTIVITY_BAR_BACKGROUND) || ''; - container.style.backgroundColor = background; +} - const borderColor = this.getColor(ACTIVITY_BAR_BORDER) || this.getColor(contrastBorder) || ''; - container.classList.toggle('bordered', !!borderColor); - container.style.borderColor = borderColor ? borderColor : ''; +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.activityBarLocation.side', + title: { + value: localize('positionActivityBarSide', 'Move Activity Bar to Side'), + original: 'Move Activity Bar to Side', + mnemonicTitle: localize({ key: 'miSideActivityBar', comment: ['&& denotes a mnemonic'] }, "&&Side"), + }, + shortTitle: localize('side', "Side"), + category: Categories.View, + toggled: ContextKeyExpr.equals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.SIDE), + menu: [{ + id: MenuId.ActivityBarPositionMenu, + order: 1 + }, { + id: MenuId.CommandPalette, + when: ContextKeyExpr.notEquals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.SIDE), + }] + }); + } + run(accessor: ServicesAccessor): void { + const configurationService = accessor.get(IConfigurationService); + configurationService.updateValue(LayoutSettings.ACTIVITY_BAR_LOCATION, ActivityBarPosition.SIDE); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.activityBarLocation.top', + title: { + value: localize('positionActivityBarTop', 'Move Activity Bar to Top'), + original: 'Move Activity Bar to Top', + mnemonicTitle: localize({ key: 'miTopActivityBar', comment: ['&& denotes a mnemonic'] }, "&&Top"), + }, + shortTitle: localize('top', "Top"), + category: Categories.View, + toggled: ContextKeyExpr.equals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.TOP), + menu: [{ + id: MenuId.ActivityBarPositionMenu, + when: TitleBarVisibleContext.isEqualTo(true), + order: 2 + }, { + id: MenuId.CommandPalette, + when: ContextKeyExpr.and(ContextKeyExpr.notEquals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.TOP), TitleBarVisibleContext.isEqualTo(true)), + }] + }); + } + run(accessor: ServicesAccessor): void { + const configurationService = accessor.get(IConfigurationService); + configurationService.updateValue(LayoutSettings.ACTIVITY_BAR_LOCATION, ActivityBarPosition.TOP); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.action.activityBarLocation.hide', + title: { + value: localize('hideActivityBar', 'Hide Activity Bar'), + original: 'Hide Activity Bar', + mnemonicTitle: localize({ key: 'miHideActivityBar', comment: ['&& denotes a mnemonic'] }, "&&Hidden"), + }, + shortTitle: localize('hide', "Hidden"), + category: Categories.View, + toggled: ContextKeyExpr.equals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.HIDDEN), + menu: [{ + id: MenuId.ActivityBarPositionMenu, + order: 3 + }, { + id: MenuId.CommandPalette, + when: ContextKeyExpr.notEquals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.HIDDEN), + }] + }); + } + run(accessor: ServicesAccessor): void { + const configurationService = accessor.get(IConfigurationService); + configurationService.updateValue(LayoutSettings.ACTIVITY_BAR_LOCATION, ActivityBarPosition.HIDDEN); } +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { + submenu: MenuId.ActivityBarPositionMenu, + title: localize('positionActivituBar', "Activity Bar Position"), + group: '3_workbench_layout_move', + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.ViewContainerTitleContext, { + submenu: MenuId.ActivityBarPositionMenu, + title: localize('positionActivituBar', "Activity Bar Position"), + when: ContextKeyExpr.equals('viewContainerLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar)), + group: '3_workbench_layout_move', + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.ViewTitleContext, { + submenu: MenuId.ActivityBarPositionMenu, + title: localize('positionActivituBar', "Activity Bar Position"), + when: ContextKeyExpr.equals('viewLocation', ViewContainerLocationToString(ViewContainerLocation.Sidebar)), + group: '3_workbench_layout_move', + order: 1 +}); + +class SwitchSideBarViewAction extends Action2 { - private getActivitybarItemColors(theme: IColorTheme): ICompositeBarColors { - return { - activeForegroundColor: theme.getColor(ACTIVITY_BAR_FOREGROUND), - inactiveForegroundColor: theme.getColor(ACTIVITY_BAR_INACTIVE_FOREGROUND), - activeBorderColor: theme.getColor(ACTIVITY_BAR_ACTIVE_BORDER), - activeBackground: theme.getColor(ACTIVITY_BAR_ACTIVE_BACKGROUND), - badgeBackground: theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND), - badgeForeground: theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND), - dragAndDropBorder: theme.getColor(ACTIVITY_BAR_DRAG_AND_DROP_BORDER), - activeBackgroundColor: undefined, inactiveBackgroundColor: undefined, activeBorderBottomColor: undefined, - }; + constructor( + desc: Readonly, + private readonly offset: number + ) { + super(desc); } - override layout(width: number, height: number): void { - if (!this.layoutService.isVisible(Parts.ACTIVITYBAR_PART)) { + async run(accessor: ServicesAccessor): Promise { + const paneCompositeService = accessor.get(IPaneCompositePartService); + + const visibleViewletIds = paneCompositeService.getVisiblePaneCompositeIds(ViewContainerLocation.Sidebar); + + const activeViewlet = paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar); + if (!activeViewlet) { return; } + let targetViewletId: string | undefined; + for (let i = 0; i < visibleViewletIds.length; i++) { + if (visibleViewletIds[i] === activeViewlet.getId()) { + targetViewletId = visibleViewletIds[(i + visibleViewletIds.length + this.offset) % visibleViewletIds.length]; + break; + } + } - // Layout contents - const contentAreaSize = super.layoutContents(width, height).contentSize; + await paneCompositeService.openPaneComposite(targetViewletId, ViewContainerLocation.Sidebar, true); + } +} - // Layout composite bar - let availableHeight = contentAreaSize.height; - if (this.menuBarContainer) { - availableHeight -= this.menuBarContainer.clientHeight; +registerAction2( + class PreviousSideBarViewAction extends SwitchSideBarViewAction { + constructor() { + super({ + id: 'workbench.action.previousSideBarView', + title: { value: localize('previousSideBarView', "Previous Primary Side Bar View"), original: 'Previous Primary Side Bar View' }, + category: Categories.View, + f1: true + }, -1); + } + } +); + +registerAction2( + class NextSideBarViewAction extends SwitchSideBarViewAction { + constructor() { + super({ + id: 'workbench.action.nextSideBarView', + title: { value: localize('nextSideBarView', "Next Primary Side Bar View"), original: 'Next Primary Side Bar View' }, + category: Categories.View, + f1: true + }, 1); } - availableHeight -= (this.globalCompositeBar.size() * ActivitybarPart.ACTION_HEIGHT); // adjust height for global actions showing - this.compositeBar.layout(width, availableHeight); } +); + +registerAction2( + class FocusActivityBarAction extends Action2 { + constructor() { + super({ + id: 'workbench.action.focusActivityBar', + title: { value: localize('focusActivityBar', "Focus Activity Bar"), original: 'Focus Activity Bar' }, + category: Categories.View, + f1: true + }); + } - toJSON(): object { - return { - type: Parts.ACTIVITYBAR_PART - }; + async run(accessor: ServicesAccessor): Promise { + const layoutService = accessor.get(IWorkbenchLayoutService); + layoutService.focusPart(Parts.ACTIVITYBAR_PART); + } + }); + +registerThemingParticipant((theme, collector) => { + + const activityBarActiveBorderColor = theme.getColor(ACTIVITY_BAR_ACTIVE_BORDER); + if (activityBarActiveBorderColor) { + collector.addRule(` + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator:before { + border-left-color: ${activityBarActiveBorderColor}; + } + `); } -} + + const activityBarActiveFocusBorderColor = theme.getColor(ACTIVITY_BAR_ACTIVE_FOCUS_BORDER); + if (activityBarActiveFocusBorderColor) { + collector.addRule(` + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:focus::before { + visibility: hidden; + } + + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:focus .active-item-indicator:before { + visibility: visible; + border-left-color: ${activityBarActiveFocusBorderColor}; + } + `); + } + + const activityBarActiveBackgroundColor = theme.getColor(ACTIVITY_BAR_ACTIVE_BACKGROUND); + if (activityBarActiveBackgroundColor) { + collector.addRule(` + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked .active-item-indicator { + z-index: 0; + background-color: ${activityBarActiveBackgroundColor}; + } + `); + } + + // Styling with Outline color (e.g. high contrast theme) + const outline = theme.getColor(activeContrastBorder); + if (outline) { + collector.addRule(` + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:before { + content: ""; + position: absolute; + top: 8px; + left: 8px; + height: 32px; + width: 32px; + z-index: 1; + } + + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.profile-activity-item:before { + top: -6px; + } + + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.active:before, + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.active:hover:before, + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:before, + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:hover:before { + outline: 1px solid; + } + + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:hover:before { + outline: 1px dashed; + } + + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus .active-item-indicator:before { + border-left-color: ${outline}; + } + + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.active:before, + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.active:hover:before, + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:before, + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:hover:before, + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:hover:before { + outline-color: ${outline}; + } + `); + } + + // Styling without outline color + else { + const focusBorderColor = theme.getColor(focusBorder); + if (focusBorderColor) { + collector.addRule(` + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus .active-item-indicator:before { + border-left-color: ${focusBorderColor}; + } + `); + } + } +}); diff --git a/code/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts b/code/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts index 7a2032d996a..e6bd36f86c6 100644 --- a/code/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts +++ b/code/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts @@ -11,10 +11,10 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { badgeBackground, badgeForeground, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; +import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ActiveAuxiliaryContext, AuxiliaryBarFocusContext } from 'vs/workbench/common/contextkeys'; -import { PANEL_ACTIVE_TITLE_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_DRAG_AND_DROP_BORDER, PANEL_INACTIVE_TITLE_FOREGROUND, SIDE_BAR_BACKGROUND, SIDE_BAR_BORDER, SIDE_BAR_FOREGROUND } from 'vs/workbench/common/theme'; +import { ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_DRAG_AND_DROP_BORDER, PANEL_INACTIVE_TITLE_FOREGROUND, SIDE_BAR_BACKGROUND, SIDE_BAR_BORDER, SIDE_BAR_FOREGROUND } from 'vs/workbench/common/theme'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; @@ -122,7 +122,7 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart { container.style.borderRightWidth = borderColor && isPositionLeft ? '1px' : '0px'; } - protected getCompoisteBarOptions(): IPaneCompositeBarOptions { + protected getCompositeBarOptions(): IPaneCompositeBarOptions { return { partContainerClass: 'auxiliarybar', pinnedViewContainersKey: AuxiliaryBarPart.pinnedPanelsKey, @@ -143,10 +143,11 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart { activeBorderBottomColor: theme.getColor(PANEL_ACTIVE_TITLE_BORDER), activeForegroundColor: theme.getColor(PANEL_ACTIVE_TITLE_FOREGROUND), inactiveForegroundColor: theme.getColor(PANEL_INACTIVE_TITLE_FOREGROUND), - badgeBackground: theme.getColor(badgeBackground), - badgeForeground: theme.getColor(badgeForeground), + badgeBackground: theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND), + badgeForeground: theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND), dragAndDropBorder: theme.getColor(PANEL_DRAG_AND_DROP_BORDER) - }) + }), + compact: true }; } diff --git a/code/src/vs/workbench/browser/parts/auxiliarybar/media/auxiliaryBarPart.css b/code/src/vs/workbench/browser/parts/auxiliarybar/media/auxiliaryBarPart.css index cd9fc84764b..2c0c99d85cc 100644 --- a/code/src/vs/workbench/browser/parts/auxiliarybar/media/auxiliaryBarPart.css +++ b/code/src/vs/workbench/browser/parts/auxiliarybar/media/auxiliaryBarPart.css @@ -14,16 +14,20 @@ background-color: var(--vscode-sideBar-background); } -.monaco-workbench .part.auxiliarybar > .title > .pane-composite-bar > .monaco-action-bar .action-item:hover .action-label, -.monaco-workbench .part.auxiliarybar > .title > .pane-composite-bar > .monaco-action-bar .action-item:focus .action-label { +.monaco-workbench .part.auxiliarybar > .title > .composite-bar-container { + flex: 1; +} + +.monaco-workbench .part.auxiliarybar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label, +.monaco-workbench .part.auxiliarybar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .action-label { color: var(--vscode-sideBarTitle-foreground) !important; } -.monaco-workbench .part.auxiliarybar > .title > .pane-composite-bar > .monaco-action-bar .action-item.checked .action-label, -.monaco-workbench .part.auxiliarybar > .title > .pane-composite-bar > .monaco-action-bar .action-item:hover .action-label { +.monaco-workbench .part.auxiliarybar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label, +.monaco-workbench .part.auxiliarybar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label { outline: var(--vscode-contrastActiveBorder, unset) solid 1px !important; } -.monaco-workbench .part.auxiliarybar > .title > .pane-composite-bar > .monaco-action-bar .action-item:not(.checked):hover .action-label { +.monaco-workbench .part.auxiliarybar > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.checked):hover .action-label { outline: var(--vscode-contrastActiveBorder, unset) dashed 1px !important; } diff --git a/code/src/vs/workbench/browser/parts/compositeBar.ts b/code/src/vs/workbench/browser/parts/compositeBar.ts index 34911eebde9..04403ef1a22 100644 --- a/code/src/vs/workbench/browser/parts/compositeBar.ts +++ b/code/src/vs/workbench/browser/parts/compositeBar.ts @@ -5,12 +5,10 @@ import { localize } from 'vs/nls'; import { IAction, toAction } from 'vs/base/common/actions'; -import { illegalArgument } from 'vs/base/common/errors'; -import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IBadge } from 'vs/workbench/services/activity/common/activity'; +import { IActivity } from 'vs/workbench/services/activity/common/activity'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; -import { CompositeActionViewItem, CompositeOverflowActivityAction, ICompositeActivity, CompositeOverflowActivityActionViewItem, ActivityAction, ICompositeBar, ICompositeBarColors, IActivityHoverOptions } from 'vs/workbench/browser/parts/compositeBarActions'; +import { CompositeActionViewItem, CompositeOverflowActivityAction, CompositeOverflowActivityActionViewItem, CompositeBarAction, ICompositeBar, ICompositeBarColors, IActivityHoverOptions } from 'vs/workbench/browser/parts/compositeBarActions'; import { Dimension, $, addDisposableListener, EventType, EventHelper, isAncestor } from 'vs/base/browser/dom'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -59,14 +57,14 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { } // ... on a different composite bar else { - this.viewDescriptorService.moveViewContainerToLocation(currentContainer, this.targetContainerLocation, this.getTargetIndex(targetCompositeId, before)); + this.viewDescriptorService.moveViewContainerToLocation(currentContainer, this.targetContainerLocation, this.getTargetIndex(targetCompositeId, before), 'dnd'); } } if (dragData.type === 'view') { const viewToMove = this.viewDescriptorService.getViewDescriptorById(dragData.id)!; if (viewToMove && viewToMove.canMoveView) { - this.viewDescriptorService.moveViewToLocation(viewToMove, this.targetContainerLocation); + this.viewDescriptorService.moveViewToLocation(viewToMove, this.targetContainerLocation, 'dnd'); const newContainer = this.viewDescriptorService.getViewContainerByViewId(viewToMove.id)!; @@ -135,13 +133,14 @@ export interface ICompositeBarOptions { readonly icon: boolean; readonly orientation: ActionsOrientation; readonly colors: (theme: IColorTheme) => ICompositeBarColors; + readonly compact?: boolean; readonly compositeSize: number; readonly overflowActionSize: number; readonly dndHandler: ICompositeDragAndDrop; readonly activityHoverOptions: IActivityHoverOptions; readonly preventLoopNavigation?: boolean; - readonly getActivityAction: (compositeId: string) => ActivityAction; + readonly getActivityAction: (compositeId: string) => CompositeBarAction; readonly getCompositePinnedAction: (compositeId: string) => IAction; readonly getCompositeBadgeAction: (compositeId: string) => IAction; readonly getOnCompositeClickAction: (compositeId: string) => IAction; @@ -187,9 +186,8 @@ export class CompositeBar extends Widget implements ICompositeBar { } setCompositeBarItems(items: ICompositeBarItem[]): void { - if (this.model.setItems(items)) { - this.updateCompositeSwitcher(); - } + this.model.setItems(items); + this.updateCompositeSwitcher(); } getPinnedComposites(): ICompositeBarItem[] { @@ -210,8 +208,8 @@ export class CompositeBar extends Widget implements ICompositeBar { const item = this.model.findItem(action.id); return item && this.instantiationService.createInstance( CompositeActionViewItem, - { draggable: true, colors: this.options.colors, icon: this.options.icon, hoverOptions: this.options.activityHoverOptions }, - action as ActivityAction, + { draggable: true, colors: this.options.colors, icon: this.options.icon, hoverOptions: this.options.activityHoverOptions, compact: this.options.compact }, + action as CompositeBarAction, item.pinnedAction, item.toggleBadgeAction, compositeId => this.options.getContextMenuActionsForComposite(compositeId), @@ -368,21 +366,6 @@ export class CompositeBar extends Widget implements ICompositeBar { } } - showActivity(compositeId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable { - if (!badge) { - throw illegalArgument('badge'); - } - - if (typeof priority !== 'number') { - priority = 0; - } - - const activity: ICompositeActivity = { badge, clazz, priority }; - this.model.addActivity(compositeId, activity); - - return toDisposable(() => this.model.removeActivity(compositeId, activity)); - } - async pin(compositeId: string, open?: boolean): Promise { if (this.model.setPinned(compositeId, true)) { this.updateCompositeSwitcher(); @@ -414,7 +397,7 @@ export class CompositeBar extends Widget implements ICompositeBar { if (item) { // TODO @lramos15 how do we tell the activity to re-render the badge? This triggers an onDidChange but isn't the right way to do it. // I could add another specific function like `activity.updateBadgeEnablement` would then the activity store the sate? - item.activityAction.setBadge(item.activityAction.getBadge(), item.activityAction.getClass()); + item.activityAction.activity = item.activityAction.activity; } } @@ -477,7 +460,7 @@ export class CompositeBar extends Widget implements ICompositeBar { } } - getAction(compositeId: string): ActivityAction { + getAction(compositeId: string): CompositeBarAction { const item = this.model.findItem(compositeId); return item?.activityAction; @@ -674,10 +657,10 @@ export class CompositeBar extends Widget implements ICompositeBar { } interface ICompositeBarModelItem extends ICompositeBarItem { - readonly activityAction: ActivityAction; + readonly activityAction: CompositeBarAction; readonly pinnedAction: IAction; readonly toggleBadgeAction: IAction; - readonly activity: ICompositeActivity[]; + readonly activity: IActivity[]; } class CompositeBarModel { @@ -697,37 +680,11 @@ class CompositeBarModel { this.setItems(items); } - setItems(items: ICompositeBarItem[]): boolean { - const result: ICompositeBarModelItem[] = []; - let hasChanges: boolean = false; - if (!this.items || this.items.length === 0) { - this._items = items.map(i => this.createCompositeBarItem(i.id, i.name, i.order, i.pinned, i.visible)); - hasChanges = true; - } else { - const existingItems = this.items; - for (let index = 0; index < items.length; index++) { - const newItem = items[index]; - const existingItem = existingItems.filter(({ id }) => id === newItem.id)[0]; - if (existingItem) { - if ( - existingItem.pinned !== newItem.pinned || - index !== existingItems.indexOf(existingItem) - ) { - existingItem.pinned = newItem.pinned; - result.push(existingItem); - hasChanges = true; - } else { - result.push(existingItem); - } - } else { - result.push(this.createCompositeBarItem(newItem.id, newItem.name, newItem.order, newItem.pinned, newItem.visible)); - hasChanges = true; - } - } - this._items = result; - } - - return hasChanges; + setItems(items: ICompositeBarItem[]): void { + this._items = []; + this._items = items + .map(i => this.createCompositeBarItem(i.id, i.name, i.order, i.pinned, i.visible)) + .sort((a, b) => (a.order ?? items.length) - (b.order ?? items.length)); } get visibleItems(): ICompositeBarModelItem[] { @@ -851,51 +808,6 @@ class CompositeBarModel { return false; } - addActivity(id: string, activity: ICompositeActivity): boolean { - const item = this.findItem(id); - if (item) { - const stack = item.activity; - for (let i = 0; i <= stack.length; i++) { - if (i === stack.length) { - stack.push(activity); - break; - } else if (stack[i].priority <= activity.priority) { - stack.splice(i, 0, activity); - break; - } - } - this.updateActivity(id); - return true; - } - return false; - } - - removeActivity(id: string, activity: ICompositeActivity): boolean { - const item = this.findItem(id); - if (item) { - const index = item.activity.indexOf(activity); - if (index !== -1) { - item.activity.splice(index, 1); - this.updateActivity(id); - return true; - } - } - return false; - } - - updateActivity(id: string): void { - const item = this.findItem(id); - if (item) { - if (item.activity.length) { - const [{ badge, clazz }] = item.activity; - item.activityAction.setBadge(badge, clazz); - } - else { - item.activityAction.setBadge(undefined); - } - } - } - activate(id: string): boolean { if (!this.activeItem || this.activeItem.id !== id) { if (this.activeItem) { diff --git a/code/src/vs/workbench/browser/parts/compositeBarActions.ts b/code/src/vs/workbench/browser/parts/compositeBarActions.ts index 5d5a42266a0..b4dfa8fdc9f 100644 --- a/code/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/code/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -7,31 +7,25 @@ import { localize } from 'vs/nls'; import { Action, IAction, Separator } from 'vs/base/common/actions'; import { $, addDisposableListener, append, clearNode, EventHelper, EventType, getDomNodePagePosition, hide, show } from 'vs/base/browser/dom'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { toDisposable, MutableDisposable, DisposableStore, disposeIfDisposable } from 'vs/base/common/lifecycle'; +import { toDisposable, DisposableStore, disposeIfDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; -import { TextBadge, NumberBadge, IBadge, IconBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity'; +import { TextBadge, NumberBadge, IBadge, IActivity, IconBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { DelayedDragHandler } from 'vs/base/browser/dnd'; -import { IActivity } from 'vs/workbench/common/activity'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Emitter, Event } from 'vs/base/common/event'; import { CompositeDragAndDropObserver, ICompositeDragAndDrop, Before2D, toggleDropEffect } from 'vs/workbench/browser/dnd'; import { Color } from 'vs/base/common/color'; -import { IBaseActionViewItemOptions, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { BaseActionViewItem, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; import { IHoverService, IHoverWidget } from 'vs/workbench/services/hover/browser/hover'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; - -export interface ICompositeActivity { - readonly badge: IBadge; - readonly clazz?: string; - readonly priority: number; -} +import { URI } from 'vs/base/common/uri'; +import { badgeBackground, badgeForeground, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; export interface ICompositeBar { @@ -68,29 +62,45 @@ export interface ICompositeBar { move(compositeId: string, tocompositeId: string): void; } -export class ActivityAction extends Action { +export interface ICompositeBarActionItem { + id: string; + name: string; + keybindingId?: string; + classNames?: string[]; + iconUrl?: URI; +} + +export class CompositeBarAction extends Action { + + private readonly _onDidChangeCompositeBarActionItem = this._register(new Emitter()); + readonly onDidChangeCompositeBarActionItem = this._onDidChangeCompositeBarActionItem.event; - private readonly _onDidChangeActivity = this._register(new Emitter()); + private readonly _onDidChangeActivity = this._register(new Emitter()); readonly onDidChangeActivity = this._onDidChangeActivity.event; - private readonly _onDidChangeBadge = this._register(new Emitter()); - readonly onDidChangeBadge = this._onDidChangeBadge.event; + private _activity: IActivity | undefined; - private badge: IBadge | undefined; - private clazz: string | undefined; + constructor(private item: ICompositeBarActionItem) { + super(item.id, item.name, item.classNames?.join(' '), true); + } + + get compositeBarActionItem(): ICompositeBarActionItem { + return this.item; + } - constructor(private _activity: IActivity) { - super(_activity.id, _activity.name, _activity.classNames?.join(' '), true); + set compositeBarActionItem(item: ICompositeBarActionItem) { + this._label = item.name; + this.item = item; + this._onDidChangeCompositeBarActionItem.fire(this); } - get activity(): IActivity { + get activity(): IActivity | undefined { return this._activity; } - set activity(activity: IActivity) { - this._label = activity.name; + set activity(activity: IActivity | undefined) { this._activity = activity; - this._onDidChangeActivity.fire(this); + this._onDidChangeActivity.fire(activity); } activate(): void { @@ -105,26 +115,6 @@ export class ActivityAction extends Action { } } - getBadge(): IBadge | undefined { - return this.badge; - } - - getClass(): string | undefined { - return this.clazz; - } - - setBadge(badge: IBadge | undefined, clazz?: string): void { - this.badge = badge; - this.clazz = clazz; - this._onDidChangeBadge.fire(this); - } - - override dispose(): void { - this._onDidChangeActivity.dispose(); - this._onDidChangeBadge.dispose(); - - super.dispose(); - } } export interface ICompositeBarColors { @@ -144,22 +134,23 @@ export interface IActivityHoverOptions { readonly position: () => HoverPosition; } -export interface IActivityActionViewItemOptions extends IBaseActionViewItemOptions { +export interface ICompositeBarActionViewItemOptions extends IActionViewItemOptions { readonly icon?: boolean; readonly colors: (theme: IColorTheme) => ICompositeBarColors; readonly hoverOptions: IActivityHoverOptions; readonly hasPopup?: boolean; + readonly compact?: boolean; } -export class ActivityActionViewItem extends BaseActionViewItem { +export class CompoisteBarActionViewItem extends BaseActionViewItem { private static hoverLeaveTime = 0; protected container!: HTMLElement; protected label!: HTMLElement; protected badge!: HTMLElement; - protected override readonly options: IActivityActionViewItemOptions; + protected override readonly options: ICompositeBarActionViewItemOptions; private badgeContent: HTMLElement | undefined; private readonly badgeDisposable = this._register(new MutableDisposable()); @@ -171,8 +162,8 @@ export class ActivityActionViewItem extends BaseActionViewItem { private readonly showHoverScheduler = new RunOnceScheduler(() => this.showHover(), 0); constructor( - action: ActivityAction, - options: IActivityActionViewItemOptions, + action: CompositeBarAction, + options: ICompositeBarActionViewItemOptions, private readonly badgesEnabled: (compositeId: string) => boolean, @IThemeService protected readonly themeService: IThemeService, @IHoverService private readonly hoverService: IHoverService, @@ -184,14 +175,14 @@ export class ActivityActionViewItem extends BaseActionViewItem { this.options = options; this._register(this.themeService.onDidColorThemeChange(this.onThemeChange, this)); - this._register(action.onDidChangeActivity(this.updateActivity, this)); + this._register(action.onDidChangeCompositeBarActionItem(() => this.update())); this._register(Event.filter(keybindingService.onDidUpdateKeybindings, () => this.keybindingLabel !== this.computeKeybindingLabel())(() => this.updateTitle())); - this._register(action.onDidChangeBadge(this.updateBadge, this)); + this._register(action.onDidChangeActivity(() => this.updateActivity())); this._register(toDisposable(() => this.showHoverScheduler.cancel())); } - protected get activity(): IActivity { - return (this._action as ActivityAction).activity; + protected get compositeBarActionItem(): ICompositeBarActionItem { + return (this._action as CompositeBarAction).compositeBarActionItem; } protected updateStyles(): void { @@ -201,7 +192,7 @@ export class ActivityActionViewItem extends BaseActionViewItem { if (this.label) { if (this.options.icon) { const foreground = this._action.checked ? colors.activeForegroundColor : colors.inactiveForegroundColor; - if (this.activity.iconUrl) { + if (this.compositeBarActionItem.iconUrl) { // Apply background color to activity bar item provided with iconUrls this.label.style.backgroundColor = foreground ? foreground.toString() : ''; this.label.style.color = ''; @@ -222,12 +213,12 @@ export class ActivityActionViewItem extends BaseActionViewItem { // Badge if (this.badgeContent) { - const badgeForeground = colors.badgeForeground; - const badgeBackground = colors.badgeBackground; + const badgeFg = colors.badgeForeground ?? theme.getColor(badgeForeground); + const badgeBg = colors.badgeBackground ?? theme.getColor(badgeBackground); const contrastBorderColor = theme.getColor(contrastBorder); - this.badgeContent.style.color = badgeForeground ? badgeForeground.toString() : ''; - this.badgeContent.style.backgroundColor = badgeBackground ? badgeBackground.toString() : ''; + this.badgeContent.style.color = badgeFg ? badgeFg.toString() : ''; + this.badgeContent.style.backgroundColor = badgeBg ? badgeBg.toString() : ''; this.badgeContent.style.borderStyle = contrastBorderColor ? 'solid' : ''; this.badgeContent.style.borderWidth = contrastBorderColor ? '1px' : ''; @@ -277,7 +268,7 @@ export class ActivityActionViewItem extends BaseActionViewItem { hide(this.badge); - this.updateActivity(); + this.update(); this.updateStyles(); this.updateHover(); } @@ -286,33 +277,49 @@ export class ActivityActionViewItem extends BaseActionViewItem { this.updateStyles(); } - protected updateActivity(): void { + protected update(): void { this.updateLabel(); + this.updateActivity(); this.updateTitle(); - this.updateBadge(); this.updateStyles(); } - protected updateBadge(): void { + protected updateActivity(): void { const action = this.action; - if (!this.badge || !this.badgeContent || !(action instanceof ActivityAction)) { + if (!this.badge || !this.badgeContent || !(action instanceof CompositeBarAction)) { return; } - const badge = action.getBadge(); - const clazz = action.getClass(); + const activity = action.activity; this.badgeDisposable.clear(); clearNode(this.badgeContent); hide(this.badge); - const shouldRenderBadges = this.badgesEnabled(this.activity.id); + const shouldRenderBadges = this.badgesEnabled(this.compositeBarActionItem.id); + + if (activity && shouldRenderBadges) { + + const { badge } = activity; + const classes: string[] = []; + + if (this.options.compact) { + classes.push('compact'); + } + + // Progress + if (badge instanceof ProgressBadge) { + show(this.badge); + classes.push('progress-badge'); + } - if (badge && shouldRenderBadges) { + else if (this.options.compact) { + show(this.badge); + } // Number - if (badge instanceof NumberBadge) { + else if (badge instanceof NumberBadge) { if (badge.number) { let number = badge.number.toString(); if (badge.number > 999) { @@ -342,16 +349,11 @@ export class ActivityActionViewItem extends BaseActionViewItem { show(this.badge); } - // Progress - else if (badge instanceof ProgressBadge) { - show(this.badge); + if (classes.length) { + this.badge.classList.add(...classes); + this.badgeDisposable.value = toDisposable(() => this.badge.classList.remove(...classes)); } - if (clazz) { - const classNames = clazz.split(' '); - this.badge.classList.add(...classNames); - this.badgeDisposable.value = toDisposable(() => this.badge.classList.remove(...classNames)); - } } this.updateTitle(); @@ -360,8 +362,8 @@ export class ActivityActionViewItem extends BaseActionViewItem { protected override updateLabel(): void { this.label.className = 'action-label'; - if (this.activity.classNames) { - this.label.classList.add(...this.activity.classNames); + if (this.compositeBarActionItem.classNames) { + this.label.classList.add(...this.compositeBarActionItem.classNames); } if (!this.options.icon) { @@ -382,8 +384,8 @@ export class ActivityActionViewItem extends BaseActionViewItem { protected computeTitle(): string { this.keybindingLabel = this.computeKeybindingLabel(); - let title = this.keybindingLabel ? localize('titleKeybinding', "{0} ({1})", this.activity.name, this.keybindingLabel) : this.activity.name; - const badge = (this.action as ActivityAction).getBadge(); + let title = this.keybindingLabel ? localize('titleKeybinding', "{0} ({1})", this.compositeBarActionItem.name, this.keybindingLabel) : this.compositeBarActionItem.name; + const badge = (this.action as CompositeBarAction).activity?.badge; if (badge?.getDescription()) { title = localize('badgeTitle', "{0} - {1}", title, badge.getDescription()); } @@ -392,7 +394,7 @@ export class ActivityActionViewItem extends BaseActionViewItem { } private computeKeybindingLabel(): string | undefined | null { - const keybinding = this.activity.keybindingId ? this.keybindingService.lookupKeybinding(this.activity.keybindingId) : null; + const keybinding = this.compositeBarActionItem.keybindingId ? this.keybindingService.lookupKeybinding(this.compositeBarActionItem.keybindingId) : null; return keybinding?.getLabel(); } @@ -404,7 +406,7 @@ export class ActivityActionViewItem extends BaseActionViewItem { this.hoverDisposables.add(addDisposableListener(this.container, EventType.MOUSE_OVER, () => { if (!this.showHoverScheduler.isScheduled()) { - if (Date.now() - ActivityActionViewItem.hoverLeaveTime < 200) { + if (Date.now() - CompoisteBarActionViewItem.hoverLeaveTime < 200) { this.showHover(true); } else { this.showHoverScheduler.schedule(this.configurationService.getValue('workbench.hover.delay')); @@ -414,7 +416,7 @@ export class ActivityActionViewItem extends BaseActionViewItem { this.hoverDisposables.add(addDisposableListener(this.container, EventType.MOUSE_LEAVE, e => { if (e.target === this.container) { - ActivityActionViewItem.hoverLeaveTime = Date.now(); + CompoisteBarActionViewItem.hoverLeaveTime = Date.now(); this.hoverService.hideHover(); this.showHoverScheduler.cancel(); } @@ -454,7 +456,7 @@ export class ActivityActionViewItem extends BaseActionViewItem { } } -export class CompositeOverflowActivityAction extends ActivityAction { +export class CompositeOverflowActivityAction extends CompositeBarAction { constructor( private showMenu: () => void @@ -471,12 +473,12 @@ export class CompositeOverflowActivityAction extends ActivityAction { } } -export class CompositeOverflowActivityActionViewItem extends ActivityActionViewItem { +export class CompositeOverflowActivityActionViewItem extends CompoisteBarActionViewItem { private actions: IAction[] = []; constructor( - action: ActivityAction, + action: CompositeBarAction, private getOverflowingComposites: () => { id: string; name?: string }[], private getActiveCompositeId: () => string | undefined, private getBadge: (compositeId: string) => IBadge, @@ -552,13 +554,13 @@ class ManageExtensionAction extends Action { } } -export class CompositeActionViewItem extends ActivityActionViewItem { +export class CompositeActionViewItem extends CompoisteBarActionViewItem { private static manageExtensionAction: ManageExtensionAction; constructor( - options: IActivityActionViewItemOptions, - private readonly compositeActivityAction: ActivityAction, + options: ICompositeBarActionViewItemOptions, + private readonly compositeActivityAction: CompositeBarAction, private readonly toggleCompositePinnedAction: IAction, private readonly toggleCompositeBadgeAction: IAction, private readonly compositeContextMenuActionsProvider: (compositeId: string) => IAction[], @@ -601,9 +603,9 @@ export class CompositeActionViewItem extends ActivityActionViewItem { // Allow to drag let insertDropBefore: Before2D | undefined = undefined; - this._register(CompositeDragAndDropObserver.INSTANCE.registerDraggable(this.container, () => { return { type: 'composite', id: this.activity.id }; }, { + this._register(CompositeDragAndDropObserver.INSTANCE.registerDraggable(this.container, () => { return { type: 'composite', id: this.compositeBarActionItem.id }; }, { onDragOver: e => { - const isValidMove = e.dragAndDropData.getData().id !== this.activity.id && this.dndHandler.onDragOver(e.dragAndDropData, this.activity.id, e.eventData); + const isValidMove = e.dragAndDropData.getData().id !== this.compositeBarActionItem.id && this.dndHandler.onDragOver(e.dragAndDropData, this.compositeBarActionItem.id, e.eventData); toggleDropEffect(e.eventData.dataTransfer, 'move', isValidMove); insertDropBefore = this.updateFromDragging(container, isValidMove, e.eventData); }, @@ -615,11 +617,11 @@ export class CompositeActionViewItem extends ActivityActionViewItem { }, onDrop: e => { EventHelper.stop(e.eventData, true); - this.dndHandler.drop(e.dragAndDropData, this.activity.id, e.eventData, insertDropBefore); + this.dndHandler.drop(e.dragAndDropData, this.compositeBarActionItem.id, e.eventData, insertDropBefore); insertDropBefore = this.updateFromDragging(container, false, e.eventData); }, onDragStart: e => { - if (e.dragAndDropData.getData().id !== this.activity.id) { + if (e.dragAndDropData.getData().id !== this.compositeBarActionItem.id) { return; } @@ -682,25 +684,25 @@ export class CompositeActionViewItem extends ActivityActionViewItem { private showContextMenu(container: HTMLElement): void { const actions: IAction[] = [this.toggleCompositePinnedAction, this.toggleCompositeBadgeAction]; - const compositeContextMenuActions = this.compositeContextMenuActionsProvider(this.activity.id); + const compositeContextMenuActions = this.compositeContextMenuActionsProvider(this.compositeBarActionItem.id); if (compositeContextMenuActions.length) { actions.push(...compositeContextMenuActions); } - if ((this.compositeActivityAction.activity).extensionId) { + if ((this.compositeActivityAction.compositeBarActionItem).extensionId) { actions.push(new Separator()); actions.push(CompositeActionViewItem.manageExtensionAction); } - const isPinned = this.compositeBar.isPinned(this.activity.id); + const isPinned = this.compositeBar.isPinned(this.compositeBarActionItem.id); if (isPinned) { - this.toggleCompositePinnedAction.label = localize('hide', "Hide '{0}'", this.activity.name); + this.toggleCompositePinnedAction.label = localize('hide', "Hide '{0}'", this.compositeBarActionItem.name); this.toggleCompositePinnedAction.checked = false; } else { - this.toggleCompositePinnedAction.label = localize('keep', "Keep '{0}'", this.activity.name); + this.toggleCompositePinnedAction.label = localize('keep', "Keep '{0}'", this.compositeBarActionItem.name); } - const isBadgeEnabled = this.compositeBar.areBadgesEnabled(this.activity.id); + const isBadgeEnabled = this.compositeBar.areBadgesEnabled(this.compositeBarActionItem.id); if (isBadgeEnabled) { this.toggleCompositeBadgeAction.label = localize('hideBadge', "Hide Badge"); } else { @@ -722,7 +724,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem { this.contextMenuService.showContextMenu({ getAnchor: () => anchor, getActions: () => actions, - getActionsContext: () => this.activity.id + getActionsContext: () => this.compositeBarActionItem.id }); } @@ -764,7 +766,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem { export class ToggleCompositePinnedAction extends Action { constructor( - private activity: IActivity | undefined, + private activity: ICompositeBarActionItem | undefined, private compositeBar: ICompositeBar ) { super('show.toggleCompositePinned', activity ? activity.name : localize('toggle', "Toggle View Pinned")); @@ -785,16 +787,16 @@ export class ToggleCompositePinnedAction extends Action { export class ToggleCompositeBadgeAction extends Action { constructor( - private activity: IActivity | undefined, + private compositeBarActionItem: ICompositeBarActionItem | undefined, private compositeBar: ICompositeBar ) { - super('show.toggleCompositeBadge', activity ? activity.name : localize('toggleBadge', "Toggle View Badge")); + super('show.toggleCompositeBadge', compositeBarActionItem ? compositeBarActionItem.name : localize('toggleBadge', "Toggle View Badge")); this.checked = false; } override async run(context: string): Promise { - const id = this.activity ? this.activity.id : context; + const id = this.compositeBarActionItem ? this.compositeBarActionItem.id : context; this.compositeBar.toggleBadgeEnablement(id); } } diff --git a/code/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts b/code/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts index 9c79677dca3..4bbbec9d22a 100644 --- a/code/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts +++ b/code/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts @@ -119,7 +119,7 @@ export class BrowserDialogHandler extends AbstractDialogHandler { } : undefined; const dialog = new Dialog( - this.layoutService.container, + this.layoutService.activeContainer, message, buttons, { @@ -127,7 +127,7 @@ export class BrowserDialogHandler extends AbstractDialogHandler { cancelId, type: this.getDialogType(type), keyEventProcessor: (event: StandardKeyboardEvent) => { - const resolved = this.keybindingService.softDispatch(event, this.layoutService.container); + const resolved = this.keybindingService.softDispatch(event, this.layoutService.activeContainer); if (resolved.kind === ResultKind.KbFound && resolved.commandId) { if (BrowserDialogHandler.ALLOWABLE_COMMANDS.indexOf(resolved.commandId) === -1) { EventHelper.stop(event, true); diff --git a/code/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/code/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 7289ba1aca6..d86ebb34475 100644 --- a/code/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/code/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -5,7 +5,6 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { localize } from 'vs/nls'; -import { URI } from 'vs/base/common/uri'; import { IEditorPaneRegistry, EditorPaneDescriptor } from 'vs/workbench/browser/editor'; import { IEditorFactoryRegistry, EditorExtensions } from 'vs/workbench/common/editor'; import { @@ -58,7 +57,6 @@ import { FloatingEditorClickMenu } from 'vs/workbench/browser/codeeditor'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { EditorAutoSave } from 'vs/workbench/browser/parts/editor/editorAutoSave'; -import { ThemeIcon } from 'vs/base/common/themables'; import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess'; import { ActiveGroupEditorsByMostRecentlyUsedQuickAccess, AllEditorsByAppearanceQuickAccess, AllEditorsByMostRecentlyUsedQuickAccess } from 'vs/workbench/browser/parts/editor/editorQuickAccess'; import { FileAccess } from 'vs/base/common/network'; @@ -68,6 +66,7 @@ import { UntitledTextEditorInputSerializer, UntitledTextEditorWorkingCopyEditorH import { DynamicEditorConfigurations } from 'vs/workbench/browser/parts/editor/editorConfiguration'; import { ToggleSeparatePinnedTabsAction, ToggleTabsVisibilityAction } from 'vs/workbench/browser/actions/layoutActions'; import product from 'vs/platform/product/common/product'; +import { ICommandAction } from 'vs/platform/action/common/action'; //#region Editor Registrations @@ -339,7 +338,7 @@ if (isMacintosh) { } // Empty Editor Group Toolbar -MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroup, { command: { id: UNLOCK_GROUP_COMMAND_ID, title: localize('unlockGroupAction', "Unlock Group"), icon: Codicon.lock }, group: 'navigation', order: 10, when: ActiveEditorGroupLockedContext }); +MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroup, { command: { id: UNLOCK_GROUP_COMMAND_ID, title: localize('unlockGroupAction', "Unlock Group"), icon: Codicon.lock, toggled: ContextKeyExpr.true() }, group: 'navigation', order: 10, when: ActiveEditorGroupLockedContext }); MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroup, { command: { id: CLOSE_EDITOR_GROUP_COMMAND_ID, title: localize('closeGroupAction', "Close Group"), icon: Codicon.close }, group: 'navigation', order: 20 }); // Empty Editor Group Context Menu @@ -383,14 +382,13 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: CLOSE_SAVED_EDI MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_KEEP_EDITORS_COMMAND_ID, title: localize('togglePreviewMode', "Enable Preview Editors"), toggled: ContextKeyExpr.has('config.workbench.editor.enablePreview') }, group: '7_settings', order: 10 }); MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_LOCK_GROUP_COMMAND_ID, title: localize('lockGroup', "Lock Group"), toggled: ActiveEditorGroupLockedContext }, group: '8_lock', order: 10, when: MultipleEditorGroupsContext }); -interface IEditorToolItem { id: string; title: string; icon?: { dark?: URI; light?: URI } | ThemeIcon } - -function appendEditorToolItem(primary: IEditorToolItem, when: ContextKeyExpression | undefined, order: number, alternative?: IEditorToolItem, precondition?: ContextKeyExpression | undefined): void { +function appendEditorToolItem(primary: ICommandAction, when: ContextKeyExpression | undefined, order: number, alternative?: ICommandAction, precondition?: ContextKeyExpression | undefined): void { const item: IMenuItem = { command: { id: primary.id, title: primary.title, icon: primary.icon, + toggled: primary.toggled, precondition }, group: 'navigation', @@ -523,7 +521,8 @@ appendEditorToolItem( { id: UNLOCK_GROUP_COMMAND_ID, title: localize('unlockEditorGroup', "Unlock Group"), - icon: Codicon.lock + icon: Codicon.lock, + toggled: ContextKeyExpr.true() }, ActiveEditorGroupLockedContext, CLOSE_ORDER - 1, // left to close action diff --git a/code/src/vs/workbench/browser/parts/editor/editorActions.ts b/code/src/vs/workbench/browser/parts/editor/editorActions.ts index 2abc360ac74..23d44d4f4db 100644 --- a/code/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/code/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -2441,17 +2441,13 @@ export class ExperimentalMoveEditorIntoNewWindowAction extends Action2 { const editorService = accessor.get(IEditorService); const editorGroupService = accessor.get(IEditorGroupsService); - const activeEditor = editorService.activeEditor; - if (!activeEditor) { + const activeEditorPane = editorService.activeEditorPane; + if (!activeEditorPane) { return; } const auxiliaryEditorPart = editorGroupService.createAuxiliaryEditorPart(); - await auxiliaryEditorPart.activeGroup.openEditor(activeEditor, { - pinned: true, - viewState: activeEditor.toUntyped({ preserveViewState: editorGroupService.activeGroup.id })?.options?.viewState, - }); - await editorGroupService.activeGroup.closeEditor(activeEditor); + activeEditorPane.group.moveEditor(activeEditorPane.input, auxiliaryEditorPart.activeGroup); } } diff --git a/code/src/vs/workbench/browser/parts/editor/editorAutoSave.ts b/code/src/vs/workbench/browser/parts/editor/editorAutoSave.ts index 5fed6253090..0abccd10742 100644 --- a/code/src/vs/workbench/browser/parts/editor/editorAutoSave.ts +++ b/code/src/vs/workbench/browser/parts/editor/editorAutoSave.ts @@ -14,6 +14,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { IWorkingCopy, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopy'; import { ILogService } from 'vs/platform/log/common/log'; +import { IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; export class EditorAutoSave extends Disposable implements IWorkbenchContribution { @@ -32,7 +33,8 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution @IEditorService private readonly editorService: IEditorService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, - @ILogService private readonly logService: ILogService + @ILogService private readonly logService: ILogService, + @IAccessibleNotificationService private readonly _accessibleNotificationService: IAccessibleNotificationService ) { super(); @@ -196,7 +198,7 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution // Save if dirty if (workingCopy.isDirty()) { this.logService.trace(`[editor auto save] running auto save`, workingCopy.resource.toString(), workingCopy.typeId); - + this._accessibleNotificationService.notifySaved(false); workingCopy.save({ reason: SaveReason.AUTO }); } }, this.autoSaveAfterDelay); diff --git a/code/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/code/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 495b28c2684..b34b28c1277 100644 --- a/code/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/code/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -343,7 +343,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Toolbar const containerToolbar = this._register(new ActionBar(toolbarContainer, { - ariaLabel: localize('ariaLabelGroupActions', "Empty editor group actions") + ariaLabel: localize('ariaLabelGroupActions', "Empty editor group actions"), + highlightToggledItems: true })); // Toolbar actions @@ -1043,8 +1044,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Conditionally lock the group if ( - isNew && // only if this editor was new for the group - this.count === 1 && // only when this editor was the first editor in the group + isNew && // only if this editor was new for the group + this.count === 1 && // only when this editor was the first editor in the group this.groupsView.groups.length > 1 // only when there are more than one groups open ) { // only when the editor identifier is configured as such @@ -1504,7 +1505,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // The only exception is when the same editor is opened on both sides of a side // by side editor (https://github.com/microsoft/vscode/issues/138442) - if (this.groupsView.groups.some(groupView => { + if (this.editorPartsView.groups.some(groupView => { if (groupView === this) { return false; // skip (we already handled our group above) } diff --git a/code/src/vs/workbench/browser/parts/editor/editorGroupWatermark.ts b/code/src/vs/workbench/browser/parts/editor/editorGroupWatermark.ts index 67d993e2f04..19389705739 100644 --- a/code/src/vs/workbench/browser/parts/editor/editorGroupWatermark.ts +++ b/code/src/vs/workbench/browser/parts/editor/editorGroupWatermark.ts @@ -3,13 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from 'vs/nls'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { isMacintosh, isWeb, OS } from 'vs/base/common/platform'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import * as nls from 'vs/nls'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { append, clearNode, $, h } from 'vs/base/browser/dom'; import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; @@ -18,25 +17,25 @@ import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from 'vs/pla import { defaultKeybindingLabelStyles } from 'vs/platform/theme/browser/defaultStyles'; interface WatermarkEntry { - text: string; - id: string; - mac?: boolean; - when?: ContextKeyExpression; + readonly text: string; + readonly id: string; + readonly mac?: boolean; + readonly when?: ContextKeyExpression; } -const showCommands: WatermarkEntry = { text: nls.localize('watermark.showCommands', "Show All Commands"), id: 'workbench.action.showCommands' }; -const quickAccess: WatermarkEntry = { text: nls.localize('watermark.quickAccess', "Go to File"), id: 'workbench.action.quickOpen' }; -const openFileNonMacOnly: WatermarkEntry = { text: nls.localize('watermark.openFile', "Open File"), id: 'workbench.action.files.openFile', mac: false }; -const openFolderNonMacOnly: WatermarkEntry = { text: nls.localize('watermark.openFolder', "Open Folder"), id: 'workbench.action.files.openFolder', mac: false }; -const openFileOrFolderMacOnly: WatermarkEntry = { text: nls.localize('watermark.openFileFolder', "Open File or Folder"), id: 'workbench.action.files.openFileFolder', mac: true }; -const openRecent: WatermarkEntry = { text: nls.localize('watermark.openRecent', "Open Recent"), id: 'workbench.action.openRecent' }; -const newUntitledFile: WatermarkEntry = { text: nls.localize('watermark.newUntitledFile', "New Untitled Text File"), id: 'workbench.action.files.newUntitledFile' }; +const showCommands: WatermarkEntry = { text: localize('watermark.showCommands', "Show All Commands"), id: 'workbench.action.showCommands' }; +const quickAccess: WatermarkEntry = { text: localize('watermark.quickAccess', "Go to File"), id: 'workbench.action.quickOpen' }; +const openFileNonMacOnly: WatermarkEntry = { text: localize('watermark.openFile', "Open File"), id: 'workbench.action.files.openFile', mac: false }; +const openFolderNonMacOnly: WatermarkEntry = { text: localize('watermark.openFolder', "Open Folder"), id: 'workbench.action.files.openFolder', mac: false }; +const openFileOrFolderMacOnly: WatermarkEntry = { text: localize('watermark.openFileFolder', "Open File or Folder"), id: 'workbench.action.files.openFileFolder', mac: true }; +const openRecent: WatermarkEntry = { text: localize('watermark.openRecent', "Open Recent"), id: 'workbench.action.openRecent' }; +const newUntitledFile: WatermarkEntry = { text: localize('watermark.newUntitledFile', "New Untitled Text File"), id: 'workbench.action.files.newUntitledFile' }; const newUntitledFileMacOnly: WatermarkEntry = Object.assign({ mac: true }, newUntitledFile); -const findInFiles: WatermarkEntry = { text: nls.localize('watermark.findInFiles', "Find in Files"), id: 'workbench.action.findInFiles' }; -const toggleTerminal: WatermarkEntry = { text: nls.localize({ key: 'watermark.toggleTerminal', comment: ['toggle is a verb here'] }, "Toggle Terminal"), id: 'workbench.action.terminal.toggleTerminal', when: ContextKeyExpr.equals('terminalProcessSupported', true) }; -const startDebugging: WatermarkEntry = { text: nls.localize('watermark.startDebugging', "Start Debugging"), id: 'workbench.action.debug.start', when: ContextKeyExpr.equals('terminalProcessSupported', true) }; -const toggleFullscreen: WatermarkEntry = { text: nls.localize({ key: 'watermark.toggleFullscreen', comment: ['toggle is a verb here'] }, "Toggle Full Screen"), id: 'workbench.action.toggleFullScreen', when: ContextKeyExpr.equals('terminalProcessSupported', true).negate() }; -const showSettings: WatermarkEntry = { text: nls.localize('watermark.showSettings', "Show Settings"), id: 'workbench.action.openSettings', when: ContextKeyExpr.equals('terminalProcessSupported', true).negate() }; +const findInFiles: WatermarkEntry = { text: localize('watermark.findInFiles', "Find in Files"), id: 'workbench.action.findInFiles' }; +const toggleTerminal: WatermarkEntry = { text: localize({ key: 'watermark.toggleTerminal', comment: ['toggle is a verb here'] }, "Toggle Terminal"), id: 'workbench.action.terminal.toggleTerminal', when: ContextKeyExpr.equals('terminalProcessSupported', true) }; +const startDebugging: WatermarkEntry = { text: localize('watermark.startDebugging', "Start Debugging"), id: 'workbench.action.debug.start', when: ContextKeyExpr.equals('terminalProcessSupported', true) }; +const toggleFullscreen: WatermarkEntry = { text: localize({ key: 'watermark.toggleFullscreen', comment: ['toggle is a verb here'] }, "Toggle Full Screen"), id: 'workbench.action.toggleFullScreen', when: ContextKeyExpr.equals('terminalProcessSupported', true).negate() }; +const showSettings: WatermarkEntry = { text: localize('watermark.showSettings', "Show Settings"), id: 'workbench.action.openSettings', when: ContextKeyExpr.equals('terminalProcessSupported', true).negate() }; const noFolderEntries = [ showCommands, @@ -58,15 +57,13 @@ const folderEntries = [ ]; export class EditorGroupWatermark extends Disposable { - private readonly shortcuts: HTMLElement; - private transientDisposables = this._register(new DisposableStore()); + private readonly transientDisposables = this._register(new DisposableStore()); private enabled: boolean = false; private workbenchState: WorkbenchState; constructor( container: HTMLElement, - @ILifecycleService private readonly lifecycleService: ILifecycleService, @IKeybindingService private readonly keybindingService: IKeybindingService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @@ -90,8 +87,6 @@ export class EditorGroupWatermark extends Disposable { } private registerListeners(): void { - this._register(this.lifecycleService.onDidShutdown(() => this.dispose())); - this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('workbench.tips.enabled')) { this.render(); diff --git a/code/src/vs/workbench/browser/parts/editor/editorPanes.ts b/code/src/vs/workbench/browser/parts/editor/editorPanes.ts index 69911cb3d1b..6bc385910a1 100644 --- a/code/src/vs/workbench/browser/parts/editor/editorPanes.ts +++ b/code/src/vs/workbench/browser/parts/editor/editorPanes.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { IAction } from 'vs/base/common/actions'; +import { IAction, toAction } from 'vs/base/common/actions'; import { Emitter } from 'vs/base/common/event'; import Severity from 'vs/base/common/severity'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { EditorExtensions, EditorInputCapabilities, IEditorOpenContext, IVisibleEditorPane, isEditorOpenError } from 'vs/workbench/common/editor'; +import { EditorExtensions, EditorInputCapabilities, IEditorOpenContext, IVisibleEditorPane, createEditorOpenError, isEditorOpenError } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { Dimension, show, hide, IDomNodePagePosition, isAncestor, getWindow, getActiveWindow } from 'vs/base/browser/dom'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -128,7 +128,23 @@ export class EditorPanes extends Disposable { async openEditor(editor: EditorInput, options: IEditorOptions | undefined, context: IEditorOpenContext = Object.create(null)): Promise { try { - return await this.doOpenEditor(this.getEditorPaneDescriptor(editor), editor, options, context); + + // Assert the `EditorInputCapabilities.AuxWindowUnsupported` condition + // TODO@bpasero revisit this once all editors can support aux windows + if (getWindow(this.editorPanesParent) !== window && editor.hasCapability(EditorInputCapabilities.AuxWindowUnsupported)) { + return await this.doShowError(createEditorOpenError(localize('editorUnsupportedInAuxWindow', "This type of editor cannot be opened in floating windows yet."), [ + toAction({ + id: 'workbench.editor.action.closeEditor', label: localize('openFolder', "Close Editor"), run: async () => { + return this.groupView.closeEditor(editor); + } + }) + ], { forceMessage: true, forceSeverity: Severity.Warning }), editor, options, context); + } + + // Open editor normally + else { + return await this.doOpenEditor(this.getEditorPaneDescriptor(editor), editor, options, context); + } } catch (error) { // First check if caller instructed us to ignore error handling diff --git a/code/src/vs/workbench/browser/parts/editor/editorParts.ts b/code/src/vs/workbench/browser/parts/editor/editorParts.ts index 9147cdac8d7..492b8c63b76 100644 --- a/code/src/vs/workbench/browser/parts/editor/editorParts.ts +++ b/code/src/vs/workbench/browser/parts/editor/editorParts.ts @@ -13,6 +13,7 @@ import { IEditorGroupView, IEditorPartsView } from 'vs/workbench/browser/parts/e import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IAuxiliaryWindowService } from 'vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService'; +import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; export class EditorParts extends Disposable implements IEditorGroupsService, IEditorPartsView { @@ -22,7 +23,8 @@ export class EditorParts extends Disposable implements IEditorGroupsService, IEd constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, - @IAuxiliaryWindowService private readonly auxiliaryWindowService: IAuxiliaryWindowService + @IAuxiliaryWindowService private readonly auxiliaryWindowService: IAuxiliaryWindowService, + @ILifecycleService private readonly lifecycleService: ILifecycleService ) { super(); @@ -48,10 +50,14 @@ export class EditorParts extends Disposable implements IEditorGroupsService, IEd const editorPart = disposables.add(this.instantiationService.createInstance(AuxiliaryEditorPart, this)); disposables.add(this.registerEditorPart(editorPart)); + disposables.add(Event.once(editorPart.onDidClose)(() => disposables.dispose())); + disposables.add(Event.once(this.lifecycleService.onDidShutdown)(() => disposables.dispose())); editorPart.create(partContainer, { restorePreviousState: false }); - disposables.add(auxiliaryWindow.onDidResize(dimension => editorPart.layout(dimension.width, dimension.height, 0, 0))); + + disposables.add(auxiliaryWindow.onWillLayout(dimension => editorPart.layout(dimension.width, dimension.height, 0, 0))); + auxiliaryWindow.layout(); this._onDidAddGroup.fire(editorPart.activeGroup); @@ -205,14 +211,16 @@ export class EditorParts extends Disposable implements IEditorGroupsService, IEd } getGroup(identifier: GroupIdentifier): IEditorGroupView | undefined { - for (const part of this.parts) { - const group = part.getGroup(identifier); - if (group) { - return group; + if (this.parts.size > 1) { + for (const part of this.parts) { + const group = part.getGroup(identifier); + if (group) { + return group; + } } } - return undefined; + return this.mainEditorPart.getGroup(identifier); } activateGroup(group: IEditorGroupView | GroupIdentifier): IEditorGroupView { @@ -227,6 +235,8 @@ export class EditorParts extends Disposable implements IEditorGroupsService, IEd return this.getPart(group).setSize(group, size); } + get contentDimension() { return this.activePart.contentDimension; } + arrangeGroups(arrangement: GroupsArrangement): void { return this.activePart.arrangeGroups(arrangement); } @@ -297,11 +307,13 @@ export class EditorParts extends Disposable implements IEditorGroupsService, IEd //#endregion - //#region TODO@bpasero TO BE INVESTIGATED + //#region Main Editor Part Only - get onDidVisibilityChange() { return this.mainEditorPart.onDidVisibilityChange; } + get isReady() { return this.mainEditorPart.isReady; } + get whenReady() { return this.mainEditorPart.whenReady; } + get whenRestored() { return this.mainEditorPart.whenRestored; } + get hasRestorableState() { return this.mainEditorPart.hasRestorableState; } - get contentDimension() { return this.mainEditorPart.contentDimension; } get partOptions() { return this.mainEditorPart.partOptions; } get onDidChangeEditorPartOptions() { return this.mainEditorPart.onDidChangeEditorPartOptions; } @@ -310,15 +322,6 @@ export class EditorParts extends Disposable implements IEditorGroupsService, IEd } //#endregion - - //#region Main Editor Part Only - - get isReady() { return this.mainEditorPart.isReady; } - get whenReady() { return this.mainEditorPart.whenReady; } - get whenRestored() { return this.mainEditorPart.whenRestored; } - get hasRestorableState() { return this.mainEditorPart.hasRestorableState; } - - //#endregion } registerSingleton(IEditorGroupsService, EditorParts, InstantiationType.Eager); diff --git a/code/src/vs/workbench/browser/parts/editor/editorTabsControl.ts b/code/src/vs/workbench/browser/parts/editor/editorTabsControl.ts index e862a401765..d2ee98e46a0 100644 --- a/code/src/vs/workbench/browser/parts/editor/editorTabsControl.ts +++ b/code/src/vs/workbench/browser/parts/editor/editorTabsControl.ts @@ -176,7 +176,7 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC telemetrySource: 'editorPart', resetMenu: MenuId.EditorTitle, overflowBehavior: { maxItems: 9, exempted: EDITOR_CORE_NAVIGATION_COMMANDS }, - highlightToggledItems: true, + highlightToggledItems: true })); // Context diff --git a/code/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/code/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index 65511dc0221..af5579120fd 100644 --- a/code/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/code/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -39,7 +39,7 @@ import { CloseOneEditorAction, UnpinEditorAction } from 'vs/workbench/browser/pa import { assertAllDefined, assertIsDefined } from 'vs/base/common/types'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { basenameOrAuthority } from 'vs/base/common/resources'; -import { RunOnceScheduler } from 'vs/base/common/async'; +import { RunOnceScheduler, runWhenIdle } from 'vs/base/common/async'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { IPath, win32, posix } from 'vs/base/common/path'; import { coalesce, insert } from 'vs/base/common/arrays'; @@ -55,6 +55,7 @@ import { IEditorResolverService } from 'vs/workbench/services/editor/common/edit import { IEditorTitleControlDimensions } from 'vs/workbench/browser/parts/editor/editorTitleControl'; import { StickyEditorGroupModel, UnstickyEditorGroupModel } from 'vs/workbench/common/editor/filteredEditorGroupModel'; import { IReadonlyEditorGroupModel } from 'vs/workbench/common/editor/editorGroupModel'; +import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; interface IEditorInputLabel { readonly editor: EditorInput; @@ -150,7 +151,8 @@ export class MultiEditorTabsControl extends EditorTabsControl { @IPathService private readonly pathService: IPathService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @ITreeViewsDnDService private readonly treeViewsDragAndDropService: ITreeViewsDnDService, - @IEditorResolverService editorResolverService: IEditorResolverService + @IEditorResolverService editorResolverService: IEditorResolverService, + @ILifecycleService private readonly lifecycleService: ILifecycleService ) { super(parent, editorPartsView, groupsView, groupView, tabsModel, contextMenuService, instantiationService, contextKeyService, keybindingService, notificationService, menuService, quickInputService, themeService, editorResolverService); @@ -1586,9 +1588,10 @@ export class MultiEditorTabsControl extends EditorTabsControl { if (this.visible) { // The layout of tabs can be an expensive operation because we access DOM properties // that can result in the browser doing a full page layout to validate them. To buffer - // this a little bit we try at least to schedule this work on the next animation frame. + // this a little bit we try at least to schedule this work on the next animation frame + // when we have restored or when idle otherwise. if (!this.layoutScheduler.value) { - const scheduledLayout = scheduleAtNextAnimationFrame(() => { + const scheduledLayout = (this.lifecycleService.phase >= LifecyclePhase.Restored ? scheduleAtNextAnimationFrame : runWhenIdle)(() => { this.doLayout(this.dimensions, this.layoutScheduler.value?.options /* ensure to pick up latest options */); this.layoutScheduler.clear(); diff --git a/code/src/vs/workbench/browser/parts/globalCompositeBar.ts b/code/src/vs/workbench/browser/parts/globalCompositeBar.ts index 12f46f951bf..f7391720341 100644 --- a/code/src/vs/workbench/browser/parts/globalCompositeBar.ts +++ b/code/src/vs/workbench/browser/parts/globalCompositeBar.ts @@ -4,56 +4,92 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { ActionsOrientation, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { GLOBAL_ACTIVITY_ID, IActivity, ACCOUNTS_ACTIVITY_ID } from 'vs/workbench/common/activity'; -import { GlobalActivityActionViewItem, AccountsActivityActionViewItem } from 'vs/workbench/browser/parts/activitybar/activitybarActions'; -import { IBadge, NumberBadge } from 'vs/workbench/services/activity/common/activity'; +import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ACCOUNTS_ACTIVITY_ID, GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity'; +import { IActivity, IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IDisposable, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; -import { IColorTheme } from 'vs/platform/theme/common/themeService'; +import { DisposableStore, Disposable } from 'vs/base/common/lifecycle'; +import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { ActivityAction, IActivityHoverOptions, ICompositeActivity, ICompositeBarColors } from 'vs/workbench/browser/parts/compositeBarActions'; +import { CompoisteBarActionViewItem, CompositeBarAction, IActivityHoverOptions, ICompositeBarActionViewItemOptions, ICompositeBarColors } from 'vs/workbench/browser/parts/compositeBarActions'; import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; +import { Action, IAction, Separator, SubmenuAction, toAction } from 'vs/base/common/actions'; +import { IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { addDisposableListener, EventType, append, clearNode, hide, show, EventHelper, $ } from 'vs/base/browser/dom'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; +import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch'; +import { AnchorAlignment, AnchorAxisAlignment } from 'vs/base/browser/ui/contextview/contextview'; +import { runWhenIdle } from 'vs/base/common/async'; +import { Lazy } from 'vs/base/common/lazy'; +import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { ISecretStorageService } from 'vs/platform/secrets/common/secrets'; +import { AuthenticationSessionInfo, getCurrentAuthenticationSessionInfo } from 'vs/workbench/services/authentication/browser/authenticationService'; +import { AuthenticationSessionAccount, IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { DEFAULT_ICON } from 'vs/workbench/services/userDataProfile/common/userDataProfileIcons'; -import { IAction, toAction } from 'vs/base/common/actions'; +import { isString } from 'vs/base/common/types'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND } from 'vs/workbench/common/theme'; export class GlobalCompositeBar extends Disposable { private static readonly ACCOUNTS_ACTION_INDEX = 0; - private static readonly ACCOUNTS_ICON = registerIcon('accounts-view-bar-icon', Codicon.account, localize('accountsViewBarIcon', "Accounts icon in the view bar.")); + static readonly ACCOUNTS_ICON = registerIcon('accounts-view-bar-icon', Codicon.account, localize('accountsViewBarIcon', "Accounts icon in the view bar.")); readonly element: HTMLElement; - private readonly globalActivityActionBar: ActionBar; - private readonly globalActivityAction: ActivityAction; - private readonly globalActivity: ICompositeActivity[] = []; - private accountsActivityAction: ActivityAction | undefined; - private readonly accountsActivity: ICompositeActivity[] = []; + private readonly globalActivityAction = this._register(new Action(GLOBAL_ACTIVITY_ID)); + private readonly accountAction = this._register(new Action(ACCOUNTS_ACTIVITY_ID)); + private readonly globalActivityActionBar: ActionBar; constructor( private readonly contextMenuActionsProvider: () => IAction[], private readonly colors: (theme: IColorTheme) => ICompositeBarColors, private readonly activityHoverOptions: IActivityHoverOptions, + @IConfigurationService configurationService: IConfigurationService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IStorageService private readonly storageService: IStorageService, @IExtensionService private readonly extensionService: IExtensionService, - @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, ) { super(); this.element = document.createElement('div'); + const anchorAlignment = configurationService.getValue('workbench.sideBar.location') === 'left' ? AnchorAlignment.RIGHT : AnchorAlignment.LEFT; + const anchorAxisAlignment = AnchorAxisAlignment.HORIZONTAL; this.globalActivityActionBar = this._register(new ActionBar(this.element, { actionViewItemProvider: action => { - if (action.id === 'workbench.actions.manage') { - return this.instantiationService.createInstance(GlobalActivityActionViewItem, action as ActivityAction, this.contextMenuActionsProvider, this.colors, this.activityHoverOptions); + if (action.id === GLOBAL_ACTIVITY_ID) { + return this.instantiationService.createInstance(GlobalActivityActionViewItem, this.contextMenuActionsProvider, { colors: this.colors, hoverOptions: this.activityHoverOptions }, anchorAlignment, anchorAxisAlignment); } - if (action.id === 'workbench.actions.accounts') { - return this.instantiationService.createInstance(AccountsActivityActionViewItem, action as ActivityAction, this.contextMenuActionsProvider, this.colors, this.activityHoverOptions); + if (action.id === ACCOUNTS_ACTIVITY_ID) { + return this.instantiationService.createInstance(AccountsActivityActionViewItem, + this.contextMenuActionsProvider, + { + colors: this.colors, + hoverOptions: this.activityHoverOptions + }, + anchorAlignment, + anchorAxisAlignment, + (actions: IAction[]) => { + actions.unshift(...[ + toAction({ id: 'hideAccounts', label: localize('hideAccounts', "Hide Accounts"), run: () => this.storageService.store(AccountsActivityActionViewItem.ACCOUNTS_VISIBILITY_PREFERENCE_KEY, false, StorageScope.PROFILE, StorageTarget.USER) }), + new Separator() + ]); + }); } throw new Error(`No view item for action '${action.id}'`); @@ -64,21 +100,8 @@ export class GlobalCompositeBar extends Disposable { preventLoopNavigation: true })); - this.globalActivityAction = this._register(new ActivityAction(this.createGlobalActivity())); - this._register(this.userDataProfileService.onDidChangeCurrentProfile(e => { - if (this.globalActivityAction) { - this.globalActivityAction.activity = this.createGlobalActivity(); - } - })); - if (this.accountsVisibilityPreference) { - this.accountsActivityAction = this._register(new ActivityAction({ - id: 'workbench.actions.accounts', - name: localize('accounts', "Accounts"), - classNames: ThemeIcon.asClassNameArray(GlobalCompositeBar.ACCOUNTS_ICON) - })); - - this.globalActivityActionBar.push(this.accountsActivityAction, { index: GlobalCompositeBar.ACCOUNTS_ACTION_INDEX }); + this.globalActivityActionBar.push(this.accountAction, { index: GlobalCompositeBar.ACCOUNTS_ACTION_INDEX }); } this.globalActivityActionBar.push(this.globalActivityAction); @@ -94,70 +117,84 @@ export class GlobalCompositeBar extends Disposable { })); } - showActivity(viewContainerOrActionId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable | undefined { - if (viewContainerOrActionId === GLOBAL_ACTIVITY_ID) { - return this.showGlobalActivity(GLOBAL_ACTIVITY_ID, badge, clazz, priority); - } - - if (viewContainerOrActionId === ACCOUNTS_ACTIVITY_ID) { - return this.showGlobalActivity(ACCOUNTS_ACTIVITY_ID, badge, clazz, priority); - } + create(parent: HTMLElement): void { + parent.appendChild(this.element); + } - return undefined; + focus(): void { + this.globalActivityActionBar.focus(true); } - private showGlobalActivity(activityId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable { - if (typeof priority !== 'number') { - priority = 0; - } + size(): number { + return this.globalActivityActionBar.viewItems.length; + } - const activity: ICompositeActivity = { badge, clazz, priority }; - const activityCache = activityId === GLOBAL_ACTIVITY_ID ? this.globalActivity : this.accountsActivity; + getContextMenuActions(): IAction[] { + return [toAction({ id: 'toggleAccountsVisibility', label: localize('accounts', "Accounts"), checked: this.accountsVisibilityPreference, run: () => this.accountsVisibilityPreference = !this.accountsVisibilityPreference })]; + } - for (let i = 0; i <= activityCache.length; i++) { - if (i === activityCache.length) { - activityCache.push(activity); - break; - } else if (activityCache[i].priority <= priority) { - activityCache.splice(i, 0, activity); - break; - } + private toggleAccountsActivity() { + if (this.globalActivityActionBar.length() === 2 && this.accountsVisibilityPreference) { + return; + } + if (this.globalActivityActionBar.length() === 2) { + this.globalActivityActionBar.pull(GlobalCompositeBar.ACCOUNTS_ACTION_INDEX); + } else { + this.globalActivityActionBar.push(this.accountAction, { index: GlobalCompositeBar.ACCOUNTS_ACTION_INDEX }); } - this.updateGlobalActivity(activityId); + } - return toDisposable(() => this.removeGlobalActivity(activityId, activity)); + private get accountsVisibilityPreference(): boolean { + return this.storageService.getBoolean(AccountsActivityActionViewItem.ACCOUNTS_VISIBILITY_PREFERENCE_KEY, StorageScope.PROFILE, true); } - private removeGlobalActivity(activityId: string, activity: ICompositeActivity): void { - const activityCache = activityId === GLOBAL_ACTIVITY_ID ? this.globalActivity : this.accountsActivity; - const index = activityCache.indexOf(activity); - if (index !== -1) { - activityCache.splice(index, 1); - this.updateGlobalActivity(activityId); - } + private set accountsVisibilityPreference(value: boolean) { + this.storageService.store(AccountsActivityActionViewItem.ACCOUNTS_VISIBILITY_PREFERENCE_KEY, value, StorageScope.PROFILE, StorageTarget.USER); } +} - private updateGlobalActivity(activityId: string): void { - const activityAction = activityId === GLOBAL_ACTIVITY_ID ? this.globalActivityAction : this.accountsActivityAction; - if (!activityAction) { - return; - } +abstract class AbstractGlobalActivityActionViewItem extends CompoisteBarActionViewItem { + + constructor( + private readonly menuId: MenuId, + action: CompositeBarAction, + options: ICompositeBarActionViewItemOptions, + private readonly contextMenuActionsProvider: () => IAction[], + private readonly anchorAlignment: AnchorAlignment | undefined, + private readonly anchorAxisAlignment: AnchorAxisAlignment | undefined, + @IThemeService themeService: IThemeService, + @IHoverService hoverService: IHoverService, + @IMenuService private readonly menuService: IMenuService, + @IContextMenuService private readonly contextMenuService: IContextMenuService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IConfigurationService configurationService: IConfigurationService, + @IKeybindingService keybindingService: IKeybindingService, + @IActivityService private readonly activityService: IActivityService, + ) { + super(action, { draggable: false, icon: true, hasPopup: true, ...options }, () => true, themeService, hoverService, configurationService, keybindingService); - const activityCache = activityId === GLOBAL_ACTIVITY_ID ? this.globalActivity : this.accountsActivity; - if (activityCache.length) { - const [{ badge, clazz, priority }] = activityCache; - if (badge instanceof NumberBadge && activityCache.length > 1) { - const cumulativeNumberBadge = this.getCumulativeNumberBadge(activityCache, priority); - activityAction.setBadge(cumulativeNumberBadge); - } else { - activityAction.setBadge(badge, clazz); + this.updateItemActivity(); + this._register(this.activityService.onDidChangeActivity(viewContainerOrAction => { + if (isString(viewContainerOrAction) && viewContainerOrAction === this.compositeBarActionItem.id) { + this.updateItemActivity(); + } + })); + } + + private updateItemActivity(): void { + const activities = this.activityService.getActivity(this.compositeBarActionItem.id); + let activity = activities[0]; + if (activity) { + const { badge, priority } = activity; + if (badge instanceof NumberBadge && activities.length > 1) { + const cumulativeNumberBadge = this.getCumulativeNumberBadge(activities, priority ?? 0); + activity = { badge: cumulativeNumberBadge }; } - } else { - activityAction.setBadge(undefined); } + (this.action as CompositeBarAction).activity = activity; } - private getCumulativeNumberBadge(activityCache: ICompositeActivity[], priority: number): NumberBadge { + private getCumulativeNumberBadge(activityCache: IActivity[], priority: number): NumberBadge { const numberActivities = activityCache.filter(activity => activity.badge instanceof NumberBadge && activity.priority === priority); const number = numberActivities.reduce((result, activity) => { return result + (activity.badge).number; }, 0); const descriptorFn = (): string => { @@ -174,56 +211,453 @@ export class GlobalCompositeBar extends Disposable { return new NumberBadge(number, descriptorFn); } - create(parent: HTMLElement): void { - parent.appendChild(this.element); + override render(container: HTMLElement): void { + super.render(container); + + this._register(addDisposableListener(this.container, EventType.MOUSE_DOWN, async (e: MouseEvent) => { + EventHelper.stop(e, true); + const isLeftClick = e?.button !== 2; + // Left-click run + if (isLeftClick) { + this.run(); + } + })); + + // The rest of the activity bar uses context menu event for the context menu, so we match this + this._register(addDisposableListener(this.container, EventType.CONTEXT_MENU, async (e: MouseEvent) => { + const disposables = new DisposableStore(); + const actions = await this.resolveContextMenuActions(disposables); + + const event = new StandardMouseEvent(e); + + this.contextMenuService.showContextMenu({ + getAnchor: () => event, + getActions: () => actions, + onHide: () => disposables.dispose() + }); + })); + + this._register(addDisposableListener(this.container, EventType.KEY_UP, (e: KeyboardEvent) => { + const event = new StandardKeyboardEvent(e); + if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) { + EventHelper.stop(e, true); + this.run(); + } + })); + + this._register(addDisposableListener(this.container, TouchEventType.Tap, (e: GestureEvent) => { + EventHelper.stop(e, true); + this.run(); + })); } - focus(): void { - this.globalActivityActionBar.focus(true); + protected async resolveContextMenuActions(disposables: DisposableStore): Promise { + return this.contextMenuActionsProvider(); } - size(): number { - return this.globalActivityActionBar.viewItems.length; + private async run(): Promise { + const disposables = new DisposableStore(); + const menu = disposables.add(this.menuService.createMenu(this.menuId, this.contextKeyService)); + const actions = await this.resolveMainMenuActions(menu, disposables); + + this.contextMenuService.showContextMenu({ + getAnchor: () => this.label, + anchorAlignment: this.anchorAlignment, + anchorAxisAlignment: this.anchorAxisAlignment, + getActions: () => actions, + onHide: () => disposables.dispose(), + menuActionOptions: { renderShortTitle: true }, + }); + } - getContextMenuActions(): IAction[] { - return [toAction({ id: 'toggleAccountsVisibility', label: localize('accounts', "Accounts"), checked: this.accountsVisibilityPreference, run: () => this.accountsVisibilityPreference = !this.accountsVisibilityPreference })]; + protected async resolveMainMenuActions(menu: IMenu, _disposable: DisposableStore): Promise { + const actions: IAction[] = []; + createAndFillInActionBarActions(menu, { renderShortTitle: true }, { primary: [], secondary: actions }); + return actions; } +} - private createGlobalActivity(): IActivity { - return { - id: 'workbench.actions.manage', - name: localize('manage', "Manage"), - classNames: ThemeIcon.asClassNameArray(this.userDataProfileService.currentProfile.icon ? ThemeIcon.fromId(this.userDataProfileService.currentProfile.icon) : DEFAULT_ICON), - }; +export class AccountsActivityActionViewItem extends AbstractGlobalActivityActionViewItem { + + static readonly ACCOUNTS_VISIBILITY_PREFERENCE_KEY = 'workbench.activity.showAccounts'; + + private readonly groupedAccounts: Map = new Map(); + private readonly problematicProviders: Set = new Set(); + + private initialized = false; + private sessionFromEmbedder = new Lazy>(() => getCurrentAuthenticationSessionInfo(this.secretStorageService, this.productService)); + + constructor( + contextMenuActionsProvider: () => IAction[], + options: ICompositeBarActionViewItemOptions, + anchorAlignment: AnchorAlignment | undefined, + anchorAxisAlignment: AnchorAxisAlignment | undefined, + private readonly fillContextMenuActions: (actions: IAction[]) => void, + @IThemeService themeService: IThemeService, + @ILifecycleService private readonly lifecycleService: ILifecycleService, + @IHoverService hoverService: IHoverService, + @IContextMenuService contextMenuService: IContextMenuService, + @IMenuService menuService: IMenuService, + @IContextKeyService contextKeyService: IContextKeyService, + @IAuthenticationService private readonly authenticationService: IAuthenticationService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IProductService private readonly productService: IProductService, + @IConfigurationService configurationService: IConfigurationService, + @IKeybindingService keybindingService: IKeybindingService, + @ISecretStorageService private readonly secretStorageService: ISecretStorageService, + @ILogService private readonly logService: ILogService, + @IActivityService activityService: IActivityService, + @IInstantiationService instantiationService: IInstantiationService, + ) { + const action = instantiationService.createInstance(CompositeBarAction, { + id: ACCOUNTS_ACTIVITY_ID, + name: localize('accounts', "Accounts"), + classNames: ThemeIcon.asClassNameArray(GlobalCompositeBar.ACCOUNTS_ICON) + }); + super(MenuId.AccountsContext, action, options, contextMenuActionsProvider, anchorAlignment, anchorAxisAlignment, themeService, hoverService, menuService, contextMenuService, contextKeyService, configurationService, keybindingService, activityService); + this._register(action); + this.registerListeners(); + this.initialize(); } - private toggleAccountsActivity() { - if (!!this.accountsActivityAction === this.accountsVisibilityPreference) { - return; + private registerListeners(): void { + this._register(this.authenticationService.onDidRegisterAuthenticationProvider(async (e) => { + await this.addAccountsFromProvider(e.id); + })); + + this._register(this.authenticationService.onDidUnregisterAuthenticationProvider((e) => { + this.groupedAccounts.delete(e.id); + this.problematicProviders.delete(e.id); + })); + + this._register(this.authenticationService.onDidChangeSessions(async e => { + for (const changed of [...e.event.changed, ...e.event.added]) { + try { + await this.addOrUpdateAccount(e.providerId, changed.account); + } catch (e) { + this.logService.error(e); + } + } + for (const removed of e.event.removed) { + this.removeAccount(e.providerId, removed.account); + } + })); + } + + // This function exists to ensure that the accounts are added for auth providers that had already been registered + // before the menu was created. + private async initialize(): Promise { + // Resolving the menu doesn't need to happen immediately, so we can wait until after the workbench has been restored + // and only run this when the system is idle. + await this.lifecycleService.when(LifecyclePhase.Restored); + const disposable = this._register(runWhenIdle(async () => { + await this.doInitialize(); + disposable.dispose(); + })); + } + + private async doInitialize(): Promise { + const providerIds = this.authenticationService.getProviderIds(); + const results = await Promise.allSettled(providerIds.map(providerId => this.addAccountsFromProvider(providerId))); + + // Log any errors that occurred while initializing. We try to be best effort here to show the most amount of accounts + for (const result of results) { + if (result.status === 'rejected') { + this.logService.error(result.reason); + } } - if (this.globalActivityActionBar) { - if (this.accountsActivityAction) { - this.globalActivityActionBar.pull(GlobalCompositeBar.ACCOUNTS_ACTION_INDEX); - this.accountsActivityAction = undefined; - } else { - this.accountsActivityAction = this._register(new ActivityAction({ - id: 'workbench.actions.accounts', - name: localize('accounts', "Accounts"), - classNames: ThemeIcon.asClassNameArray(Codicon.account) + + this.initialized = true; + } + + //#region overrides + + protected override async resolveMainMenuActions(accountsMenu: IMenu, disposables: DisposableStore): Promise { + await super.resolveMainMenuActions(accountsMenu, disposables); + + const providers = this.authenticationService.getProviderIds(); + const otherCommands = accountsMenu.getActions(); + let menus: IAction[] = []; + + for (const providerId of providers) { + if (!this.initialized) { + const noAccountsAvailableAction = disposables.add(new Action('noAccountsAvailable', localize('loading', "Loading..."), undefined, false)); + menus.push(noAccountsAvailableAction); + break; + } + const providerLabel = this.authenticationService.getLabel(providerId); + const accounts = this.groupedAccounts.get(providerId); + if (!accounts) { + if (this.problematicProviders.has(providerId)) { + const providerUnavailableAction = disposables.add(new Action('providerUnavailable', localize('authProviderUnavailable', '{0} is currently unavailable', providerLabel), undefined, false)); + menus.push(providerUnavailableAction); + // try again in the background so that if the failure was intermittent, we can resolve it on the next showing of the menu + try { + await this.addAccountsFromProvider(providerId); + } catch (e) { + this.logService.error(e); + } + } + continue; + } + + for (const account of accounts) { + const manageExtensionsAction = disposables.add(new Action(`configureSessions${account.label}`, localize('manageTrustedExtensions', "Manage Trusted Extensions"), undefined, true, () => { + return this.authenticationService.manageTrustedExtensionsForAccount(providerId, account.label); })); - this.globalActivityActionBar.push(this.accountsActivityAction, { index: GlobalCompositeBar.ACCOUNTS_ACTION_INDEX }); + + const providerSubMenuActions: Action[] = [manageExtensionsAction]; + + if (account.canSignOut) { + const signOutAction = disposables.add(new Action('signOut', localize('signOut', "Sign Out"), undefined, true, async () => { + const allSessions = await this.authenticationService.getSessions(providerId); + const sessionsForAccount = allSessions.filter(s => s.account.label === account.label); + return await this.authenticationService.removeAccountSessions(providerId, account.label, sessionsForAccount); + })); + providerSubMenuActions.push(signOutAction); + } + + const providerSubMenu = new SubmenuAction('activitybar.submenu', `${account.label} (${providerLabel})`, providerSubMenuActions); + menus.push(providerSubMenu); } } - this.updateGlobalActivity(ACCOUNTS_ACTIVITY_ID); + if (providers.length && !menus.length) { + const noAccountsAvailableAction = disposables.add(new Action('noAccountsAvailable', localize('noAccounts', "You are not signed in to any accounts"), undefined, false)); + menus.push(noAccountsAvailableAction); + } + + if (menus.length && otherCommands.length) { + menus.push(new Separator()); + } + + otherCommands.forEach((group, i) => { + const actions = group[1]; + menus = menus.concat(actions); + if (i !== otherCommands.length - 1) { + menus.push(new Separator()); + } + }); + + return menus; } - private get accountsVisibilityPreference(): boolean { - return this.storageService.getBoolean(AccountsActivityActionViewItem.ACCOUNTS_VISIBILITY_PREFERENCE_KEY, StorageScope.PROFILE, true); + protected override async resolveContextMenuActions(disposables: DisposableStore): Promise { + const actions = await super.resolveContextMenuActions(disposables); + this.fillContextMenuActions(actions); + return actions; } - private set accountsVisibilityPreference(value: boolean) { - this.storageService.store(AccountsActivityActionViewItem.ACCOUNTS_VISIBILITY_PREFERENCE_KEY, value, StorageScope.PROFILE, StorageTarget.USER); + //#endregion + + //#region groupedAccounts helpers + + private async addOrUpdateAccount(providerId: string, account: AuthenticationSessionAccount): Promise { + let accounts = this.groupedAccounts.get(providerId); + if (!accounts) { + accounts = []; + this.groupedAccounts.set(providerId, accounts); + } + + const sessionFromEmbedder = await this.sessionFromEmbedder.value; + let canSignOut = true; + if ( + sessionFromEmbedder // if we have a session from the embedder + && !sessionFromEmbedder.canSignOut // and that session says we can't sign out + && (await this.authenticationService.getSessions(providerId)) // and that session is associated with the account we are adding/updating + .some(s => + s.id === sessionFromEmbedder.id + && s.account.id === account.id + ) + ) { + canSignOut = false; + } + + const existingAccount = accounts.find(a => a.label === account.label); + if (existingAccount) { + // if we have an existing account and we discover that we + // can't sign out of it, update the account to mark it as "can't sign out" + if (!canSignOut) { + existingAccount.canSignOut = canSignOut; + } + } else { + accounts.push({ ...account, canSignOut }); + } + } + + private removeAccount(providerId: string, account: AuthenticationSessionAccount): void { + const accounts = this.groupedAccounts.get(providerId); + if (!accounts) { + return; + } + + const index = accounts.findIndex(a => a.id === account.id); + if (index === -1) { + return; + } + + accounts.splice(index, 1); + if (accounts.length === 0) { + this.groupedAccounts.delete(providerId); + } + } + + private async addAccountsFromProvider(providerId: string): Promise { + try { + const sessions = await this.authenticationService.getSessions(providerId); + this.problematicProviders.delete(providerId); + + for (const session of sessions) { + try { + await this.addOrUpdateAccount(providerId, session.account); + } catch (e) { + this.logService.error(e); + } + } + } catch (e) { + this.logService.error(e); + this.problematicProviders.add(providerId); + } + } + + //#endregion +} + +export class GlobalActivityActionViewItem extends AbstractGlobalActivityActionViewItem { + + private profileBadge: HTMLElement | undefined; + private profileBadgeContent: HTMLElement | undefined; + + constructor( + contextMenuActionsProvider: () => IAction[], + options: ICompositeBarActionViewItemOptions, + anchorAlignment: AnchorAlignment | undefined, + anchorAxisAlignment: AnchorAxisAlignment | undefined, + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, + @IThemeService themeService: IThemeService, + @IHoverService hoverService: IHoverService, + @IMenuService menuService: IMenuService, + @IContextMenuService contextMenuService: IContextMenuService, + @IContextKeyService contextKeyService: IContextKeyService, + @IConfigurationService configurationService: IConfigurationService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IKeybindingService keybindingService: IKeybindingService, + @IInstantiationService instantiationService: IInstantiationService, + @IActivityService activityService: IActivityService, + ) { + const action = instantiationService.createInstance(CompositeBarAction, { + id: GLOBAL_ACTIVITY_ID, + name: localize('manage', "Manage"), + classNames: ThemeIcon.asClassNameArray(userDataProfileService.currentProfile.icon ? ThemeIcon.fromId(userDataProfileService.currentProfile.icon) : DEFAULT_ICON) + }); + super(MenuId.GlobalActivity, action, options, contextMenuActionsProvider, anchorAlignment, anchorAxisAlignment, themeService, hoverService, menuService, contextMenuService, contextKeyService, configurationService, keybindingService, activityService); + this._register(action); + this._register(this.userDataProfileService.onDidChangeCurrentProfile(e => { + action.compositeBarActionItem = { + ...action.compositeBarActionItem, + classNames: ThemeIcon.asClassNameArray(userDataProfileService.currentProfile.icon ? ThemeIcon.fromId(userDataProfileService.currentProfile.icon) : DEFAULT_ICON) + }; + })); + } + + override render(container: HTMLElement): void { + super.render(container); + + this.profileBadge = append(container, $('.profile-badge')); + this.profileBadgeContent = append(this.profileBadge, $('.profile-badge-content')); + this.updateProfileBadge(); + } + + private updateProfileBadge(): void { + if (!this.profileBadge || !this.profileBadgeContent) { + return; + } + + clearNode(this.profileBadgeContent); + hide(this.profileBadge); + + if (this.userDataProfileService.currentProfile.isDefault) { + return; + } + + if ((this.action as CompositeBarAction).activity) { + return; + } + + if (!this.userDataProfileService.currentProfile.icon || this.userDataProfileService.currentProfile.icon === DEFAULT_ICON.id) { + this.profileBadgeContent.classList.toggle('profile-text-overlay', true); + this.profileBadgeContent.classList.toggle('profile-icon-overlay', false); + this.profileBadgeContent.textContent = this.userDataProfileService.currentProfile.name.substring(0, 2).toUpperCase(); + } + + show(this.profileBadge); + } + + protected override updateActivity(): void { + super.updateActivity(); + this.updateProfileBadge(); + } + + protected override computeTitle(): string { + return this.userDataProfileService.currentProfile.isDefault ? super.computeTitle() : localize('manage profile', "Manage {0} (Profile)", this.userDataProfileService.currentProfile.name); + } +} + +export class SimpleAccountActivityActionViewItem extends AccountsActivityActionViewItem { + + constructor( + hoverOptions: IActivityHoverOptions, + @IThemeService themeService: IThemeService, + @ILifecycleService lifecycleService: ILifecycleService, + @IHoverService hoverService: IHoverService, + @IContextMenuService contextMenuService: IContextMenuService, + @IMenuService menuService: IMenuService, + @IContextKeyService contextKeyService: IContextKeyService, + @IAuthenticationService authenticationService: IAuthenticationService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IProductService productService: IProductService, + @IConfigurationService configurationService: IConfigurationService, + @IKeybindingService keybindingService: IKeybindingService, + @ISecretStorageService secretStorageService: ISecretStorageService, + @ILogService logService: ILogService, + @IActivityService activityService: IActivityService, + @IInstantiationService instantiationService: IInstantiationService + ) { + super(() => [], { + colors: theme => ({ + badgeBackground: theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND), + badgeForeground: theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND), + }), + hoverOptions, + compact: true, + }, undefined, undefined, actions => actions, themeService, lifecycleService, hoverService, contextMenuService, menuService, contextKeyService, authenticationService, environmentService, productService, configurationService, keybindingService, secretStorageService, logService, activityService, instantiationService); + } +} + +export class SimpleGlobalActivityActionViewItem extends GlobalActivityActionViewItem { + + constructor( + hoverOptions: IActivityHoverOptions, + @IUserDataProfileService userDataProfileService: IUserDataProfileService, + @IThemeService themeService: IThemeService, + @IHoverService hoverService: IHoverService, + @IMenuService menuService: IMenuService, + @IContextMenuService contextMenuService: IContextMenuService, + @IContextKeyService contextKeyService: IContextKeyService, + @IConfigurationService configurationService: IConfigurationService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IKeybindingService keybindingService: IKeybindingService, + @IInstantiationService instantiationService: IInstantiationService, + @IActivityService activityService: IActivityService, + ) { + super(() => [], { + colors: theme => ({ + badgeBackground: theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND), + badgeForeground: theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND), + }), + hoverOptions, + compact: true, + }, undefined, undefined, userDataProfileService, themeService, hoverService, menuService, contextMenuService, contextKeyService, configurationService, environmentService, keybindingService, instantiationService, activityService); } } diff --git a/code/src/vs/workbench/browser/parts/media/paneCompositePart.css b/code/src/vs/workbench/browser/parts/media/paneCompositePart.css index af676e88c63..ee9abd7b981 100644 --- a/code/src/vs/workbench/browser/parts/media/paneCompositePart.css +++ b/code/src/vs/workbench/browser/parts/media/paneCompositePart.css @@ -3,20 +3,28 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench .pane-composite-part > .title.composite-bar-container > .title-actions .monaco-action-bar .actions-container { +.monaco-workbench .pane-composite-part > .title.has-composite-bar > .title-actions .monaco-action-bar .actions-container { justify-content: flex-end; } -.monaco-workbench .pane-composite-part > .title.composite-bar-container > .title-actions .monaco-action-bar .action-item, -.monaco-workbench .pane-composite-part > .title.composite-bar-container > .global-actions .monaco-action-bar .action-item { +.monaco-workbench .pane-composite-part > .title.has-composite-bar > .title-actions .monaco-action-bar .action-item, +.monaco-workbench .pane-composite-part > .title.has-composite-bar > .global-actions .monaco-action-bar .action-item { margin-right: 4px; } -.monaco-workbench .pane-composite-part > .title.composite-bar-container > .title-actions .monaco-action-bar .action-item .action-label { +.monaco-workbench .pane-composite-part > .title.has-composite-bar > .title-actions .monaco-action-bar .action-item .action-label { outline-offset: -2px; } -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-label.codicon-more { +.monaco-workbench .pane-composite-part > .title.has-composite-bar > .title-label { + display: none; +} + +.monaco-workbench .pane-composite-part > .title > .composite-bar-container { + display: flex; +} + +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-label.codicon-more { display: flex; align-items: center; justify-content: center; @@ -25,12 +33,12 @@ color: inherit !important; } -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar { line-height: 27px; /* matches panel titles in settings */ height: 35px; } -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item { text-transform: uppercase; padding-left: 10px; padding-right: 10px; @@ -40,18 +48,22 @@ display: flex; } -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item.icon { - padding-left: 2px; - padding-right: 2px; +.monaco-workbench .pane-composite-part > .title > .composite-bar-container >.composite-bar > .monaco-action-bar .action-item.icon { + height: 24px; + padding: 0 5px; +} + +.monaco-workbench .pane-composite-part > .title > .composite-bar-container >.composite-bar .monaco-action-bar .action-label.codicon { + font-size: 18px; } -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item.icon .action-label:not(.codicon) { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .action-label:not(.codicon) { width: 16px; height: 16px; } -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item::before, -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item::after { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::before, +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::after { content: ''; width: 2px; height: 24px; @@ -64,75 +76,75 @@ transition-delay: 100ms; } -.monaco-workbench .pane-composite-part > .title.dragged-over > .pane-composite-bar > .monaco-action-bar .action-item::before, -.monaco-workbench .pane-composite-part > .title.dragged-over > .pane-composite-bar > .monaco-action-bar .action-item::after { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container.dragged-over > .composite-bar > .monaco-action-bar .action-item::before, +.monaco-workbench .pane-composite-part > .title > .composite-bar-container.dragged-over > .composite-bar > .monaco-action-bar .action-item::after { display: block; } -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item::before { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::before { left: 1px; margin-left: -2px; } -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item::after { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item::after { right: 1px; margin-right: -2px; } -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item:first-of-type::before { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:first-of-type::before { left: 2px; margin-left: -2px; } -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item:last-of-type::after { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:last-of-type::after { right: 2px; margin-right: -2px; } -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item.right::before, -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item.left::after, -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item.left::before, -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item.right::after { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.right::before, +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.left::after, +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.left::before, +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.right::after { transition-delay: 0s; } -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item.right + .action-item::before, -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item.left::before, -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item:last-of-type.right::after, -.monaco-workbench .pane-composite-part > .title.dragged-over-head > .pane-composite-bar > .monaco-action-bar .action-item:first-of-type::before, -.monaco-workbench .pane-composite-part > .title.dragged-over-tail > .pane-composite-bar > .monaco-action-bar .action-item:last-of-type::after { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.right + .action-item::before, +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.left::before, +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:last-of-type.right::after, +.monaco-workbench .pane-composite-part > .title > .composite-bar-container.dragged-over-head > .composite-bar > .monaco-action-bar .action-item:first-of-type::before, +.monaco-workbench .pane-composite-part > .title > .composite-bar-container.dragged-over-tail > .composite-bar > .monaco-action-bar .action-item:last-of-type::after { opacity: 1; } -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item .action-label { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .action-label { margin-right: 0; padding: 2px; } -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item .action-label { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .action-label { border-radius: 0; } -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item:not(.icon) .action-label, -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item.icon .action-label.codicon { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.icon) .action-label, +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .action-label.codicon { background: none !important; } -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item.checked .action-label { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label { margin-right: 0; } -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .badge { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .badge { margin-left: 8px; display: flex; align-items: center; } -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item.icon .badge { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge { margin-left: 0px; } -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .badge .badge-content { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .badge .badge-content { padding: 3px 5px; border-radius: 11px; font-size: 11px; @@ -146,8 +158,39 @@ position: relative; } +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact { + position: absolute; + top: 0; + bottom: 0; + margin: auto; + left: 0; + overflow: hidden; + width: 100%; + height: 100%; + z-index: 2; +} + +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact .badge-content { + position: absolute; + top: 13px; + right: 2px; + font-size: 9px; + font-weight: 600; + min-width: 10px; + height: 10px; + padding: 0 4px; + border-radius: 16px; + text-align: center; +} + +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact.progress-badge .badge-content::before { + mask-size: 10px; + -webkit-mask-size: 10px; + top: 4px; +} + /* active item indicator */ -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item .active-item-indicator { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .active-item-indicator { position: absolute; z-index: 1; bottom: 0; @@ -156,20 +199,20 @@ height: 100%; } -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item .active-item-indicator { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item .active-item-indicator { top: -4px; left: 10px; width: calc(100% - 20px); } -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item.icon .active-item-indicator { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .active-item-indicator { top: 1px; left: 2px; width: calc(100% - 4px); } -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item.checked .active-item-indicator:before, - .monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item:focus .active-item-indicator:before { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .active-item-indicator:before, +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .active-item-indicator:before { content: ""; position: absolute; z-index: 1; @@ -180,24 +223,24 @@ border-top-style: solid; } -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item.clicked:not(.checked):focus .active-item-indicator:before { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.clicked:not(.checked):focus .active-item-indicator:before { border-top-color: transparent !important; /* hides border on clicked state */ } -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item:focus .active-item-indicator:before { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:focus .active-item-indicator:before { border-top-color: var(--vscode-focusBorder) !important; } -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item.checked:not(:focus) .active-item-indicator:before, -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item.checked.clicked:focus .active-item-indicator:before { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked:not(:focus) .active-item-indicator:before, +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked.clicked:focus .active-item-indicator:before { border-top-color: var(--vscode-panelTitle-activeBorder) !important; } -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item.checked .action-label, -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item:hover .action-label { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.checked .action-label, +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:hover .action-label { outline: var(--vscode-contrastActiveBorder, unset) solid 1px !important; } -.monaco-workbench .pane-composite-part > .title > .pane-composite-bar > .monaco-action-bar .action-item:not(.checked):hover .action-label { +.monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item:not(.checked):hover .action-label { outline: var(--vscode-contrastActiveBorder, unset) dashed 1px !important; } diff --git a/code/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts b/code/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts index ddc6358bfa2..21a7891569b 100644 --- a/code/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts +++ b/code/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts @@ -72,7 +72,7 @@ export function getNotificationFromContext(listService: IListService, context?: if (!isNotificationViewItem(element)) { if (list.isDOMFocused()) { // the notification list might have received focus - // via keyboard and might not have a focussed element. + // via keyboard and might not have a focused element. // in that case just return the first element // https://github.com/microsoft/vscode/issues/191705 element = list.element(0); diff --git a/code/src/vs/workbench/browser/parts/paneCompositeBar.ts b/code/src/vs/workbench/browser/parts/paneCompositeBar.ts index f8e71ea9a67..aa080683cc6 100644 --- a/code/src/vs/workbench/browser/parts/paneCompositeBar.ts +++ b/code/src/vs/workbench/browser/parts/paneCompositeBar.ts @@ -5,8 +5,7 @@ import { localize } from 'vs/nls'; import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IActivity } from 'vs/workbench/common/activity'; -import { IBadge } from 'vs/workbench/services/activity/common/activity'; +import { IActivityService } from 'vs/workbench/services/activity/common/activity'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable, DisposableStore, Disposable, DisposableMap } from 'vs/base/common/lifecycle'; @@ -16,7 +15,7 @@ import { Dimension, createCSSRule, asCSSUrl, isMouseEvent } from 'vs/base/browse import { IStorageService, StorageScope, StorageTarget, IProfileStorageValueChangeEvent } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { ToggleCompositePinnedAction, ICompositeBarColors, IActivityHoverOptions, ToggleCompositeBadgeAction, ActivityAction, ICompositeBar } from 'vs/workbench/browser/parts/compositeBarActions'; +import { ToggleCompositePinnedAction, ICompositeBarColors, IActivityHoverOptions, ToggleCompositeBadgeAction, CompositeBarAction, ICompositeBar, ICompositeBarActionItem } from 'vs/workbench/browser/parts/compositeBarActions'; import { IViewDescriptorService, ViewContainer, IViewContainerModel, ViewContainerLocation } from 'vs/workbench/common/views'; import { getEnabledViewContainerContextKey } from 'vs/workbench/common/contextkeys'; import { IContextKeyService, ContextKeyExpr, IContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -31,6 +30,7 @@ import { GestureEvent } from 'vs/base/browser/touch'; import { IPaneCompositePart } from 'vs/workbench/browser/parts/paneCompositePart'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IExtensionBisectService } from 'vs/workbench/services/extensionManagement/browser/extensionBisect'; interface IPlaceholderViewContainer { readonly id: string; @@ -39,13 +39,14 @@ interface IPlaceholderViewContainer { readonly themeIcon?: ThemeIcon; readonly isBuiltin?: boolean; readonly views?: { when?: string }[]; + readonly visible?: boolean; } interface IPinnedViewContainer { readonly id: string; readonly pinned: boolean; readonly order?: number; - // TODO @sandy081: Remove this after a while. This is not used and is only here to be compatible with old cached state. + // TODO @sandy081: Remove this after a while. Migrated to visible in IPlaceholderViewContainer readonly visible: boolean; } @@ -55,7 +56,6 @@ interface ICachedViewContainer { icon?: URI | ThemeIcon; readonly pinned: boolean; readonly order?: number; - // TODO @sandy081: Remove this after a while. This is not used and is only here to be compatible with old cached state. visible: boolean; isBuiltin?: boolean; views?: { when?: string }[]; @@ -66,6 +66,7 @@ export interface IPaneCompositeBarOptions { readonly pinnedViewContainersKey: string; readonly placeholderViewContainersKey: string; readonly icon: boolean; + readonly compact?: boolean; readonly iconSize: number; readonly recomputeSizes: boolean; readonly orientation: ActionsOrientation; @@ -90,14 +91,15 @@ export class PaneCompositeBar extends Disposable { private hasExtensionsRegistered: boolean = false; constructor( - private readonly options: IPaneCompositeBarOptions, + protected readonly options: IPaneCompositeBarOptions, private readonly part: Parts, private readonly paneCompositePart: IPaneCompositePart, - @IInstantiationService private readonly instantiationService: IInstantiationService, + @IInstantiationService protected readonly instantiationService: IInstantiationService, @IStorageService private readonly storageService: IStorageService, @IExtensionService private readonly extensionService: IExtensionService, + @IExtensionBisectService private readonly extensionBisectService: IExtensionBisectService, @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IContextKeyService protected readonly contextKeyService: IContextKeyService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, ) { super(); @@ -127,6 +129,7 @@ export class PaneCompositeBar extends Disposable { private createCompositeBar(cachedItems: ICompositeBarItem[]) { return this._register(this.instantiationService.createInstance(CompositeBar, cachedItems, { icon: this.options.icon, + compact: this.options.compact, orientation: this.options.orientation, activityHoverOptions: this.options.activityHoverOptions, preventLoopNavigation: this.options.preventLoopNavigation, @@ -153,14 +156,14 @@ export class PaneCompositeBar extends Disposable { const viewContainer = this.viewDescriptorService.getViewContainerById(compositeId)!; const defaultLocation = this.viewDescriptorService.getDefaultViewContainerLocation(viewContainer)!; if (defaultLocation !== this.viewDescriptorService.getViewContainerLocation(viewContainer)) { - actions.push(toAction({ id: 'resetLocationAction', label: localize('resetLocation', "Reset Location"), run: () => this.viewDescriptorService.moveViewContainerToLocation(viewContainer, defaultLocation) })); + actions.push(toAction({ id: 'resetLocationAction', label: localize('resetLocation', "Reset Location"), run: () => this.viewDescriptorService.moveViewContainerToLocation(viewContainer, defaultLocation, undefined, 'resetLocationAction') })); } else { const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); if (viewContainerModel.allViewDescriptors.length === 1) { const viewToReset = viewContainerModel.allViewDescriptors[0]; const defaultContainer = this.viewDescriptorService.getDefaultContainerById(viewToReset.id)!; if (defaultContainer !== viewContainer) { - actions.push(toAction({ id: 'resetLocationAction', label: localize('resetLocation', "Reset Location"), run: () => this.viewDescriptorService.moveViewsToContainer([viewToReset], defaultContainer) })); + actions.push(toAction({ id: 'resetLocationAction', label: localize('resetLocation', "Reset Location"), run: () => this.viewDescriptorService.moveViewsToContainer([viewToReset], defaultContainer, undefined, 'resetLocationAction') })); } } } @@ -216,12 +219,16 @@ export class PaneCompositeBar extends Disposable { this.hasExtensionsRegistered = true; // show/hide/remove composites + const shouldRemoveNotExsitingComposite = !(this.extensionBisectService.isActive + || this.environmentService.disableExtensions === true + || (Array.isArray(this.environmentService.disableExtensions) && this.environmentService.disableExtensions.length > 0)); + for (const { id } of this.cachedViewContainers) { const viewContainer = this.getViewContainer(id); if (viewContainer) { this.showOrHideViewContainer(viewContainer); } else { - if (this.viewDescriptorService.isViewContainerRemovedPermanently(id)) { + if (shouldRemoveNotExsitingComposite) { this.removeComposite(id); } else { this.hideComposite(id); @@ -250,14 +257,6 @@ export class PaneCompositeBar extends Disposable { } } - showActivity(viewContainerOrActionId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable { - if (this.getViewContainer(viewContainerOrActionId)) { - return this.compositeBar.showActivity(viewContainerOrActionId, badge, clazz, priority); - } - - return Disposable.None; - } - create(parent: HTMLElement): HTMLElement { return this.compositeBar.create(parent); } @@ -269,14 +268,14 @@ export class PaneCompositeBar extends Disposable { if (viewContainer) { const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); compositeActions = { - activityAction: this.instantiationService.createInstance(ViewContainerActivityAction, this.toActivityFrom(viewContainerModel), this.part, this.paneCompositePart), - pinnedAction: new ToggleCompositePinnedAction(this.toActivityFrom(viewContainerModel), this.compositeBar), - badgeAction: new ToggleCompositeBadgeAction(this.toActivityFrom(viewContainerModel), this.compositeBar) + activityAction: this.instantiationService.createInstance(ViewContainerActivityAction, this.toCompositeBarActionItemFrom(viewContainerModel), this.part, this.paneCompositePart), + pinnedAction: new ToggleCompositePinnedAction(this.toCompositeBarActionItemFrom(viewContainerModel), this.compositeBar), + badgeAction: new ToggleCompositeBadgeAction(this.toCompositeBarActionItemFrom(viewContainerModel), this.compositeBar) }; } else { const cachedComposite = this.cachedViewContainers.filter(c => c.id === compositeId)[0]; compositeActions = { - activityAction: this.instantiationService.createInstance(PlaceHolderViewContainerActivityAction, this.toActivity(compositeId, cachedComposite?.name ?? compositeId, cachedComposite?.icon, undefined), this.part, this.paneCompositePart), + activityAction: this.instantiationService.createInstance(PlaceHolderViewContainerActivityAction, this.toCompositeBarActionItem(compositeId, cachedComposite?.name ?? compositeId, cachedComposite?.icon, undefined), this.part, this.paneCompositePart), pinnedAction: new PlaceHolderToggleCompositePinnedAction(compositeId, this.compositeBar), badgeAction: new PlaceHolderToggleCompositeBadgeAction(compositeId, this.compositeBar) }; @@ -305,11 +304,11 @@ export class PaneCompositeBar extends Disposable { } const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); - this.updateActivity(viewContainer, viewContainerModel); + this.updateCompositeBarActionItem(viewContainer, viewContainerModel); this.showOrHideViewContainer(viewContainer); const disposables = new DisposableStore(); - disposables.add(viewContainerModel.onDidChangeContainerInfo(() => this.updateActivity(viewContainer, viewContainerModel))); + disposables.add(viewContainerModel.onDidChangeContainerInfo(() => this.updateCompositeBarActionItem(viewContainer, viewContainerModel))); disposables.add(viewContainerModel.onDidChangeActiveViewDescriptors(() => this.showOrHideViewContainer(viewContainer))); this.viewContainerDisposables.set(viewContainer.id, disposables); @@ -321,13 +320,13 @@ export class PaneCompositeBar extends Disposable { this.removeComposite(viewContainer.id); } - private updateActivity(viewContainer: ViewContainer, viewContainerModel: IViewContainerModel): void { - const activity: IActivity = this.toActivityFrom(viewContainerModel); + private updateCompositeBarActionItem(viewContainer: ViewContainer, viewContainerModel: IViewContainerModel): void { + const compositeBarActionItem = this.toCompositeBarActionItemFrom(viewContainerModel); const { activityAction, pinnedAction } = this.getCompositeActions(viewContainer.id); - activityAction.updateActivity(activity); + activityAction.updateCompositeBarActionItem(compositeBarActionItem); if (pinnedAction instanceof PlaceHolderToggleCompositePinnedAction) { - pinnedAction.setActivity(activity); + pinnedAction.setActivity(compositeBarActionItem); } if (this.options.recomputeSizes) { @@ -337,11 +336,11 @@ export class PaneCompositeBar extends Disposable { this.saveCachedViewContainers(); } - private toActivityFrom(viewContainerModel: IViewContainerModel): IActivity { - return this.toActivity(viewContainerModel.viewContainer.id, viewContainerModel.title, viewContainerModel.icon, viewContainerModel.keybindingId); + private toCompositeBarActionItemFrom(viewContainerModel: IViewContainerModel): ICompositeBarActionItem { + return this.toCompositeBarActionItem(viewContainerModel.viewContainer.id, viewContainerModel.title, viewContainerModel.icon, viewContainerModel.keybindingId); } - private toActivity(id: string, name: string, icon: URI | ThemeIcon | undefined, keybindingId: string | undefined): IActivity { + private toCompositeBarActionItem(id: string, name: string, icon: URI | ThemeIcon | undefined, keybindingId: string | undefined): ICompositeBarActionItem { let classNames: string[] | undefined = undefined; let iconUrl: URI | undefined = undefined; if (this.options.icon) { @@ -403,7 +402,7 @@ export class PaneCompositeBar extends Disposable { cachedViewContainer = cachedViewContainer || this.cachedViewContainers.find(({ id }) => id === viewContainerId); // Show builtin ViewContainer if not registered yet - if (!viewContainer && cachedViewContainer?.isBuiltin) { + if (!viewContainer && cachedViewContainer?.isBuiltin && cachedViewContainer?.visible) { return false; } @@ -478,6 +477,7 @@ export class PaneCompositeBar extends Disposable { private onDidPinnedViewContainersStorageValueChange(e: IProfileStorageValueChangeEvent): void { if (this.pinnedViewContainersValue !== this.getStoredPinnedViewContainersValue() /* This checks if current window changed the value or not */) { + this._placeholderViewContainersValue = undefined; this._pinnedViewContainersValue = undefined; this._cachedViewContainers = undefined; @@ -490,19 +490,20 @@ export class PaneCompositeBar extends Disposable { name: cachedViewContainer.name, order: cachedViewContainer.order, pinned: cachedViewContainer.pinned, - visible: !!compositeItems.find(({ id }) => id === cachedViewContainer.id) + visible: cachedViewContainer.visible, }); } - for (let index = 0; index < compositeItems.length; index++) { - // Add items currently exists but does not exist in new. - if (!newCompositeItems.some(({ id }) => id === compositeItems[index].id)) { - const viewContainer = this.viewDescriptorService.getViewContainerById(compositeItems[index].id); - newCompositeItems.splice(index, 0, { - ...compositeItems[index], - pinned: true, - visible: true, - order: viewContainer?.order, + for (const viewContainer of this.getViewContainers()) { + // Add missing view containers + if (!newCompositeItems.some(({ id }) => id === viewContainer.id)) { + const compositeItem = compositeItems.find(({ id }) => id === viewContainer.id); + newCompositeItems.push({ + id: viewContainer.id, + name: typeof viewContainer.title === 'string' ? viewContainer.title : viewContainer.title.value, + order: viewContainer.order, + pinned: e.external ? true : compositeItem?.pinned ?? true, + visible: e.external ? !this.shouldBeHidden(viewContainer) : compositeItem?.visible ?? true, }); } } @@ -516,7 +517,6 @@ export class PaneCompositeBar extends Disposable { const compositeItems = this.compositeBar.getCompositeBarItems(); for (const compositeItem of compositeItems) { - const cachedViewContainer = this.cachedViewContainers.find(({ id }) => id === compositeItem.id); const viewContainer = this.getViewContainer(compositeItem.id); if (viewContainer) { const viewContainerModel = this.viewDescriptorService.getViewContainerModel(viewContainer); @@ -531,11 +531,11 @@ export class PaneCompositeBar extends Disposable { views, pinned: compositeItem.pinned, order: compositeItem.order, - visible: cachedViewContainer?.visible ?? compositeItem.visible, + visible: compositeItem.visible, isBuiltin: !viewContainer.extensionId }); } else { - state.push({ id: compositeItem.id, name: compositeItem.name, pinned: compositeItem.pinned, order: compositeItem.order, visible: !!cachedViewContainer?.visible, isBuiltin: false }); + state.push({ id: compositeItem.id, name: compositeItem.name, pinned: compositeItem.pinned, order: compositeItem.order, visible: false, isBuiltin: false }); } } @@ -547,8 +547,9 @@ export class PaneCompositeBar extends Disposable { if (this._cachedViewContainers === undefined) { this._cachedViewContainers = this.getPinnedViewContainers(); for (const placeholderViewContainer of this.getPlaceholderViewContainers()) { - const cachedViewContainer = this._cachedViewContainers.filter(cached => cached.id === placeholderViewContainer.id)[0]; + const cachedViewContainer = this._cachedViewContainers.find(cached => cached.id === placeholderViewContainer.id); if (cachedViewContainer) { + cachedViewContainer.visible = placeholderViewContainer.visible ?? cachedViewContainer.visible; cachedViewContainer.name = placeholderViewContainer.name; cachedViewContainer.icon = placeholderViewContainer.themeIcon ? placeholderViewContainer.themeIcon : placeholderViewContainer.iconUrl ? URI.revive(placeholderViewContainer.iconUrl) : undefined; @@ -565,19 +566,21 @@ export class PaneCompositeBar extends Disposable { } private storeCachedViewContainersState(cachedViewContainers: ICachedViewContainer[]): void { - this.setPinnedViewContainers(cachedViewContainers.map(({ id, pinned, visible, order }) => ({ + const pinnedViewContainers = this.getPinnedViewContainers(); + this.setPinnedViewContainers(cachedViewContainers.map(({ id, pinned, order }) => ({ id, pinned, - visible, + visible: pinnedViewContainers.find(({ id: pinnedId }) => pinnedId === id)?.visible, order }))); - this.setPlaceholderViewContainers(cachedViewContainers.map(({ id, icon, name, views, isBuiltin }) => ({ + this.setPlaceholderViewContainers(cachedViewContainers.map(({ id, icon, name, views, visible, isBuiltin }) => ({ id, iconUrl: URI.isUri(icon) ? icon : undefined, themeIcon: ThemeIcon.isThemeIcon(icon) ? icon : undefined, name, isBuiltin, + visible, views }))); } @@ -647,25 +650,37 @@ export class PaneCompositeBar extends Disposable { } } -class ViewContainerActivityAction extends ActivityAction { +class ViewContainerActivityAction extends CompositeBarAction { private static readonly preventDoubleClickDelay = 300; private lastRun = 0; constructor( - activity: IActivity, + compositeBarActionItem: ICompositeBarActionItem, private readonly part: Parts, private readonly paneCompositePart: IPaneCompositePart, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IConfigurationService private readonly configurationService: IConfigurationService + @IConfigurationService private readonly configurationService: IConfigurationService, + @IActivityService private readonly activityService: IActivityService, ) { - super(activity); + super(compositeBarActionItem); + this.updateActivity(); + this._register(this.activityService.onDidChangeActivity(viewContainerOrAction => { + if (!isString(viewContainerOrAction) && viewContainerOrAction.id === this.compositeBarActionItem.id) { + this.updateActivity(); + } + })); } - updateActivity(activity: IActivity): void { - this.activity = activity; + updateCompositeBarActionItem(compositeBarActionItem: ICompositeBarActionItem): void { + this.compositeBarActionItem = compositeBarActionItem; + } + + private updateActivity(): void { + const activities = this.activityService.getViewContainerActivities(this.compositeBarActionItem.id); + this.activity = activities[0]; } override async run(event: { preserveFocus: boolean }): Promise { @@ -687,11 +702,11 @@ class ViewContainerActivityAction extends ActivityAction { const activeViewlet = this.paneCompositePart.getActivePaneComposite(); const focusBehavior = this.configurationService.getValue('workbench.activityBar.iconClickBehavior'); - if (sideBarVisible && activeViewlet?.getId() === this.activity.id) { + if (sideBarVisible && activeViewlet?.getId() === this.compositeBarActionItem.id) { switch (focusBehavior) { case 'focus': this.logAction('refocus'); - this.paneCompositePart.openPaneComposite(this.activity.id, focus); + this.paneCompositePart.openPaneComposite(this.compositeBarActionItem.id, focus); break; case 'toggle': default: @@ -707,7 +722,7 @@ class ViewContainerActivityAction extends ActivityAction { this.logAction('show'); } - await this.paneCompositePart.openPaneComposite(this.activity.id, focus); + await this.paneCompositePart.openPaneComposite(this.compositeBarActionItem.id, focus); return this.activate(); } @@ -718,7 +733,7 @@ class ViewContainerActivityAction extends ActivityAction { viewletId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The view in the activity bar for which the action was performed.' }; action: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The action that was performed. e.g. "hide", "show", or "refocus"' }; }; - this.telemetryService.publicLog2<{ viewletId: String; action: String }, ActivityBarActionClassification>('activityBarAction', { viewletId: this.activity.id, action }); + this.telemetryService.publicLog2<{ viewletId: String; action: String }, ActivityBarActionClassification>('activityBarAction', { viewletId: this.compositeBarActionItem.id, action }); } } @@ -730,7 +745,7 @@ class PlaceHolderToggleCompositePinnedAction extends ToggleCompositePinnedAction super({ id, name: id, classNames: undefined }, compositeBar); } - setActivity(activity: IActivity): void { + setActivity(activity: ICompositeBarActionItem): void { this.label = activity.name; } } @@ -741,7 +756,7 @@ class PlaceHolderToggleCompositeBadgeAction extends ToggleCompositeBadgeAction { super({ id, name: id, classNames: undefined }, compositeBar); } - setActivity(activity: IActivity): void { - this.label = activity.name; + setCompositeBarActionItem(actionItem: ICompositeBarActionItem): void { + this.label = actionItem.name; } } diff --git a/code/src/vs/workbench/browser/parts/paneCompositePart.ts b/code/src/vs/workbench/browser/parts/paneCompositePart.ts index 467a9f89306..35666e0dac7 100644 --- a/code/src/vs/workbench/browser/parts/paneCompositePart.ts +++ b/code/src/vs/workbench/browser/parts/paneCompositePart.ts @@ -8,16 +8,14 @@ import { Event } from 'vs/base/common/event'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IProgressIndicator } from 'vs/platform/progress/common/progress'; import { Extensions, PaneComposite, PaneCompositeDescriptor, PaneCompositeRegistry } from 'vs/workbench/browser/panecomposite'; -// import { PanelPart } from 'vs/workbench/browser/parts/panel/panelPart'; import { IPaneComposite } from 'vs/workbench/common/panecomposite'; import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; -import { IBadge } from 'vs/workbench/services/activity/common/activity'; -import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { MutableDisposable } from 'vs/base/common/lifecycle'; import { IView } from 'vs/base/browser/ui/grid/grid'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { CompositePart, ICompositeTitleLabel } from 'vs/workbench/browser/parts/compositePart'; import { IPaneCompositeBarOptions, PaneCompositeBar } from 'vs/workbench/browser/parts/paneCompositeBar'; -import { Dimension, EventHelper, clearNode, trackFocus, $, addDisposableListener, EventType } from 'vs/base/browser/dom'; +import { Dimension, EventHelper, trackFocus, $, addDisposableListener, EventType, prepend } from 'vs/base/browser/dom'; import { Registry } from 'vs/platform/registry/common/platform'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -33,10 +31,13 @@ import { EDITOR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme'; import { IPartOptions } from 'vs/workbench/browser/part'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { CompositeMenuActions } from 'vs/workbench/browser/actions'; -import { MenuId } from 'vs/platform/actions/common/actions'; +import { MenuId, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { ActionsOrientation, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; import { Gesture, EventType as GestureEventType } from 'vs/base/browser/touch'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; +import { IAction, SubmenuAction } from 'vs/base/common/actions'; +import { Composite } from 'vs/workbench/browser/composite'; +import { ViewsSubMenu } from 'vs/workbench/browser/parts/views/viewPaneContainer'; export interface IPaneCompositePart extends IView { @@ -89,11 +90,6 @@ export interface IPaneCompositePart extends IView { * Returns id of visible view containers following the visual order. */ getVisiblePaneCompositeIds(): string[]; - - /** - * Show activity on the view pane - */ - showActivity(id: string, badge: IBadge, clazz?: string, priority?: number): IDisposable; } export abstract class AbstractPaneCompositePart extends CompositePart implements IPaneCompositePart { @@ -110,9 +106,8 @@ export abstract class AbstractPaneCompositePart extends CompositePart; private readonly location: ViewContainerLocation; - private titleDisposables = this._register(new DisposableStore()); private titleContainer: HTMLElement | undefined; - private paneTitleLabel: ICompositeTitleLabel | undefined; + private paneCompositeBarContainer: HTMLElement | undefined; private paneCompositeBar = this._register(new MutableDisposable()); private emptyPaneMessageElement: HTMLElement | undefined; @@ -200,11 +195,14 @@ export abstract class AbstractPaneCompositePart extends CompositePart { + this.layoutCompositeBar(); + })); } private onDidOpen(composite: IComposite): void { this.activePaneContextKey.set(composite.getId()); - this.layoutEmptyMessage(); } private onDidClose(composite: IComposite): void { @@ -212,9 +210,21 @@ export abstract class AbstractPaneCompositePart extends CompositePart { - if (!this.updateTitleArea() && this.paneTitleLabel) { - this.paneTitleLabel.updateTitle(id, title, keybinding); - } - }, - updateStyles: () => this.paneTitleLabel?.updateStyles() + + const titleLabel = super.createTitleLabel(parent); + this.titleLabelElement!.draggable = true; + const draggedItemProvider = (): { type: 'view' | 'composite'; id: string } => { + const activeViewlet = this.getActivePaneComposite()!; + return { type: 'composite', id: activeViewlet.getId() }; }; + this._register(CompositeDragAndDropObserver.INSTANCE.registerDraggable(this.titleLabelElement!, draggedItemProvider, {})); + + this.updateTitleArea(); + return titleLabel; } - protected updateTitleArea(): boolean { + protected updateTitleArea(): void { if (!this.titleContainer) { - return false; - } - if (!this.paneCompositeBar.value && this.shouldShowCompositeBar()) { - this.titleContainer.classList.add('composite-bar-container'); - this.titleDisposables.clear(); - this.titleLabelElement = undefined; - clearNode(this.titleContainer); - this.paneCompositeBar.value = this.createCompisteBar(); - const titleArea = this.paneCompositeBar.value.create(this.titleContainer); - titleArea.classList.add('pane-composite-bar'); - return true; + return; } - if (!this.titleLabelElement && !this.shouldShowCompositeBar()) { + if (this.shouldShowCompositeBar()) { + if (!this.paneCompositeBar.value) { + this.titleContainer.classList.add('has-composite-bar'); + this.paneCompositeBarContainer = prepend(this.titleContainer, $('.composite-bar-container')); + this.paneCompositeBar.value = this.createCompisteBar(); + this.paneCompositeBar.value.create(this.paneCompositeBarContainer); + } + } else { + this.titleContainer.classList.remove('has-composite-bar'); + this.paneCompositeBarContainer?.remove(); + this.paneCompositeBarContainer = undefined; this.paneCompositeBar.clear(); - this.titleDisposables.clear(); - clearNode(this.titleContainer); - this.titleContainer.classList.remove('pane-composite-bar-container'); - this.paneTitleLabel = super.createTitleLabel(this.titleContainer); - this.titleLabelElement!.draggable = true; - const draggedItemProvider = (): { type: 'view' | 'composite'; id: string } => { - const activeViewlet = this.getActivePaneComposite()!; - return { type: 'composite', id: activeViewlet.getId() }; - }; - this.titleDisposables.add(CompositeDragAndDropObserver.INSTANCE.registerDraggable(this.titleLabelElement!, draggedItemProvider, {})); - return false; } - return false; } protected createCompisteBar(): PaneCompositeBar { - return this.instantiationService.createInstance(PaneCompositeBar, this.getCompoisteBarOptions(), this.partId, this); + return this.instantiationService.createInstance(PaneCompositeBar, this.getCompositeBarOptions(), this.partId, this); } protected override onTitleAreaUpdate(compositeId: string): void { @@ -388,10 +388,6 @@ export abstract class AbstractPaneCompositePart extends CompositePart event, - getActions: () => contextMenuActions.slice(), + getActions: () => actions, + skipTelemetry: true + }); + } + } else { + const activePaneComposite = this.getActivePaneComposite() as PaneComposite; + const activePaneCompositeActions = activePaneComposite ? activePaneComposite.getContextMenuActions() : []; + if (activePaneCompositeActions.length) { + this.contextMenuService.showContextMenu({ + getAnchor: () => event, + getActions: () => activePaneCompositeActions, getActionViewItem: action => this.actionViewItemProvider(action), - actionRunner: activeViewlet.getActionRunner(), + actionRunner: activePaneComposite.getActionRunner(), skipTelemetry: true }); } } } + protected getViewsSubmenuAction(): SubmenuAction | undefined { + const viewsActions: IAction[] = []; + const activePaneComposite = this.getActivePaneComposite() as PaneComposite; + const activePaneCompositeActions = activePaneComposite ? activePaneComposite.getSecondaryActions() : []; + const viewsSubmenuAction = activePaneCompositeActions.find(action => action instanceof SubmenuItemAction && action.item.submenu === ViewsSubMenu) as SubmenuItemAction | undefined; + if (viewsSubmenuAction) { + viewsActions.push(...viewsSubmenuAction.actions); + } else { + viewsActions.push(...activePaneCompositeActions); + } + return viewsActions.length > 1 ? new SubmenuAction('views', localize('views', "Views"), viewsActions) : undefined; + } + protected abstract shouldShowCompositeBar(): boolean; - protected abstract getCompoisteBarOptions(): IPaneCompositeBarOptions; + protected abstract getCompositeBarOptions(): IPaneCompositeBarOptions; } diff --git a/code/src/vs/workbench/browser/parts/paneCompositePartService.ts b/code/src/vs/workbench/browser/parts/paneCompositePartService.ts index a07b30bd61c..fe8930252ef 100644 --- a/code/src/vs/workbench/browser/parts/paneCompositePartService.ts +++ b/code/src/vs/workbench/browser/parts/paneCompositePartService.ts @@ -14,9 +14,8 @@ import { PanelPart } from 'vs/workbench/browser/parts/panel/panelPart'; import { SidebarPart } from 'vs/workbench/browser/parts/sidebar/sidebarPart'; import { IPaneComposite } from 'vs/workbench/common/panecomposite'; import { ViewContainerLocation, ViewContainerLocations } from 'vs/workbench/common/views'; -import { IBadge } from 'vs/workbench/services/activity/common/activity'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; -import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IPaneCompositePart } from 'vs/workbench/browser/parts/paneCompositePart'; export class PaneCompositePartService extends Disposable implements IPaneCompositePartService { @@ -82,10 +81,6 @@ export class PaneCompositePartService extends Disposable implements IPaneComposi return this.getPartByLocation(viewContainerLocation).getLastActivePaneCompositeId(); } - showActivity(id: string, viewContainerLocation: ViewContainerLocation, badge: IBadge, clazz?: string, priority?: number): IDisposable { - return this.getPartByLocation(viewContainerLocation).showActivity(id, badge, clazz, priority); - } - private getPartByLocation(viewContainerLocation: ViewContainerLocation): IPaneCompositePart { return assertIsDefined(this.paneCompositeParts.get(viewContainerLocation)); } diff --git a/code/src/vs/workbench/browser/parts/panel/media/panelpart.css b/code/src/vs/workbench/browser/parts/panel/media/panelpart.css index 60df191356d..0cb8bea4fa7 100644 --- a/code/src/vs/workbench/browser/parts/panel/media/panelpart.css +++ b/code/src/vs/workbench/browser/parts/panel/media/panelpart.css @@ -41,8 +41,8 @@ background-color: var(--vscode-panel-background); } -.monaco-workbench .part.panel > .title > .pane-composite-bar > .monaco-action-bar .action-item:focus .action-label, -.monaco-workbench .part.panel > .title > .pane-composite-bar > .monaco-action-bar .action-item:hover .action-label { +.monaco-workbench .part.panel > .title > .composite-bar-container >.composite-bar > .monaco-action-bar .action-item:focus .action-label, +.monaco-workbench .part.panel > .title > .composite-bar-container >.composite-bar > .monaco-action-bar .action-item:hover .action-label { color: var(--vscode-panelTitle-activeForeground) !important; } @@ -50,6 +50,6 @@ border-color: var(--vscode-panelInput-border, transparent) !important; } -.monaco-workbench .part.panel > .title > .pane-composite-bar > .monaco-action-bar .action-item:focus { +.monaco-workbench .part.panel > .title > .composite-bar-container >.composite-bar > .monaco-action-bar .action-item:focus { outline: none; } diff --git a/code/src/vs/workbench/browser/parts/panel/panelActions.ts b/code/src/vs/workbench/browser/parts/panel/panelActions.ts index bc580fd7fe6..e407f2b8592 100644 --- a/code/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/code/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -407,7 +407,7 @@ class MoveViewsBetweenPanelsAction extends Action2 { if (srcContainers.length) { const activeViewContainer = viewsService.getVisibleViewContainer(this.source); - srcContainers.forEach(viewContainer => viewDescriptorService.moveViewContainerToLocation(viewContainer, this.destination)); + srcContainers.forEach(viewContainer => viewDescriptorService.moveViewContainerToLocation(viewContainer, this.destination, undefined, this.desc.id)); layoutService.setPartHidden(false, this.destination === ViewContainerLocation.Panel ? Parts.PANEL_PART : Parts.AUXILIARYBAR_PART); if (activeViewContainer && destContainers.length === 0) { diff --git a/code/src/vs/workbench/browser/parts/panel/panelPart.ts b/code/src/vs/workbench/browser/parts/panel/panelPart.ts index f646fa500da..cd158dfcdba 100644 --- a/code/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/code/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -115,7 +115,7 @@ export class PanelPart extends AbstractPaneCompositePart { } } - protected getCompoisteBarOptions(): IPaneCompositeBarOptions { + protected getCompositeBarOptions(): IPaneCompositeBarOptions { return { partContainerClass: 'panel', pinnedViewContainersKey: 'workbench.panel.pinnedPanels', diff --git a/code/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css b/code/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css index ed995215854..4b0289f87ef 100644 --- a/code/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css +++ b/code/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css @@ -59,3 +59,7 @@ width: 16px; height: 16px; } + +.monaco-workbench .sidebar.pane-composite-part > .title > .composite-bar-container { + flex: 1; +} diff --git a/code/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/code/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index 80f26be7d4e..17a010dfad8 100644 --- a/code/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/code/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -5,31 +5,34 @@ import 'vs/css!./media/sidebarpart'; import 'vs/workbench/browser/parts/sidebar/sidebarActions'; -import { IWorkbenchLayoutService, Parts, Position as SideBarPosition } from 'vs/workbench/services/layout/browser/layoutService'; +import { ActivityBarPosition, IWorkbenchLayoutService, LayoutSettings, Parts, Position as SideBarPosition } from 'vs/workbench/services/layout/browser/layoutService'; import { SidebarFocusContext, ActiveViewletContext } from 'vs/workbench/common/contextkeys'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { badgeBackground, badgeForeground, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; -import { SIDE_BAR_TITLE_FOREGROUND, SIDE_BAR_BACKGROUND, SIDE_BAR_FOREGROUND, SIDE_BAR_BORDER, SIDE_BAR_DRAG_AND_DROP_BACKGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_DRAG_AND_DROP_BORDER } from 'vs/workbench/common/theme'; +import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; +import { SIDE_BAR_TITLE_FOREGROUND, SIDE_BAR_BACKGROUND, SIDE_BAR_FOREGROUND, SIDE_BAR_BORDER, SIDE_BAR_DRAG_AND_DROP_BACKGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_DRAG_AND_DROP_BORDER, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND } from 'vs/workbench/common/theme'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { LayoutPriority } from 'vs/base/browser/ui/grid/grid'; import { assertIsDefined } from 'vs/base/common/types'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { AbstractPaneCompositePart } from 'vs/workbench/browser/parts/paneCompositePart'; -import { ActivitybarPart } from 'vs/workbench/browser/parts/activitybar/activitybarPart'; +import { ActivityBarCompositeBar, ActivitybarPart } from 'vs/workbench/browser/parts/activitybar/activitybarPart'; import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; -import { ToggleSidebarPositionAction } from 'vs/workbench/browser/actions/layoutActions'; -import { Separator, toAction } from 'vs/base/common/actions'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { IBadge } from 'vs/workbench/services/activity/common/activity'; import { IPaneCompositeBarOptions } from 'vs/workbench/browser/parts/paneCompositeBar'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { localize } from 'vs/nls'; +import { ACCOUNTS_ACTIVITY_ID, GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { Separator } from 'vs/base/common/actions'; export class SidebarPart extends AbstractPaneCompositePart { @@ -74,6 +77,9 @@ export class SidebarPart extends AbstractPaneCompositePart { @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IContextKeyService contextKeyService: IContextKeyService, @IExtensionService extensionService: IExtensionService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @ITelemetryService telemetryService: ITelemetryService, + @ILifecycleService lifecycleService: ILifecycleService, ) { super( Parts.SIDEBAR_PART, @@ -97,6 +103,25 @@ export class SidebarPart extends AbstractPaneCompositePart { ); this.acitivityBarPart = this._register(instantiationService.createInstance(ActivitybarPart, this)); + this._register(configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('workbench.activityBar.location')) { + this.updateTitleArea(); + const id = this.getActiveComposite()?.getId(); + if (id) { + this.onTitleAreaUpdate(id!); + } + } + })); + + this.registerGlobalActions(); + + lifecycleService.when(LifecyclePhase.Eventually).then(() => { + telemetryService.publicLog2<{ location: string }, { + owner: 'sandy081'; + location: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Locaiton where the activity bar is shown' }; + comment: 'This is used to know where activity bar is shown in the workbench.'; + }>('activityBar:location', { location: configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) }); + }); } override updateStyles(): void { @@ -131,9 +156,13 @@ export class SidebarPart extends AbstractPaneCompositePart { return this.layoutService.getSideBarPosition() === SideBarPosition.LEFT ? AnchorAlignment.LEFT : AnchorAlignment.RIGHT; } - protected getCompoisteBarOptions(): IPaneCompositeBarOptions { + protected override createCompisteBar(): ActivityBarCompositeBar { + return this.instantiationService.createInstance(ActivityBarCompositeBar, this.getCompositeBarOptions(), this.partId, this, false); + } + + protected getCompositeBarOptions(): IPaneCompositeBarOptions { return { - partContainerClass: 'activitybar', + partContainerClass: 'sidebar', pinnedViewContainersKey: ActivitybarPart.pinnedViewContainersKey, placeholderViewContainersKey: ActivitybarPart.placeholderViewContainersKey, icon: true, @@ -143,9 +172,11 @@ export class SidebarPart extends AbstractPaneCompositePart { position: () => HoverPosition.BELOW, }, fillExtraContextMenuActions: actions => { - // Toggle Sidebar - actions.push(new Separator()); - actions.push(toAction({ id: ToggleSidebarPositionAction.ID, label: ToggleSidebarPositionAction.getLabel(this.layoutService), run: () => this.instantiationService.invokeFunction(accessor => new ToggleSidebarPositionAction().run(accessor)) })); + const viewsSubmenuAction = this.getViewsSubmenuAction(); + if (viewsSubmenuAction) { + actions.push(new Separator()); + actions.push(viewsSubmenuAction); + } }, compositeSize: 0, iconSize: 16, @@ -156,15 +187,16 @@ export class SidebarPart extends AbstractPaneCompositePart { activeBorderBottomColor: theme.getColor(PANEL_ACTIVE_TITLE_BORDER), activeForegroundColor: theme.getColor(PANEL_ACTIVE_TITLE_FOREGROUND), inactiveForegroundColor: theme.getColor(PANEL_INACTIVE_TITLE_FOREGROUND), - badgeBackground: theme.getColor(badgeBackground), - badgeForeground: theme.getColor(badgeForeground), + badgeBackground: theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND), + badgeForeground: theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND), dragAndDropBorder: theme.getColor(PANEL_DRAG_AND_DROP_BORDER) - }) + }), + compact: true }; } protected shouldShowCompositeBar(): boolean { - return false; + return this.layoutService.isVisible(Parts.TITLEBAR_PART) && this.configurationService.getValue('workbench.activityBar.location') === ActivityBarPosition.TOP; } override getPinnedPaneCompositeIds(): string[] { @@ -175,14 +207,54 @@ export class SidebarPart extends AbstractPaneCompositePart { return this.shouldShowCompositeBar() ? super.getVisiblePaneCompositeIds() : this.acitivityBarPart.getVisiblePaneCompositeIds(); } - override showActivity(id: string, badge: IBadge, clazz?: string, priority?: number): IDisposable { + focusActivityBar(): void { if (this.shouldShowCompositeBar()) { - return super.showActivity(id, badge, clazz, priority); + this.focusComositeBar(); } else { - return this.acitivityBarPart.showActivity(id, badge, clazz, priority); + if (!this.layoutService.isVisible(Parts.ACTIVITYBAR_PART)) { + this.layoutService.setPartHidden(false, Parts.ACTIVITYBAR_PART); + } + this.acitivityBarPart.focus(); } } + private registerGlobalActions() { + this._register(registerAction2( + class extends Action2 { + constructor() { + super({ + id: GLOBAL_ACTIVITY_ID, + title: { value: localize('manage', "Manage"), original: 'Manage' }, + menu: [{ + id: MenuId.TitleBarGlobalControlMenu, + when: ContextKeyExpr.equals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.TOP), + order: 2 + }] + }); + } + + async run(): Promise { + } + })); + this._register(registerAction2( + class extends Action2 { + constructor() { + super({ + id: ACCOUNTS_ACTIVITY_ID, + title: { value: localize('accounts', "Accounts"), original: 'Accounts' }, + menu: [{ + id: MenuId.TitleBarGlobalControlMenu, + when: ContextKeyExpr.equals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.TOP), + order: 2 + }] + }); + } + + async run(): Promise { + } + })); + } + toJSON(): object { return { type: Parts.SIDEBAR_PART diff --git a/code/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/code/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 86a2065ff18..5325e70151d 100644 --- a/code/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/code/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -362,6 +362,7 @@ } /* Layout Controls */ +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .global-actions-container, .monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .layout-controls-container { display: none; padding-right: 2px; @@ -372,23 +373,89 @@ z-index: 2500; -webkit-app-region: no-drag; height: 100%; - margin-left: auto; min-width: 28px; } +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .layout-controls-container { + margin-left: auto; +} + +.monaco-workbench.mac:not(.web) .part.titlebar > .titlebar-container > .titlebar-right > .global-actions-container, .monaco-workbench.mac:not(.web) .part.titlebar > .titlebar-container > .titlebar-right > .layout-controls-container { right: 8px; } +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .global-actions-container:not(.has-no-actions), .monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .layout-controls-container.show-layout-control { display: flex; justify-content: center; } +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .global-actions-container .codicon, .monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .layout-controls-container .codicon { color: inherit; } +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .global-actions-container .monaco-action-bar .action-item { + display: flex; +} + +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .global-actions-container .monaco-action-bar .badge { + margin-left: 8px; + display: flex; + align-items: center; +} + +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .global-actions-container .monaco-action-bar .action-item.icon .badge { + margin-left: 0px; +} + +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .global-actions-container .monaco-action-bar .badge .badge-content { + padding: 3px 5px; + border-radius: 11px; + font-size: 9px; + min-width: 11px; + height: 16px; + line-height: 11px; + font-weight: normal; + text-align: center; + display: inline-block; + box-sizing: border-box; + position: relative; +} + +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .global-actions-container .monaco-action-bar .action-item.icon .badge.compact { + position: absolute; + top: 0; + bottom: 0; + margin: auto; + left: 0; + overflow: hidden; + width: 100%; + height: 100%; + z-index: 2; +} + +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .global-actions-container .monaco-action-bar .action-item.icon .badge.compact .badge-content::before { + mask-size: 10px; + -webkit-mask-size: 10px; + top: 4px; +} + +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .global-actions-container .monaco-action-bar .action-item.icon .badge.compact .badge-content { + position: absolute; + top: 12px; + right: 0px; + font-size: 9px; + font-weight: 600; + min-width: 10px; + height: 10px; + line-height: 10px; + padding: 0 4px; + border-radius: 16px; + text-align: center; +} + .monaco-workbench .part.titlebar .window-controls-container .window-icon { color: var(--vscode-titleBar-activeForeground); } diff --git a/code/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/code/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index b15e2930425..a2db02dcadf 100644 --- a/code/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/code/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -36,7 +36,10 @@ import { CommandCenterControl } from 'vs/workbench/browser/parts/titlebar/comman import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; -import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; +import { HiddenItemStrategy, MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; +import { ACCOUNTS_ACTIVITY_ID, GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity'; +import { SimpleAccountActivityActionViewItem, SimpleGlobalActivityActionViewItem } from 'vs/workbench/browser/parts/globalCompositeBar'; +import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; export class TitlebarPart extends Part implements ITitleService { @@ -283,6 +286,23 @@ export class TitlebarPart extends Part implements ITitleService { return createActionViewItem(this.instantiationService, action, { hoverDelegate: this.hoverDelegate }); } })); + + const globalActionControls = append(this.rightContent, $('div.global-actions-container.show-control')); + + this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, globalActionControls, MenuId.TitleBarGlobalControlMenu, { + contextMenu: MenuId.TitleBarContext, + toolbarOptions: { primaryGroup: () => true }, + hiddenItemStrategy: HiddenItemStrategy.NoHide, + actionViewItemProvider: action => { + if (action.id === GLOBAL_ACTIVITY_ID) { + return this.instantiationService.createInstance(SimpleGlobalActivityActionViewItem, { position: () => HoverPosition.BELOW }); + } + if (action.id === ACCOUNTS_ACTIVITY_ID) { + return this.instantiationService.createInstance(SimpleAccountActivityActionViewItem, { position: () => HoverPosition.BELOW }); + } + return createActionViewItem(this.instantiationService, action, { hoverDelegate: this.hoverDelegate }); + } + })); } let primaryControlLocation = isMacintosh ? 'left' : 'right'; diff --git a/code/src/vs/workbench/browser/parts/titlebar/windowTitle.ts b/code/src/vs/workbench/browser/parts/titlebar/windowTitle.ts index 64588af027b..a723be76707 100644 --- a/code/src/vs/workbench/browser/parts/titlebar/windowTitle.ts +++ b/code/src/vs/workbench/browser/parts/titlebar/windowTitle.ts @@ -25,6 +25,7 @@ import { Schemas } from 'vs/base/common/network'; import { getVirtualWorkspaceLocation } from 'vs/platform/workspace/common/virtualWorkspace'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { IViewsService } from 'vs/workbench/common/views'; +import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; const enum WindowSettingNames { titleSeparator = 'window.titleSeparator', @@ -45,6 +46,7 @@ export class WindowTitle extends Disposable { readonly onDidChange = this.onDidChangeEmitter.event; private title: string | undefined; + private titleIncludesFocusedView: boolean = false; constructor( @IConfigurationService protected readonly configurationService: IConfigurationService, @@ -58,6 +60,8 @@ export class WindowTitle extends Disposable { @IViewsService private readonly viewsService: IViewsService ) { super(); + + this.updateTitleIncludesFocusedView(); this.registerListeners(); } @@ -77,15 +81,28 @@ export class WindowTitle extends Disposable { this._register(this.contextService.onDidChangeWorkspaceName(() => this.titleUpdater.schedule())); this._register(this.labelService.onDidChangeFormatters(() => this.titleUpdater.schedule())); this._register(this.userDataProfileService.onDidChangeCurrentProfile(() => this.titleUpdater.schedule())); - this._register(this.viewsService.onDidChangeFocusedView(() => this.titleUpdater.schedule())); + this._register(this.viewsService.onDidChangeFocusedView(() => { + if (this.titleIncludesFocusedView) { + this.titleUpdater.schedule(); + } + })); } private onConfigurationChanged(event: IConfigurationChangeEvent): void { + if (event.affectsConfiguration(WindowSettingNames.title)) { + this.updateTitleIncludesFocusedView(); + } + if (event.affectsConfiguration(WindowSettingNames.title) || event.affectsConfiguration(WindowSettingNames.titleSeparator)) { this.titleUpdater.schedule(); } } + private updateTitleIncludesFocusedView(): void { + const titleTemplate = this.configurationService.getValue(WindowSettingNames.title); + this.titleIncludesFocusedView = typeof titleTemplate === 'string' && titleTemplate.includes('${focusedView}'); + } + private onActiveEditorChange(): void { // Dispose old listeners @@ -100,6 +117,22 @@ export class WindowTitle extends Disposable { this.activeEditorListeners.add(activeEditor.onDidChangeDirty(() => this.titleUpdater.schedule())); this.activeEditorListeners.add(activeEditor.onDidChangeLabel(() => this.titleUpdater.schedule())); } + + // Apply listeners for tracking focused code editor + if (this.titleIncludesFocusedView) { + const activeTextEditorControl = this.editorService.activeTextEditorControl; + const textEditorControls: ICodeEditor[] = []; + if (isCodeEditor(activeTextEditorControl)) { + textEditorControls.push(activeTextEditorControl); + } else if (isDiffEditor(activeTextEditorControl)) { + textEditorControls.push(activeTextEditorControl.getOriginalEditor(), activeTextEditorControl.getModifiedEditor()); + } + + for (const textEditorControl of textEditorControls) { + this.activeEditorListeners.add(textEditorControl.onDidBlurEditorText(() => this.titleUpdater.schedule())); + this.activeEditorListeners.add(textEditorControl.onDidFocusEditorText(() => this.titleUpdater.schedule())); + } + } } private doUpdateTitle(): void { @@ -189,6 +222,7 @@ export class WindowTitle extends Disposable { * {appName}: e.g. VS Code * {remoteName}: e.g. SSH * {dirty}: indicator + * {focusedView}: e.g. Terminal * {separator}: conditional separator */ getWindowTitle(): string { @@ -276,6 +310,7 @@ export class WindowTitle extends Disposable { isCustomTitleFormat(): boolean { const title = this.configurationService.inspect(WindowSettingNames.title); const titleSeparator = this.configurationService.inspect(WindowSettingNames.titleSeparator); + return title.value !== title.defaultValue || titleSeparator.value !== titleSeparator.defaultValue; } } diff --git a/code/src/vs/workbench/browser/parts/views/viewPane.ts b/code/src/vs/workbench/browser/parts/views/viewPane.ts index 8b2c60b2d66..0f4390cc8b0 100644 --- a/code/src/vs/workbench/browser/parts/views/viewPane.ts +++ b/code/src/vs/workbench/browser/parts/views/viewPane.ts @@ -370,7 +370,7 @@ export abstract class ViewPane extends Pane implements IView { const viewLocationKey = this.scopedContextKeyService.createKey('viewLocation', ViewContainerLocationToString(viewDescriptorService.getViewLocationById(this.id)!)); this._register(Event.filter(viewDescriptorService.onDidChangeLocation, e => e.views.some(view => view.id === this.id))(() => viewLocationKey.set(ViewContainerLocationToString(viewDescriptorService.getViewLocationById(this.id)!)))); - this.menuActions = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService])).createInstance(CompositeMenuActions, options.titleMenuId ?? MenuId.ViewTitle, MenuId.ViewTitleContext, { shouldForwardArgs: !options.donotForwardArgs })); + this.menuActions = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService])).createInstance(CompositeMenuActions, options.titleMenuId ?? MenuId.ViewTitle, MenuId.ViewTitleContext, { shouldForwardArgs: !options.donotForwardArgs, renderShortTitle: true })); this._register(this.menuActions.onDidChange(() => this.updateActions())); } diff --git a/code/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/code/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index f23c48b7db1..0a785719598 100644 --- a/code/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/code/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -38,7 +38,7 @@ import { PANEL_SECTION_BORDER, PANEL_SECTION_DRAG_AND_DROP_BACKGROUND, PANEL_SEC import { IAddedViewDescriptorRef, ICustomViewDescriptor, IView, IViewContainerModel, IViewDescriptor, IViewDescriptorRef, IViewDescriptorService, IViewPaneContainer, IViewsService, ViewContainer, ViewContainerLocation, ViewContainerLocationToString, ViewVisibilityState } from 'vs/workbench/common/views'; import { FocusedViewContext } from 'vs/workbench/common/contextkeys'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IWorkbenchLayoutService, Position } from 'vs/workbench/services/layout/browser/layoutService'; +import { ActivityBarPosition, IWorkbenchLayoutService, LayoutSettings, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; export const ViewsSubMenu = new MenuId('Views'); MenuRegistry.appendMenuItem(MenuId.ViewContainerTitle, { @@ -299,7 +299,7 @@ class ViewContainerMenuActions extends CompositeMenuActions { const scopedContextKeyService = contextKeyService.createScoped(element); scopedContextKeyService.createKey('viewContainer', viewContainer.id); const viewContainerLocationKey = scopedContextKeyService.createKey('viewContainerLocation', ViewContainerLocationToString(viewDescriptorService.getViewContainerLocation(viewContainer)!)); - super(MenuId.ViewContainerTitle, MenuId.ViewContainerTitleContext, { shouldForwardArgs: true }, scopedContextKeyService, menuService); + super(MenuId.ViewContainerTitle, MenuId.ViewContainerTitleContext, { shouldForwardArgs: true, renderShortTitle: true }, scopedContextKeyService, menuService); this._register(scopedContextKeyService); this._register(Event.filter(viewDescriptorService.onDidChangeContainerLocation, e => e.viewContainer === viewContainer)(() => viewContainerLocationKey.set(ViewContainerLocationToString(viewDescriptorService.getViewContainerLocation(viewContainer)!)))); } @@ -503,14 +503,14 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { const oldViewContainer = this.viewDescriptorService.getViewContainerByViewId(dropData.id); const viewDescriptor = this.viewDescriptorService.getViewDescriptorById(dropData.id); if (oldViewContainer !== this.viewContainer && viewDescriptor && viewDescriptor.canMoveView) { - this.viewDescriptorService.moveViewsToContainer([viewDescriptor], this.viewContainer); + this.viewDescriptorService.moveViewsToContainer([viewDescriptor], this.viewContainer, undefined, 'dnd'); } } const paneCount = this.panes.length; if (viewsToMove.length > 0) { - this.viewDescriptorService.moveViewsToContainer(viewsToMove, this.viewContainer); + this.viewDescriptorService.moveViewsToContainer(viewsToMove, this.viewContainer, undefined, 'dnd'); } if (paneCount > 0) { @@ -770,7 +770,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { const pane = this.createView(viewDescriptor, { id: viewDescriptor.id, - title: viewDescriptor.name, + title: viewDescriptor.name.value, fromExtensionId: (viewDescriptor as Partial).extensionId, expanded: !collapsed }); @@ -923,7 +923,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } if (viewsToMove) { - this.viewDescriptorService.moveViewsToContainer(viewsToMove, this.viewContainer); + this.viewDescriptorService.moveViewsToContainer(viewsToMove, this.viewContainer, undefined, 'dnd'); } if (anchorView) { @@ -1069,6 +1069,15 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } isViewMergedWithContainer(): boolean { + const location = this.viewDescriptorService.getViewContainerLocation(this.viewContainer); + // Do not merge views in secondary side bar because the view title is not shown + if (location === ViewContainerLocation.AuxiliaryBar) { + return false; + } + // Do not merge views in side bar when activity bar is on top because the view title is not shown + if (location === ViewContainerLocation.Sidebar && !this.layoutService.isVisible(Parts.ACTIVITYBAR_PART) && this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.TOP) { + return false; + } if (!(this.options.mergeViewWithContainerWhenSingleView && this.paneItems.length === 1)) { return false; } @@ -1272,7 +1281,7 @@ registerAction2(class MoveViews extends Action2 { for (const viewId of options.viewIds) { const viewDescriptor = viewDescriptorService.getViewDescriptorById(viewId); if (viewDescriptor?.canMoveView) { - viewDescriptorService.moveViewsToContainer([viewDescriptor], destination, ViewVisibilityState.Default); + viewDescriptorService.moveViewsToContainer([viewDescriptor], destination, ViewVisibilityState.Default, this.desc.id); } } diff --git a/code/src/vs/workbench/browser/parts/views/viewsService.ts b/code/src/vs/workbench/browser/parts/views/viewsService.ts index 560293cc1a1..0907c9e8783 100644 --- a/code/src/vs/workbench/browser/parts/views/viewsService.ts +++ b/code/src/vs/workbench/browser/parts/views/viewsService.ts @@ -32,6 +32,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { FilterViewPaneContainer } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { ICommandActionTitle, ILocalizedString } from 'vs/platform/action/common/action'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export class ViewsService extends Disposable implements IViewsService { @@ -56,7 +57,8 @@ export class ViewsService extends Disposable implements IViewsService { @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, @IPaneCompositePartService private readonly paneCompositeService: IPaneCompositePartService, @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + @IEditorService private readonly editorService: IEditorService ) { super(); @@ -239,7 +241,8 @@ export class ViewsService extends Disposable implements IViewsService { getFocusedViewName(): string { const viewId: string = this.contextKeyService.getContextKeyValue(FocusedViewContext.key) ?? ''; - return this.viewDescriptorService.getViewDescriptorById(viewId.toString())?.name ?? ''; + const textEditorFocused = this.editorService.activeTextEditorControl?.hasTextFocus() ? localize('editor', "Text Editor") : undefined; + return this.viewDescriptorService.getViewDescriptorById(viewId.toString())?.name?.value ?? textEditorFocused ?? ''; } async openView(id: string, focus?: boolean): Promise { @@ -484,10 +487,10 @@ export class ViewsService extends Disposable implements IViewsService { private registerFocusViewAction(viewDescriptor: IViewDescriptor, category?: string | ILocalizedString): IDisposable { return registerAction2(class FocusViewAction extends Action2 { constructor() { - const title = localize({ key: 'focus view', comment: ['{0} indicates the name of the view to be focused.'] }, "Focus on {0} View", viewDescriptor.name); + const title = localize({ key: 'focus view', comment: ['{0} indicates the name of the view to be focused.'] }, "Focus on {0} View", viewDescriptor.name.value); super({ id: viewDescriptor.focusCommand ? viewDescriptor.focusCommand.id : `${viewDescriptor.id}.focus`, - title: { original: `Focus on ${viewDescriptor.name} View`, value: title }, + title: { original: `Focus on ${viewDescriptor.name.original} View`, value: title }, category, menu: [{ id: MenuId.CommandPalette, @@ -558,10 +561,10 @@ export class ViewsService extends Disposable implements IViewsService { // The default container is hidden so we should try to reset its location first if (defaultContainer.hideIfEmpty && containerModel.visibleViewDescriptors.length === 0) { const defaultLocation = viewDescriptorService.getDefaultViewContainerLocation(defaultContainer)!; - viewDescriptorService.moveViewContainerToLocation(defaultContainer, defaultLocation); + viewDescriptorService.moveViewContainerToLocation(defaultContainer, defaultLocation, undefined, this.desc.id); } - viewDescriptorService.moveViewsToContainer([viewDescriptor], viewDescriptorService.getDefaultContainerById(viewDescriptor.id)!); + viewDescriptorService.moveViewsToContainer([viewDescriptor], viewDescriptorService.getDefaultContainerById(viewDescriptor.id)!, undefined, this.desc.id); accessor.get(IViewsService).openView(viewDescriptor.id, true); } }); diff --git a/code/src/vs/workbench/browser/web.main.ts b/code/src/vs/workbench/browser/web.main.ts index 03a5a2f4d7e..0ec2e0388f0 100644 --- a/code/src/vs/workbench/browser/web.main.ts +++ b/code/src/vs/workbench/browser/web.main.ts @@ -20,7 +20,7 @@ import { RemoteAgentService } from 'vs/workbench/services/remote/browser/remoteA import { RemoteAuthorityResolverService } from 'vs/platform/remote/browser/remoteAuthorityResolverService'; import { IRemoteAuthorityResolverService, RemoteConnectionType } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { IWorkbenchFileService } from 'vs/workbench/services/files/common/files'; +import { IFileService } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; import { Schemas, connectionTokenCookieName } from 'vs/base/common/network'; import { IAnyWorkspaceIdentifier, IWorkspaceContextService, UNKNOWN_EMPTY_WINDOW_WORKSPACE, isTemporaryWorkspace, isWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; @@ -261,7 +261,7 @@ export class BrowserMain extends Disposable { // Files const fileLogger = new BufferLogger(); const fileService = this._register(new FileService(fileLogger)); - serviceCollection.set(IWorkbenchFileService, fileService); + serviceCollection.set(IFileService, fileService); // Logger const loggerService = new FileLoggerService(getLogLevel(environmentService), logsPath, fileService); @@ -429,7 +429,8 @@ export class BrowserMain extends Disposable { } } - private async registerIndexedDBFileSystemProviders(environmentService: IWorkbenchEnvironmentService, fileService: IWorkbenchFileService, logService: ILogService, loggerService: ILoggerService, logsPath: URI): Promise { + private async registerIndexedDBFileSystemProviders(environmentService: IWorkbenchEnvironmentService, fileService: IFileService, logService: ILogService, loggerService: ILoggerService, logsPath: URI): Promise { + // IndexedDB is used for logging and user data let indexedDB: IndexedDB | undefined; const userDataStore = 'vscode-userdata-store'; diff --git a/code/src/vs/workbench/browser/window.ts b/code/src/vs/workbench/browser/window.ts index db5aa59f80e..94b46dd7381 100644 --- a/code/src/vs/workbench/browser/window.ts +++ b/code/src/vs/workbench/browser/window.ts @@ -17,7 +17,6 @@ import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IDialogService, IPromptButton } from 'vs/platform/dialogs/common/dialogs'; -import { registerWindowDriver } from 'vs/platform/driver/browser/driver'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; import { IOpenerService, matchesScheme } from 'vs/platform/opener/common/opener'; @@ -27,6 +26,7 @@ import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/la import { BrowserLifecycleService } from 'vs/workbench/services/lifecycle/browser/lifecycleService'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { registerWindowDriver } from 'vs/workbench/services/driver/browser/driver'; export class BrowserWindow extends Disposable { diff --git a/code/src/vs/workbench/browser/workbench.contribution.ts b/code/src/vs/workbench/browser/workbench.contribution.ts index 2017f37c3b9..5e867ccc662 100644 --- a/code/src/vs/workbench/browser/workbench.contribution.ts +++ b/code/src/vs/workbench/browser/workbench.contribution.ts @@ -7,10 +7,11 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { localize } from 'vs/nls'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { isMacintosh, isWindows, isLinux, isWeb, isNative } from 'vs/base/common/platform'; -import { ConfigurationMigrationWorkbenchContribution, DynamicWorkbenchConfigurationWorkbenchContribution, workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; +import { ConfigurationMigrationWorkbenchContribution, DynamicWorkbenchConfigurationWorkbenchContribution, IConfigurationMigrationRegistry, workbenchConfigurationNodeBase, Extensions, ConfigurationKeyValuePairs } from 'vs/workbench/common/configuration'; import { isStandalone } from 'vs/base/browser/browser'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { ActivityBarPosition, LayoutSettings } from 'vs/workbench/services/layout/browser/layoutService'; const registry = Registry.as(ConfigurationExtensions.Configuration); @@ -462,10 +463,16 @@ const registry = Registry.as(ConfigurationExtensions.Con 'default': true, 'description': localize('statusBarVisibility', "Controls the visibility of the status bar at the bottom of the workbench.") }, - 'workbench.activityBar.visible': { - 'type': 'boolean', - 'default': true, - 'description': localize('activityBarVisibility', "Controls the visibility of the activity bar in the workbench.") + [LayoutSettings.ACTIVITY_BAR_LOCATION]: { + 'type': 'string', + 'enum': ['side', 'top', 'hidden'], + 'default': 'side', + 'markdownDescription': localize({ comment: ['This is the description for a setting'], key: 'activityBarLocation' }, "Controls the location of the activity bar. It can either show to the `side` or `top` (requires `{0}`) of the primary side bar or `hidden`.", '#window.commandCenter#'), + 'enumDescriptions': [ + localize('workbench.activityBar.location.side', "Show the activity bar to the side of the primary side bar."), + localize('workbench.activityBar.location.top', "Show the activity bar on top of the primary side bar."), + localize('workbench.activityBar.location.hide', "Hide the activity bar.") + ] }, 'workbench.activityBar.iconClickBehavior': { 'type': 'string', @@ -747,3 +754,14 @@ const registry = Registry.as(ConfigurationExtensions.Con } }); })(); + +Registry.as(Extensions.ConfigurationMigration) + .registerConfigurationMigrations([{ + key: LayoutSettings.ACTIVITY_BAR_LOCATION, migrateFn: (value: any) => { + const result: ConfigurationKeyValuePairs = [['workbench.activityBar.visible', { value: undefined }]]; + if (value === false) { + result.push([LayoutSettings.ACTIVITY_BAR_LOCATION, { value: ActivityBarPosition.HIDDEN }]); + } + return result; + } + }]); diff --git a/code/src/vs/workbench/common/activity.ts b/code/src/vs/workbench/common/activity.ts index 2fb866870a8..748879ebf30 100644 --- a/code/src/vs/workbench/common/activity.ts +++ b/code/src/vs/workbench/common/activity.ts @@ -3,15 +3,5 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { URI } from 'vs/base/common/uri'; - -export interface IActivity { - id: string; - name: string; - keybindingId?: string; - classNames?: string[]; - iconUrl?: URI; -} - -export const GLOBAL_ACTIVITY_ID = 'workbench.action.globalActivity'; -export const ACCOUNTS_ACTIVITY_ID = 'workbench.action.accountsActivity'; +export const GLOBAL_ACTIVITY_ID = 'workbench.actions.manage'; +export const ACCOUNTS_ACTIVITY_ID = 'workbench.actions.accounts'; diff --git a/code/src/vs/workbench/common/contextkeys.ts b/code/src/vs/workbench/common/contextkeys.ts index 585b45ec13c..8403b656459 100644 --- a/code/src/vs/workbench/common/contextkeys.ts +++ b/code/src/vs/workbench/common/contextkeys.ts @@ -97,6 +97,12 @@ export const StatusBarFocused = new RawContextKey('statusBarFocused', f //#endregion +//#region < --- Title Bar --- > + +export const TitleBarVisibleContext = new RawContextKey('titleBarVisible', false, localize('titleBarVisible', "Whether the title bar is visible")); + +//#endregion + //#region < --- Banner --- > diff --git a/code/src/vs/workbench/common/editor.ts b/code/src/vs/workbench/common/editor.ts index 41664fd79f8..5cef907363e 100644 --- a/code/src/vs/workbench/common/editor.ts +++ b/code/src/vs/workbench/common/editor.ts @@ -751,7 +751,13 @@ export const enum EditorInputCapabilities { * Signals that the editor cannot be in a dirty state * and may still have unsaved changes */ - Scratchpad = 1 << 9 + Scratchpad = 1 << 9, + + /** + * Signals that the editor does not support opening in + * auxiliary windows yet. + */ + AuxWindowUnsupported = 1 << 10 } export type IUntypedEditorInput = IResourceEditorInput | ITextResourceEditorInput | IUntitledTextResourceEditorInput | IResourceDiffEditorInput | IResourceSideBySideEditorInput | IResourceMergeEditorInput; diff --git a/code/src/vs/workbench/common/views.ts b/code/src/vs/workbench/common/views.ts index 84cee2b04a6..db7f36bbbea 100644 --- a/code/src/vs/workbench/common/views.ts +++ b/code/src/vs/workbench/common/views.ts @@ -29,6 +29,8 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { VSDataTransfer } from 'vs/base/common/dataTransfer'; import { ILocalizedString } from 'vs/platform/action/common/action'; +export const VIEWS_LOG_ID = 'views'; +export const VIEWS_LOG_NAME = localize('views log', "Views"); export const defaultViewIcon = registerIcon('default-view-icon', Codicon.window, localize('defaultViewIcon', 'Default view icon.')); export namespace Extensions { @@ -263,7 +265,7 @@ export interface IViewDescriptor { readonly id: string; - readonly name: string; + readonly name: ILocalizedString; readonly ctorDescriptor: SyncDescriptor; @@ -607,14 +609,13 @@ export interface IViewDescriptorService { getDefaultViewContainer(location: ViewContainerLocation): ViewContainer | undefined; getViewContainerById(id: string): ViewContainer | null; - isViewContainerRemovedPermanently(id: string): boolean; getDefaultViewContainerLocation(viewContainer: ViewContainer): ViewContainerLocation | null; getViewContainerLocation(viewContainer: ViewContainer): ViewContainerLocation | null; getViewContainersByLocation(location: ViewContainerLocation): ViewContainer[]; getViewContainerModel(viewContainer: ViewContainer): IViewContainerModel; readonly onDidChangeContainerLocation: Event<{ viewContainer: ViewContainer; from: ViewContainerLocation; to: ViewContainerLocation }>; - moveViewContainerToLocation(viewContainer: ViewContainer, location: ViewContainerLocation, requestedIndex?: number): void; + moveViewContainerToLocation(viewContainer: ViewContainer, location: ViewContainerLocation, requestedIndex?: number, reason?: string): void; getViewContainerBadgeEnablementState(id: string): boolean; setViewContainerBadgeEnablementState(id: string, badgesEnabled: boolean): void; @@ -626,10 +627,10 @@ export interface IViewDescriptorService { getViewLocationById(id: string): ViewContainerLocation | null; readonly onDidChangeContainer: Event<{ views: IViewDescriptor[]; from: ViewContainer; to: ViewContainer }>; - moveViewsToContainer(views: IViewDescriptor[], viewContainer: ViewContainer, visibilityState?: ViewVisibilityState): void; + moveViewsToContainer(views: IViewDescriptor[], viewContainer: ViewContainer, visibilityState?: ViewVisibilityState, reason?: string): void; readonly onDidChangeLocation: Event<{ views: IViewDescriptor[]; from: ViewContainerLocation; to: ViewContainerLocation }>; - moveViewToLocation(view: IViewDescriptor, location: ViewContainerLocation): void; + moveViewToLocation(view: IViewDescriptor, location: ViewContainerLocation, reason?: string): void; reset(): void; } diff --git a/code/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts b/code/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts index efc4a052f46..5d513a9be9a 100644 --- a/code/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts +++ b/code/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts @@ -13,9 +13,12 @@ import { UnfocusedViewDimmingContribution } from 'vs/workbench/contrib/accessibi import { EditorAccessibilityHelpContribution, HoverAccessibleViewContribution, InlineCompletionsAccessibleViewContribution, NotificationAccessibleViewContribution } from 'vs/workbench/contrib/accessibility/browser/accessibilityContributions'; import { AccessibilityStatus } from 'vs/workbench/contrib/accessibility/browser/accessibilityStatus'; import { CommentsAccessibilityHelpContribution } from 'vs/workbench/contrib/comments/browser/comments.contribution'; +import { IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; +import { AccessibleNotificationService } from 'vs/platform/accessibility/browser/accessibleNotificationService'; registerAccessibilityConfiguration(); registerSingleton(IAccessibleViewService, AccessibleViewService, InstantiationType.Delayed); +registerSingleton(IAccessibleNotificationService, AccessibleNotificationService, InstantiationType.Delayed); const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchRegistry.registerWorkbenchContribution(EditorAccessibilityHelpContribution, LifecyclePhase.Eventually); diff --git a/code/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/code/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 744907f7020..b0faf8916b8 100644 --- a/code/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/code/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -23,7 +23,8 @@ export const accessibleViewCurrentProviderId = new RawContextKey('access */ export const enum AccessibilityWorkbenchSettingId { DimUnfocusedEnabled = 'accessibility.dimUnfocused.enabled', - DimUnfocusedOpacity = 'accessibility.dimUnfocused.opacity' + DimUnfocusedOpacity = 'accessibility.dimUnfocused.opacity', + HideAccessibleView = 'accessibility.hideAccessibleView' } export const enum ViewDimUnfocusedOpacityProperties { @@ -47,6 +48,10 @@ export const enum AccessibilityVerbositySettingId { Comments = 'accessibility.verbosity.comments' } +export const enum AccessibilityAlertSettingId { + Save = 'accessibility.alert.save' +} + export const enum AccessibleViewProviderId { Terminal = 'terminal', TerminalHelp = 'terminal-help', @@ -75,27 +80,27 @@ const configuration: IConfigurationNode = { type: 'object', properties: { [AccessibilityVerbositySettingId.Terminal]: { - description: localize('verbosity.terminal.description', 'Provide information about how to access the terminal accessibility help menu when the terminal is focused'), + description: localize('verbosity.terminal.description', 'Provide information about how to access the terminal accessibility help menu when the terminal is focused.'), ...baseProperty }, [AccessibilityVerbositySettingId.DiffEditor]: { - description: localize('verbosity.diffEditor.description', 'Provide information about how to navigate changes in the diff editor when it is focused'), + description: localize('verbosity.diffEditor.description', 'Provide information about how to navigate changes in the diff editor when it is focused.'), ...baseProperty }, [AccessibilityVerbositySettingId.Chat]: { - description: localize('verbosity.chat.description', 'Provide information about how to access the chat help menu when the chat input is focused'), + description: localize('verbosity.chat.description', 'Provide information about how to access the chat help menu when the chat input is focused.'), ...baseProperty }, [AccessibilityVerbositySettingId.InlineChat]: { - description: localize('verbosity.interactiveEditor.description', 'Provide information about how to access the inline editor chat accessibility help menu and alert with hints which describe how to use the feature when the input is focused'), + description: localize('verbosity.interactiveEditor.description', 'Provide information about how to access the inline editor chat accessibility help menu and alert with hints that describe how to use the feature when the input is focused.'), ...baseProperty }, [AccessibilityVerbositySettingId.InlineCompletions]: { - description: localize('verbosity.inlineCompletions.description', 'Provide information about how to access the inline completions hover and accessible view'), + description: localize('verbosity.inlineCompletions.description', 'Provide information about how to access the inline completions hover and accessible view.'), ...baseProperty }, [AccessibilityVerbositySettingId.KeybindingsEditor]: { - description: localize('verbosity.keybindingsEditor.description', 'Provide information about how to change a keybinding in the keybindings editor when a row is focused'), + description: localize('verbosity.keybindingsEditor.description', 'Provide information about how to change a keybinding in the keybindings editor when a row is focused.'), ...baseProperty }, [AccessibilityVerbositySettingId.Notebook]: { @@ -117,7 +122,19 @@ const configuration: IConfigurationNode = { [AccessibilityVerbositySettingId.Comments]: { description: localize('verbosity.comments', 'Provide information about actions that can be taken in the comment widget or in a file which contains comments.'), ...baseProperty - } + }, + [AccessibilityAlertSettingId.Save]: { + 'markdownDescription': localize('alert.save', "When in screen reader mode, alerts when a file is saved. Also see {0}", '`#audioCues.save#`'), + 'type': 'string', + 'enum': ['userGesture', 'always', 'never'], + 'default': 'never', + 'enumDescriptions': [ + localize('alert.save.userGesture', "Alerts when a file is saved via user gesture."), + localize('alert.save.always', "Alerts whenever is a file is saved, including auto save."), + localize('alert.save.never', "Never alerts.") + ], + tags: ['accessibility'] + }, } }; @@ -143,6 +160,12 @@ export function registerAccessibilityConfiguration() { default: ViewDimUnfocusedOpacityProperties.Default, tags: ['accessibility'], scope: ConfigurationScope.APPLICATION, + }, + [AccessibilityWorkbenchSettingId.HideAccessibleView]: { + description: localize('terminal.integrated.hideAccessibleView', "Controls whether the terminal's accessible view is hidden."), + type: 'boolean', + default: false, + tags: ['accessibility'] } } }); diff --git a/code/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts b/code/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts index e7506ef9e9c..4d8919facea 100644 --- a/code/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts +++ b/code/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts @@ -329,10 +329,6 @@ export class InlineCompletionsAccessibleViewContribution extends Disposable { return false; } const lineText = model.textModel.getLineContent(state.ghostText.lineNumber); - if (!lineText) { - return false; - } - const ghostText = state.ghostText.renderForScreenReader(lineText); if (!ghostText) { return false; diff --git a/code/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/code/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 95848abd6aa..751f8aa7bd0 100644 --- a/code/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/code/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -38,8 +38,7 @@ import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IPickerQuickAccessItem } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; -import { AccessibilityVerbositySettingId, AccessibleViewProviderId, accessibilityHelpIsShown, accessibleViewCurrentProviderId, accessibleViewGoToSymbolSupported, accessibleViewIsShown, accessibleViewOnLastLine, accessibleViewSupportsNavigation, accessibleViewVerbosityEnabled } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibilityVerbositySettingId, AccessibilityWorkbenchSettingId, AccessibleViewProviderId, accessibilityHelpIsShown, accessibleViewCurrentProviderId, accessibleViewGoToSymbolSupported, accessibleViewIsShown, accessibleViewOnLastLine, accessibleViewSupportsNavigation, accessibleViewVerbosityEnabled } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; @@ -75,14 +74,14 @@ export const IAccessibleViewService = createDecorator('a export interface IAccessibleViewService { readonly _serviceBrand: undefined; - show(provider: IAccessibleContentProvider): void; + show(provider: IAccessibleContentProvider, position?: Position): void; showLastProvider(id: AccessibleViewProviderId): void; showAccessibleViewHelp(): void; next(): void; previous(): void; goToSymbol(): void; disableHint(): void; - getPosition(): Position | undefined; + getPosition(id: AccessibleViewProviderId): Position | undefined; setPosition(position: Position, reveal?: boolean): void; getLastPosition(): Position | undefined; /** @@ -110,9 +109,11 @@ export interface IAccessibleViewOptions { language?: string; type: AccessibleViewType; /** - * By default, places the cursor on the last line of the accessible view. + * By default, places the cursor on the top line of the accessible view. + * If set to 'initial-bottom', places the cursor on the bottom line of the accessible view and preserves it henceforth. + * If set to 'bottom', places the cursor on the bottom line of the accessible view. */ - positionBottom?: boolean; + position?: 'bottom' | 'initial-bottom'; /** * @returns a string that will be used as the content of the help dialog * instead of the one provided by default. @@ -214,6 +215,9 @@ export class AccessibleView extends Disposable { this._accessibleViewVerbosityEnabled.set(this._configurationService.getValue(this._currentProvider.verbositySettingKey)); this._updateToolbar(this._currentProvider.actions, this._currentProvider.options.type); } + if (e.affectsConfiguration(AccessibilityWorkbenchSettingId.HideAccessibleView)) { + this._container.classList.toggle('hide', this._configurationService.getValue(AccessibilityWorkbenchSettingId.HideAccessibleView)); + } })); this._register(this._editorWidget.onDidDispose(() => this._resetContextKeys())); this._register(this._editorWidget.onDidChangeCursorPosition(() => { @@ -230,6 +234,20 @@ export class AccessibleView extends Disposable { this._accessibleViewCurrentProviderId.reset(); } + getPosition(id?: AccessibleViewProviderId): Position | undefined { + if (!id || !this._lastProvider || this._lastProvider.id !== id) { + return undefined; + } + return this._editorWidget.getPosition() || undefined; + } + + setPosition(position: Position, reveal?: boolean): void { + this._editorWidget.setPosition(position); + if (reveal) { + this._editorWidget.revealPosition(position); + } + } + showLastProvider(id: AccessibleViewProviderId): void { if (!this._lastProvider || this._lastProvider.options.id !== id) { return; @@ -237,7 +255,7 @@ export class AccessibleView extends Disposable { this.show(this._lastProvider); } - show(provider?: IAccessibleContentProvider, symbol?: IAccessibleViewSymbol, showAccessibleViewHelp?: boolean, lineNumber?: number): void { + show(provider?: IAccessibleContentProvider, symbol?: IAccessibleViewSymbol, showAccessibleViewHelp?: boolean, position?: Position): void { provider = provider ?? this._currentProvider; if (!provider) { return; @@ -258,12 +276,12 @@ export class AccessibleView extends Disposable { }; this._contextViewService.showContextView(delegate); - if (lineNumber) { + if (position) { // Context view takes time to show up, so we need to wait for it to show up before we can set the position setTimeout(() => { - this._editorWidget.revealLine(lineNumber); - this._editorWidget.setSelection({ startLineNumber: lineNumber, startColumn: 1, endLineNumber: lineNumber, endColumn: 1 }); - }, 50); + this._editorWidget.revealLine(position.lineNumber); + this._editorWidget.setSelection({ startLineNumber: position.lineNumber, startColumn: position.column, endLineNumber: position.lineNumber, endColumn: position.column }); + }, 10); } if (symbol && this._currentProvider) { @@ -374,7 +392,7 @@ export class AccessibleView extends Disposable { if (lineNumber === undefined) { return; } - this.show(provider, undefined, undefined, lineNumber); + this.show(provider, undefined, undefined, { lineNumber, column: 1 } as Position); this._updateContextKeys(provider, true); } @@ -433,7 +451,7 @@ export class AccessibleView extends Disposable { } } const verbose = this._configurationService.getValue(provider.verbositySettingKey); - const exitThisDialogHint = verbose && !provider.options.positionBottom ? localize('exit', '\n\nExit this dialog (Escape).') : ''; + const exitThisDialogHint = verbose && !provider.options.position ? localize('exit', '\n\nExit this dialog (Escape).') : ''; this._currentContent = message + provider.provideContent() + readMoreLink + disableHelpHint + exitThisDialogHint; this._updateContextKeys(provider, true); @@ -452,7 +470,7 @@ export class AccessibleView extends Disposable { const verbose = this._configurationService.getValue(provider.verbositySettingKey); const hasActions = this._accessibleViewSupportsNavigation.get() || this._accessibleViewVerbosityEnabled.get() || this._accessibleViewGoToSymbolSupported.get() || this._currentProvider?.actions; if (verbose && !showAccessibleViewHelp && hasActions) { - actionsHint = provider.options.positionBottom ? localize('ariaAccessibleViewActionsBottom', 'Explore actions such as disabling this hint (Shift+Tab), use Escape to exit this dialog.') : localize('ariaAccessibleViewActions', 'Explore actions such as disabling this hint (Shift+Tab).'); + actionsHint = provider.options.position ? localize('ariaAccessibleViewActionsBottom', 'Explore actions such as disabling this hint (Shift+Tab), use Escape to exit this dialog.') : localize('ariaAccessibleViewActions', 'Explore actions such as disabling this hint (Shift+Tab).'); } let ariaLabel = provider.options.type === AccessibleViewType.Help ? localize('accessibility-help', "Accessibility Help") : localize('accessible-view', "Accessible View"); this._title.textContent = ariaLabel; @@ -463,12 +481,10 @@ export class AccessibleView extends Disposable { } this._editorWidget.updateOptions({ ariaLabel }); this._editorWidget.focus(); - if (this._currentProvider?.options.positionBottom) { - const currentPosition = this.editorWidget.getPosition(); - const defaultPosition = currentPosition && currentPosition?.lineNumber === 1 && currentPosition?.column === 1; - if (currentPosition && this._configurationService.getValue(TerminalSettingId.AccessibleViewPreserveCursorPosition) && !defaultPosition) { - this._editorWidget.setPosition(currentPosition); - } else { + if (this._currentProvider?.options.position) { + const position = this._editorWidget.getPosition(); + const isDefaultPosition = position?.lineNumber === 1 && position.column === 1; + if (this._currentProvider.options.position === 'bottom' || this._currentProvider.options.position === 'initial-bottom' && isDefaultPosition) { const lastLine = this.editorWidget.getModel()?.getLineCount(); const position = lastLine !== undefined && lastLine > 0 ? new Position(lastLine, 1) : undefined; if (position) { @@ -653,11 +669,11 @@ export class AccessibleViewService extends Disposable implements IAccessibleView super(); } - show(provider: IAccessibleContentProvider): void { + show(provider: IAccessibleContentProvider, position?: Position): void { if (!this._accessibleView) { this._accessibleView = this._register(this._instantiationService.createInstance(AccessibleView)); } - this._accessibleView.show(provider); + this._accessibleView.show(provider, undefined, undefined, position); } showLastProvider(id: AccessibleViewProviderId): void { this._accessibleView?.showLastProvider(id); @@ -690,8 +706,8 @@ export class AccessibleViewService extends Disposable implements IAccessibleView showAccessibleViewHelp(): void { this._accessibleView?.showAccessibleViewHelp(); } - getPosition(): Position | undefined { - return this._accessibleView?.editorWidget.getPosition() ?? undefined; + getPosition(id: AccessibleViewProviderId): Position | undefined { + return this._accessibleView?.getPosition(id) ?? undefined; } getLastPosition(): Position | undefined { const lastLine = this._accessibleView?.editorWidget.getModel()?.getLineCount(); diff --git a/code/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/code/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts index f34f32e9be2..3da9c2eeade 100644 --- a/code/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts +++ b/code/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts @@ -98,15 +98,15 @@ Registry.as(ConfigurationExtensions.Configuration).regis ...audioCueFeatureBase, }, 'audioCues.diffLineInserted': { - 'description': localize('audioCues.diffLineInserted', "Plays a sound when the focus moves to an inserted line in accessible diff viewer mode or to the next/previous change"), + 'description': localize('audioCues.diffLineInserted', "Plays a sound when the focus moves to an inserted line in Accessible Diff Viewer mode or to the next/previous change."), ...audioCueFeatureBase, }, 'audioCues.diffLineDeleted': { - 'description': localize('audioCues.diffLineDeleted', "Plays a sound when the focus moves to a deleted line in accessible diff viewer mode or to the next/previous change"), + 'description': localize('audioCues.diffLineDeleted', "Plays a sound when the focus moves to a deleted line in Accessible Diff Viewer mode or to the next/previous change."), ...audioCueFeatureBase, }, 'audioCues.diffLineModified': { - 'description': localize('audioCues.diffLineModified', "Plays a sound when the focus moves to a modified line in accessible diff viewer mode or to the next/previous change"), + 'description': localize('audioCues.diffLineModified', "Plays a sound when the focus moves to a modified line in Accessible Diff Viewer mode or to the next/previous change."), ...audioCueFeatureBase, }, 'audioCues.notebookCellCompleted': { @@ -131,8 +131,25 @@ Registry.as(ConfigurationExtensions.Configuration).regis 'description': localize('audioCues.chatResponseReceived', "Plays a sound on loop while the response has been received."), ...audioCueFeatureBase, default: 'off' - } - } + }, + 'audioCues.clear': { + 'description': localize('audioCues.clear', "Plays a sound when a feature is cleared (for example, the terminal, debug console, or output channel). When this is disabled, an aria alert will announce 'Cleared'."), + ...audioCueFeatureBase, + default: 'off' + }, + 'audioCues.save': { + 'markdownDescription': localize('audioCues.save', "Plays a sound when a file is saved. Also see {0}", '`#accessibility.alert.save#`'), + 'type': 'string', + 'enum': ['userGesture', 'always', 'never'], + 'default': 'never', + 'enumDescriptions': [ + localize('audioCues.enabled.userGesture', "Plays the audio cue when a user explicitly saves a file."), + localize('audioCues.enabled.always', "Plays the audio cue whenever a file is saved, including auto save."), + localize('audioCues.enabled.never', "Never plays the audio cue.") + ], + tags: ['accessibility'] + }, + }, }); registerAction2(ShowAudioCueHelp); diff --git a/code/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts b/code/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts index ff63502983b..a00a3f22ef9 100644 --- a/code/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts +++ b/code/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts @@ -10,7 +10,7 @@ import { IBulkEditService, ResourceEdit } from 'vs/editor/browser/services/bulkE import { BulkEditPane } from 'vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane'; import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewsRegistry, IViewsService } from 'vs/workbench/common/views'; import { FocusedViewContext } from 'vs/workbench/common/contextkeys'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { RawContextKey, IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -166,8 +166,8 @@ registerAction2(class ApplyAction extends Action2 { constructor() { super({ id: 'refactorPreview.apply', - title: { value: localize('apply', "Apply Refactoring"), original: 'Apply Refactoring' }, - category: { value: localize('cat', "Refactor Preview"), original: 'Refactor Preview' }, + title: localize2('apply', "Apply Refactoring"), + category: localize2('cat', "Refactor Preview"), icon: Codicon.check, precondition: ContextKeyExpr.and(BulkEditPreviewContribution.ctxEnabled, BulkEditPane.ctxHasCheckedChanges), menu: [{ @@ -195,8 +195,8 @@ registerAction2(class DiscardAction extends Action2 { constructor() { super({ id: 'refactorPreview.discard', - title: { value: localize('Discard', "Discard Refactoring"), original: 'Discard Refactoring' }, - category: { value: localize('cat', "Refactor Preview"), original: 'Refactor Preview' }, + title: localize2('Discard', "Discard Refactoring"), + category: localize2('cat', "Refactor Preview"), icon: Codicon.clearAll, precondition: BulkEditPreviewContribution.ctxEnabled, menu: [{ @@ -220,8 +220,8 @@ registerAction2(class ToggleAction extends Action2 { constructor() { super({ id: 'refactorPreview.toggleCheckedState', - title: { value: localize('toogleSelection', "Toggle Change"), original: 'Toggle Change' }, - category: { value: localize('cat', "Refactor Preview"), original: 'Refactor Preview' }, + title: localize2('toogleSelection', "Toggle Change"), + category: localize2('cat', "Refactor Preview"), precondition: BulkEditPreviewContribution.ctxEnabled, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -249,8 +249,8 @@ registerAction2(class GroupByFile extends Action2 { constructor() { super({ id: 'refactorPreview.groupByFile', - title: { value: localize('groupByFile', "Group Changes By File"), original: 'Group Changes By File' }, - category: { value: localize('cat', "Refactor Preview"), original: 'Refactor Preview' }, + title: localize2('groupByFile', "Group Changes By File"), + category: localize2('cat', "Refactor Preview"), icon: Codicon.ungroupByRefType, precondition: ContextKeyExpr.and(BulkEditPane.ctxHasCategories, BulkEditPane.ctxGroupByFile.negate(), BulkEditPreviewContribution.ctxEnabled), menu: [{ @@ -274,8 +274,8 @@ registerAction2(class GroupByType extends Action2 { constructor() { super({ id: 'refactorPreview.groupByType', - title: { value: localize('groupByType', "Group Changes By Type"), original: 'Group Changes By Type' }, - category: { value: localize('cat', "Refactor Preview"), original: 'Refactor Preview' }, + title: localize2('groupByType', "Group Changes By Type"), + category: localize2('cat', "Refactor Preview"), icon: Codicon.groupByRefType, precondition: ContextKeyExpr.and(BulkEditPane.ctxHasCategories, BulkEditPane.ctxGroupByFile, BulkEditPreviewContribution.ctxEnabled), menu: [{ @@ -299,8 +299,8 @@ registerAction2(class ToggleGrouping extends Action2 { constructor() { super({ id: 'refactorPreview.toggleGrouping', - title: { value: localize('groupByType', "Group Changes By Type"), original: 'Group Changes By Type' }, - category: { value: localize('cat', "Refactor Preview"), original: 'Refactor Preview' }, + title: localize2('groupByType', "Group Changes By Type"), + category: localize2('cat', "Refactor Preview"), icon: Codicon.listTree, toggled: BulkEditPane.ctxGroupByFile.negate(), precondition: ContextKeyExpr.and(BulkEditPane.ctxHasCategories, BulkEditPreviewContribution.ctxEnabled), @@ -326,7 +326,7 @@ const refactorPreviewViewIcon = registerIcon('refactor-preview-view-icon', Codic const container = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: BulkEditPane.ID, - title: { value: localize('panel', "Refactor Preview"), original: 'Refactor Preview' }, + title: localize2('panel', "Refactor Preview"), hideIfEmpty: true, ctorDescriptor: new SyncDescriptor( ViewPaneContainer, @@ -338,7 +338,7 @@ const container = Registry.as(ViewContainerExtensions.V Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([{ id: BulkEditPane.ID, - name: localize('panel', "Refactor Preview"), + name: localize2('panel', "Refactor Preview"), when: BulkEditPreviewContribution.ctxEnabled, ctorDescriptor: new SyncDescriptor(BulkEditPane), containerIcon: refactorPreviewViewIcon, diff --git a/code/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts b/code/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts index 753e8b54ffc..ebd4ed9b647 100644 --- a/code/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts +++ b/code/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts @@ -7,6 +7,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize } from 'vs/nls'; +import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; import { Action2, IAction2Options, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -18,7 +19,6 @@ import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatEditorInput } from 'vs/workbench/contrib/chat/browser/chatEditorInput'; import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane'; import { CONTEXT_IN_CHAT_SESSION, CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; export const ACTION_ID_CLEAR_CHAT = `workbench.action.chat.clear`; @@ -118,5 +118,5 @@ export function getClearAction(viewId: string, providerId: string) { } function announceChatCleared(accessor: ServicesAccessor): void { - accessor.get(IAccessibilityService).alertCleared(); + accessor.get(IAccessibleNotificationService).notify(AccessibleNotificationEvent.Clear); } diff --git a/code/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/code/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index 6899e66a113..e96cba918ef 100644 --- a/code/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/code/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -17,8 +17,10 @@ import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { CopyAction } from 'vs/editor/contrib/clipboard/browser/clipboard'; import { localize } from 'vs/nls'; +import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { TerminalLocation } from 'vs/platform/terminal/common/terminal'; import { IUntitledTextResourceEditorInput } from 'vs/workbench/common/editor'; @@ -103,6 +105,8 @@ export function registerChatCodeBlockActions() { const chatService = accessor.get(IChatService); chatService.notifyUserAction({ providerId: context.element.providerId, + agentId: context.element.agent?.id, + sessionId: context.element.sessionId, action: { kind: 'copy', responseId: context.element.providerResponseId, @@ -144,6 +148,8 @@ export function registerChatCodeBlockActions() { const chatService = accessor.get(IChatService); chatService.notifyUserAction({ providerId: context.element.providerId, + agentId: context.element.agent?.id, + sessionId: context.element.sessionId, action: { kind: 'copy', codeBlockIndex: context.codeBlockIndex, @@ -180,7 +186,13 @@ export function registerChatCodeBlockActions() { menu: { id: MenuId.ChatCodeBlock, group: 'navigation', - } + }, + keybinding: { + when: CONTEXT_ACCESSIBILITY_MODE_ENABLED, + primary: KeyMod.CtrlCmd | KeyCode.Enter, + mac: { primary: KeyMod.WinCtrl | KeyCode.Enter }, + weight: KeybindingWeight.WorkbenchContrib + }, }); } @@ -312,6 +324,8 @@ export function registerChatCodeBlockActions() { const chatService = accessor.get(IChatService); chatService.notifyUserAction({ providerId: context.element.providerId, + agentId: context.element.agent?.id, + sessionId: context.element.sessionId, action: { kind: 'insert', responseId: context.element.providerResponseId, @@ -355,6 +369,8 @@ export function registerChatCodeBlockActions() { chatService.notifyUserAction({ providerId: context.element.providerId, + agentId: context.element.agent?.id, + sessionId: context.element.sessionId, action: { kind: 'insert', responseId: context.element.providerResponseId, @@ -383,14 +399,20 @@ export function registerChatCodeBlockActions() { group: 'navigation', isHiddenByDefault: true, }, - keybinding: { + keybinding: [{ primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Enter, mac: { primary: KeyMod.WinCtrl | KeyCode.Enter, }, weight: KeybindingWeight.EditorContrib, - when: CONTEXT_IN_CHAT_SESSION - } + when: ContextKeyExpr.and(CONTEXT_IN_CHAT_SESSION, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), + }, + { + primary: KeyMod.CtrlCmd | KeyCode.Slash, + mac: { primary: KeyMod.WinCtrl | KeyCode.Slash }, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(CONTEXT_IN_CHAT_SESSION, CONTEXT_ACCESSIBILITY_MODE_ENABLED), + }] }); } @@ -425,6 +447,8 @@ export function registerChatCodeBlockActions() { chatService.notifyUserAction({ providerId: context.element.providerId, + agentId: context.element.agent?.id, + sessionId: context.element.sessionId, action: { kind: 'runInTerminal', responseId: context.element.providerResponseId, @@ -474,7 +498,8 @@ export function registerChatCodeBlockActions() { original: 'Next Code Block' }, keybinding: { - primary: KeyCode.F9, + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageDown, + mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageDown, }, weight: KeybindingWeight.WorkbenchContrib, when: CONTEXT_IN_CHAT_SESSION, }, @@ -498,7 +523,8 @@ export function registerChatCodeBlockActions() { original: 'Previous Code Block' }, keybinding: { - primary: KeyMod.Shift | KeyCode.F9, + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageUp, + mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.PageUp, }, weight: KeybindingWeight.WorkbenchContrib, when: CONTEXT_IN_CHAT_SESSION, }, diff --git a/code/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts b/code/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts index 6d7a399b006..b2c5828ece2 100644 --- a/code/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts +++ b/code/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts @@ -54,10 +54,12 @@ export function registerChatTitleActions() { const chatService = accessor.get(IChatService); chatService.notifyUserAction({ providerId: item.providerId, + agentId: item.agent?.id, + sessionId: item.sessionId, action: { kind: 'vote', direction: InteractiveSessionVoteDirection.Up, - responseId: item.providerResponseId + responseId: item.providerResponseId, } }); item.setVote(InteractiveSessionVoteDirection.Up); @@ -94,10 +96,12 @@ export function registerChatTitleActions() { const chatService = accessor.get(IChatService); chatService.notifyUserAction({ providerId: item.providerId, + agentId: item.agent?.id, + sessionId: item.sessionId, action: { kind: 'vote', direction: InteractiveSessionVoteDirection.Down, - responseId: item.providerResponseId + responseId: item.providerResponseId, } }); item.setVote(InteractiveSessionVoteDirection.Down); diff --git a/code/src/vs/workbench/contrib/chat/browser/chat.ts b/code/src/vs/workbench/contrib/chat/browser/chat.ts index 5d3b85c4c7f..0748748b943 100644 --- a/code/src/vs/workbench/contrib/chat/browser/chat.ts +++ b/code/src/vs/workbench/contrib/chat/browser/chat.ts @@ -97,6 +97,8 @@ export interface IChatWidget { getFocus(): ChatTreeItem | undefined; updateInput(query?: string): void; acceptInput(query?: string): void; + setInputPlaceholder(placeholder: string): void; + resetInputPlaceholder(): void; focusLastMessage(): void; focusInput(): void; hasInputFocus(): boolean; diff --git a/code/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts b/code/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts index a16591501b5..c7e44722c3c 100644 --- a/code/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts +++ b/code/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts @@ -6,7 +6,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import * as resources from 'vs/base/common/resources'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; @@ -106,12 +106,12 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { private registerViewContainer(): ViewContainer { // Register View Container - const title = localize('chat.viewContainer.label', "Chat"); + const title = localize2('chat.viewContainer.label', "Chat"); const icon = Codicon.commentDiscussion; const viewContainerId = CHAT_SIDEBAR_PANEL_ID; const viewContainer: ViewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ id: viewContainerId, - title: { value: title, original: 'Chat' }, + title, icon, ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [viewContainerId, { mergeViewWithContainerWhenSingleView: true }]), storageId: viewContainerId, @@ -129,7 +129,7 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { id: viewId, containerIcon: this._viewContainer.icon, containerTitle: this._viewContainer.title.value, - name: providerDescriptor.label, + name: { value: providerDescriptor.label, original: providerDescriptor.label }, canToggleVisibility: false, canMoveView: true, ctorDescriptor: new SyncDescriptor(ChatViewPane, [{ providerId: providerDescriptor.id }]), diff --git a/code/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/code/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 517a7e1faea..55116ec3aa5 100644 --- a/code/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/code/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -156,7 +156,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge return this._inputEditor.hasWidgetFocus(); } - async acceptInput(query?: string | IChatReplyFollowup): Promise { + async acceptInput(query?: string): Promise { const editorValue = this._inputEditor.getValue(); if (!query && editorValue) { // Followups and programmatic messages don't go to history diff --git a/code/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/code/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 61a42ed28fc..2cb8c08dace 100644 --- a/code/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/code/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -55,6 +55,7 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { WorkbenchCompressibleAsyncDataTree, WorkbenchList } from 'vs/platform/list/browser/listService'; import { ILogService } from 'vs/platform/log/common/log'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IProductService } from 'vs/platform/product/common/productService'; import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels'; @@ -63,11 +64,11 @@ import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/brows import { IChatCodeBlockActionContext } from 'vs/workbench/contrib/chat/browser/actions/chatCodeblockActions'; import { ChatTreeItem, IChatCodeBlockInfo, IChatFileTreeInfo } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups'; -import { convertParsedRequestToMarkdown, walkTreeAndAnnotateResourceLinks } from 'vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer'; +import { convertParsedRequestToMarkdown, reduceInlineContentReferences, walkTreeAndAnnotateReferenceLinks } from 'vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer'; import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; import { CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_HAS_PROVIDER_ID, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IPlaceholderMarkdownString } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChatReplyFollowup, IChatResponseProgressFileTreeData, IChatService, IDocumentContext, ISlashCommand, IUsedContext, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatContentReference, IChatReplyFollowup, IChatResponseProgressFileTreeData, IChatService, ISlashCommand, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatResponseMarkdownRenderData, IChatResponseRenderData, IChatResponseViewModel, IChatWelcomeMessageViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { IWordCountResult, getNWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; import { MenuPreventer } from 'vs/workbench/contrib/codeEditor/browser/menuPreventer'; @@ -85,6 +86,7 @@ interface IChatListItemTemplate { avatar: HTMLElement; username: HTMLElement; value: HTMLElement; + referencesListContainer: HTMLElement; contextKeyService: IContextKeyService; templateDisposables: IDisposable; elementDisposables: DisposableStore; @@ -125,30 +127,40 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer()); + private _usedReferencesEnabled = false; + constructor( private readonly editorOptions: ChatEditorOptions, private readonly rendererOptions: IChatListItemRendererOptions, private readonly delegate: IChatRendererDelegate, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IConfigurationService private readonly configService: IConfigurationService, + @IConfigurationService configService: IConfigurationService, @ILogService private readonly logService: ILogService, @ICommandService private readonly commandService: ICommandService, @IOpenerService private readonly openerService: IOpenerService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IChatService private readonly chatService: IChatService, @IEditorService private readonly editorService: IEditorService, + @IProductService productService: IProductService, ) { super(); this.renderer = this.instantiationService.createInstance(MarkdownRenderer, {}); this._editorPool = this._register(this.instantiationService.createInstance(EditorPool, this.editorOptions)); this._treePool = this._register(this.instantiationService.createInstance(TreePool, this._onDidChangeVisibility.event)); - this._usedContextListPool = this._register(this.instantiationService.createInstance(UsedContextListPool, this._onDidChangeVisibility.event)); + this._contentReferencesListPool = this._register(this.instantiationService.createInstance(ContentReferencesListPool, this._onDidChangeVisibility.event)); + + this._usedReferencesEnabled = configService.getValue('chat.experimental.usedReferences') ?? productService.quality !== 'stable'; + this._register(configService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('chat.experimental.usedReferences')) { + this._usedReferencesEnabled = configService.getValue('chat.experimental.usedReferences') ?? productService.quality !== 'stable'; + } + })); } get templateId(): string { @@ -163,26 +175,19 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { this.chatService.notifyUserAction({ providerId: element.providerId, + agentId: element.agent?.id, + sessionId: element.sessionId, action: { kind: 'command', - command: followup + command: followup, } }); return this.commandService.executeCommand(followup.commandId, ...(followup.args ?? [])); @@ -385,6 +393,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { + renderableResponse.forEach((part, index) => { const renderedPart = renderedParts[index]; // Is this part completely new? if (!renderedPart) { @@ -488,8 +497,9 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { if (!partToRender) { @@ -504,7 +514,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer void } { + private renderContentReferencesIfNeeded(element: ChatTreeItem, templateData: IChatListItemTemplate, disposables: DisposableStore): void { + dom.clearNode(templateData.referencesListContainer); + if (isResponseVM(element) && this._usedReferencesEnabled && element.response.contentReferences.length) { + dom.show(templateData.referencesListContainer); + const contentReferencesListResult = this.renderContentReferencesListData(element.response.contentReferences, element, templateData); + templateData.referencesListContainer.appendChild(contentReferencesListResult.element); + disposables.add(contentReferencesListResult); + } else { + dom.hide(templateData.referencesListContainer); + } + } + + private renderContentReferencesListData(data: ReadonlyArray, element: IChatResponseViewModel, templateData: IChatListItemTemplate): { element: HTMLElement; dispose: () => void } { const listDisposables = new DisposableStore(); - const referencesLabel = data.documents.length > 1 ? - localize('usedReferencesPlural', "Used {0} references", data.documents.length) : + const referencesLabel = data.length > 1 ? + localize('usedReferencesPlural', "Used {0} references", data.length) : localize('usedReferencesSingular', "Used {0} reference", 1); const iconElement = $('.chat-used-context-icon'); const icon = (element: IChatResponseViewModel) => element.usedReferencesExpanded ? Codicon.chevronDown : Codicon.chevronRight; @@ -626,18 +648,18 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { if (e.element) { this.editorService.openEditor({ - resource: e.element.uri, + resource: 'uri' in e.element.reference ? e.element.reference.uri : e.element.reference, options: { ...e.editorOptions, ...{ - selection: e.element.ranges[0] + selection: 'range' in e.element.reference ? e.element.reference.range : undefined } } }); @@ -648,11 +670,8 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { - this._onDidChangeItemHeight.fire({ element, height: templateData.rowContainer.offsetHeight }); - }); + list.layout(data.length * 22); + list.splice(0, list.length, data); return { element: container, @@ -727,9 +746,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer this.codeBlocksByResponseId.delete(element.id))); } - if (isRequestVM(element)) { - walkTreeAndAnnotateResourceLinks(result.element); - } + walkTreeAndAnnotateReferenceLinks(result.element); if (usedSlashCommand) { const slashCommandElement = $('span.interactive-slash-command', { title: usedSlashCommand.detail }, `/${usedSlashCommand.command} `); @@ -1213,41 +1230,40 @@ class TreePool extends Disposable { } } -class UsedContextListPool extends Disposable { - private _pool: ResourcePool>; +class ContentReferencesListPool extends Disposable { + private _pool: ResourcePool>; - public get inUse(): ReadonlySet> { + public get inUse(): ReadonlySet> { return this._pool.inUse; } constructor( private _onDidChangeVisibility: Event, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IConfigurationService private readonly configService: IConfigurationService, @IThemeService private readonly themeService: IThemeService, ) { super(); this._pool = this._register(new ResourcePool(() => this.listFactory())); } - private listFactory(): WorkbenchList { + private listFactory(): WorkbenchList { const resourceLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this._onDidChangeVisibility }); const container = $('.chat-used-context-list'); createFileIconThemableTreeContainerScope(container, this.themeService); - const list = >this.instantiationService.createInstance( + const list = >this.instantiationService.createInstance( WorkbenchList, 'ChatListRenderer', container, - new UsedContextListDelegate(), - [new UsedContextListRenderer(resourceLabels, this.configService.getValue('explorer.decorations'))], + new ContentReferencesListDelegate(), + [new ContentReferencesListRenderer(resourceLabels)], {}); return list; } - get(): IDisposableReference> { + get(): IDisposableReference> { const object = this._pool.get(); let stale = false; return { @@ -1261,42 +1277,43 @@ class UsedContextListPool extends Disposable { } } -class UsedContextListDelegate implements IListVirtualDelegate { - getHeight(element: IDocumentContext): number { +class ContentReferencesListDelegate implements IListVirtualDelegate { + getHeight(element: IChatContentReference): number { return 22; } - getTemplateId(element: IDocumentContext): string { - return UsedContextListRenderer.TEMPLATE_ID; + getTemplateId(element: IChatContentReference): string { + return ContentReferencesListRenderer.TEMPLATE_ID; } } -interface IUsedContextListTemplate { +interface IChatContentReferenceListTemplate { label: IResourceLabel; templateDisposables: IDisposable; } -class UsedContextListRenderer implements IListRenderer { - static TEMPLATE_ID = 'usedContextListRenderer'; - readonly templateId: string = UsedContextListRenderer.TEMPLATE_ID; +class ContentReferencesListRenderer implements IListRenderer { + static TEMPLATE_ID = 'contentReferencesListRenderer'; + readonly templateId: string = ContentReferencesListRenderer.TEMPLATE_ID; - constructor(private labels: ResourceLabels, private decorations: IFilesConfiguration['explorer']['decorations']) { } + constructor(private labels: ResourceLabels) { } - renderTemplate(container: HTMLElement): IUsedContextListTemplate { + renderTemplate(container: HTMLElement): IChatContentReferenceListTemplate { const templateDisposables = new DisposableStore(); const label = templateDisposables.add(this.labels.create(container, { supportHighlights: true })); return { templateDisposables, label }; } - renderElement(element: IDocumentContext, index: number, templateData: IUsedContextListTemplate, height: number | undefined): void { + renderElement(element: IChatContentReference, index: number, templateData: IChatContentReferenceListTemplate, height: number | undefined): void { templateData.label.element.style.display = 'flex'; - templateData.label.setFile(element.uri, { + templateData.label.setFile('uri' in element.reference ? element.reference.uri : element.reference, { fileKind: FileKind.FILE, - fileDecorations: this.decorations, + // Should not have this live-updating data on a historical reference + fileDecorations: { badges: false, colors: false }, }); } - disposeTemplate(templateData: IUsedContextListTemplate): void { + disposeTemplate(templateData: IChatContentReferenceListTemplate): void { templateData.templateDisposables.dispose(); } } diff --git a/code/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts b/code/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts index e6f7b341ae7..8d078873894 100644 --- a/code/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts +++ b/code/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts @@ -4,9 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import { IParsedChatRequest, ChatRequestTextPart } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { IMarkdownString, MarkdownString, isMarkdownString } from 'vs/base/common/htmlContent'; +import { revive } from 'vs/base/common/marshalling'; +import { basename } from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; +import { Location } from 'vs/editor/common/languages'; +import { ChatRequestTextPart, IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { IChatContentInlineReference, IChatResponseProgressFileTreeData } from 'vs/workbench/contrib/chat/common/chatService'; -const variableRefUrl = 'http://_vscodeDecoration_'; +const variableRefUrl = 'http://_vscodedecoration_'; export function convertParsedRequestToMarkdown(parsedRequest: IParsedChatRequest): string { let result = ''; @@ -21,7 +27,7 @@ export function convertParsedRequestToMarkdown(parsedRequest: IParsedChatRequest return result; } -export function walkTreeAndAnnotateResourceLinks(element: HTMLElement): void { +export function walkTreeAndAnnotateReferenceLinks(element: HTMLElement): void { element.querySelectorAll('a').forEach(a => { const href = a.getAttribute('data-href'); if (href) { @@ -29,6 +35,8 @@ export function walkTreeAndAnnotateResourceLinks(element: HTMLElement): void { a.parentElement!.replaceChild( renderResourceWidget(a.textContent!), a); + } else if (href.startsWith(contentRefUrl)) { + renderFileWidget(href, a); } } }); @@ -40,3 +48,36 @@ function renderResourceWidget(name: string): HTMLElement { container.appendChild(alias); return container; } + +function renderFileWidget(href: string, a: HTMLAnchorElement): void { + // TODO this can be a nicer FileLabel widget with an icon. Do a simple link for now. + const fullUri = URI.parse(href); + const location: Location | { uri: URI; range: undefined } = revive(JSON.parse(fullUri.fragment)); + const fragment = location.range ? `${location.range.startLineNumber}-${location.range.endLineNumber}` : ''; + a.setAttribute('data-href', location.uri.with({ fragment }).toString()); +} + +const contentRefUrl = 'http://_vscodecontentref_'; // must be lowercase for URI + +export function reduceInlineContentReferences(response: ReadonlyArray): ReadonlyArray { + const result: (IMarkdownString | IChatResponseProgressFileTreeData)[] = []; + for (const item of response) { + const previousItem = result[result.length - 1]; + if ('inlineReference' in item) { + const location = 'uri' in item.inlineReference ? item.inlineReference : { uri: item.inlineReference }; + const printUri = URI.parse(contentRefUrl).with({ fragment: JSON.stringify(location) }); + const markdownText = `[${item.name || basename(location.uri)}](${printUri.toString()})`; + if (isMarkdownString(previousItem)) { + result[result.length - 1] = new MarkdownString(previousItem.value + markdownText, { isTrusted: previousItem.isTrusted }); + } else { + result.push(new MarkdownString(markdownText)); + } + } else if (isMarkdownString(item) && isMarkdownString(previousItem)) { + result[result.length - 1] = new MarkdownString(previousItem.value + item.value, { isTrusted: previousItem.isTrusted }); + } else { + result.push(item); + } + } + + return result; +} diff --git a/code/src/vs/workbench/contrib/chat/browser/chatVariables.ts b/code/src/vs/workbench/contrib/chat/browser/chatVariables.ts index 09e53ac0b3a..90ed4ac45b3 100644 --- a/code/src/vs/workbench/contrib/chat/browser/chatVariables.ts +++ b/code/src/vs/workbench/contrib/chat/browser/chatVariables.ts @@ -43,16 +43,16 @@ export class ChatVariablesService implements IChatVariablesService { resolvedVariables[part.variableName] = value; parsedPrompt[i] = `[${part.text}](values:${part.variableName})`; } else { - parsedPrompt[i] = part.text; + parsedPrompt[i] = part.promptText; } }).catch(onUnexpectedExternalError)); } } else if (part instanceof ChatRequestDynamicReferencePart) { // Maybe the dynamic reference should include a full IChatRequestVariableValue[] at the time it is inserted? resolvedVariables[part.referenceText] = [{ level: 'full', value: part.data.toString() }]; - parsedPrompt[i] = `[${part.text}](values:${part.referenceText})`; + parsedPrompt[i] = part.promptText; } else { - parsedPrompt[i] = part.text; + parsedPrompt[i] = part.promptText; } }); @@ -60,7 +60,7 @@ export class ChatVariablesService implements IChatVariablesService { return { variables: resolvedVariables, - prompt: parsedPrompt.join('') + prompt: parsedPrompt.join('').trim() }; } diff --git a/code/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/code/src/vs/workbench/contrib/chat/browser/chatWidget.ts index f6fb89ac62a..3df91a83aa5 100644 --- a/code/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/code/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -27,7 +27,7 @@ import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane'; import { CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; -import { IChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; +import { ChatModelInitState, IChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatReplyFollowup, IChatService, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService'; import { ChatViewModel, IChatResponseViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; @@ -247,12 +247,14 @@ export class ChatWidget extends Disposable implements IChatWidget { getId: (element) => { return ((isResponseVM(element) || isRequestVM(element)) ? element.dataId : element.id) + // TODO? We can give the welcome message a proper VM or get rid of the rest of the VMs - ((isWelcomeVM(element) && !this.viewModel?.isInitialized) ? '_initializing' : '') + + ((isWelcomeVM(element) && this.viewModel) ? `_${ChatModelInitState[this.viewModel.initState]}` : '') + // Ensure re-rendering an element once slash commands are loaded, so the colorization can be applied. `${(isRequestVM(element) || isWelcomeVM(element)) && !!this.lastSlashCommands ? '_scLoaded' : ''}` + // If a response is in the process of progressive rendering, we need to ensure that it will // be re-rendered so progressive rendering is restarted, even if the model wasn't updated. - `${isResponseVM(element) && element.renderData ? `_${this.visibleChangeCount}` : ''}`; + `${isResponseVM(element) && element.renderData ? `_${this.visibleChangeCount}` : ''}` + + // Re-render once content references are loaded + (isResponseVM(element) ? `_${element.response.contentReferences.length}` : ''); }, } }); @@ -264,6 +266,8 @@ export class ChatWidget extends Disposable implements IChatWidget { const lastItem = treeItems[treeItems.length - 1]?.element; if (lastItem && isResponseVM(lastItem) && lastItem.isComplete) { this.renderFollowups(lastItem.replyFollowups); + } else if (lastItem && isWelcomeVM(lastItem)) { + this.renderFollowups(lastItem.sampleQuestions); } else { this.renderFollowups(undefined); } @@ -323,7 +327,8 @@ export class ChatWidget extends Disposable implements IChatWidget { rendererDelegate )); this._register(this.renderer.onDidClickFollowup(item => { - this.acceptInput(item); + // is this used anymore? + this.acceptInput(item.message); })); this.tree = >scopedInstantiationService.createInstance( @@ -406,7 +411,10 @@ export class ChatWidget extends Disposable implements IChatWidget { this.inputPart.render(container, '', this); this._register(this.inputPart.onDidFocus(() => this._onDidFocus.fire())); - this._register(this.inputPart.onDidAcceptFollowup(followup => this.acceptInput(followup))); + this._register(this.inputPart.onDidAcceptFollowup(followup => { + // this.chatService.notifyUserAction + this.acceptInput(followup.message); + })); this._register(this.inputPart.onDidChangeHeight(() => this.bodyDimension && this.layout(this.bodyDimension.height, this.bodyDimension.width))); } @@ -463,18 +471,26 @@ export class ChatWidget extends Disposable implements IChatWidget { this.tree.domFocus(); } + setInputPlaceholder(placeholder: string): void { + this.viewModel?.setInputPlaceholder(placeholder); + } + + resetInputPlaceholder(): void { + this.viewModel?.resetInputPlaceholder(); + } + updateInput(value = ''): void { this.inputPart.setValue(value); } - async acceptInput(query?: string | IChatReplyFollowup): Promise { + async acceptInput(query?: string): Promise { if (this.viewModel) { this._onDidAcceptInput.fire(); const editorValue = this.inputPart.inputEditor.getValue(); this._chatAccessibilityService.acceptRequest(); const input = query ?? editorValue; - const usedSlashCommand = this.lookupSlashCommand(typeof input === 'string' ? input : input.message); + const usedSlashCommand = this.lookupSlashCommand(input); const result = await this.chatService.sendRequest(this.viewModel.sessionId, input, usedSlashCommand); if (result) { diff --git a/code/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/code/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index 44ebbee7bf3..cb000e55f38 100644 --- a/code/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/code/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -3,8 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { raceCancellation } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; @@ -16,6 +17,7 @@ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeat import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IProductService } from 'vs/platform/product/common/productService'; import { Registry } from 'vs/platform/registry/common/platform'; import { inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -41,10 +43,12 @@ const variableTextDecorationType = 'chat-variable-text'; class InputEditorDecorations extends Disposable { - private _previouslyUsedSlashCommands = new Set(); - public readonly id = 'inputEditorDecorations'; + private readonly previouslyUsedSlashCommands = new Set(); + + private readonly viewModelDisposables = this._register(new MutableDisposable()); + constructor( private readonly widget: IChatWidget, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -62,14 +66,25 @@ class InputEditorDecorations extends Disposable { this.updateInputEditorDecorations(); this._register(this.widget.inputEditor.onDidChangeModelContent(() => this.updateInputEditorDecorations())); this._register(this.widget.onDidChangeViewModel(() => { - this._previouslyUsedSlashCommands.clear(); + this.registerViewModelListeners(); + this.previouslyUsedSlashCommands.clear(); this.updateInputEditorDecorations(); })); this._register(this.chatService.onDidSubmitSlashCommand((e) => { - if (e.sessionId === this.widget.viewModel?.sessionId && !this._previouslyUsedSlashCommands.has(e.slashCommand)) { - this._previouslyUsedSlashCommands.add(e.slashCommand); + if (e.sessionId === this.widget.viewModel?.sessionId && !this.previouslyUsedSlashCommands.has(e.slashCommand)) { + this.previouslyUsedSlashCommands.add(e.slashCommand); } })); + + this.registerViewModelListeners(); + } + + private registerViewModelListeners(): void { + this.viewModelDisposables.value = this.widget.viewModel?.onDidChange(e => { + if (e?.kind === 'changePlaceholder') { + this.updateInputEditorDecorations(); + } + }); } private updateRegisteredDecorationTypes() { @@ -112,11 +127,11 @@ class InputEditorDecorations extends Disposable { } if (!inputValue) { - const extensionPlaceholder = this.widget.viewModel?.inputPlaceholder; + const viewModelPlaceholder = this.widget.viewModel?.inputPlaceholder; const defaultPlaceholder = slashCommands?.length ? localize('interactive.input.placeholderWithCommands', "Ask a question or type '@' or '/'") : localize('interactive.input.placeholderNoCommands', "Ask a question"); - const placeholder = extensionPlaceholder ?? defaultPlaceholder; + const placeholder = viewModelPlaceholder ?? defaultPlaceholder; const decoration: IDecorationOptions[] = [ { range: { @@ -168,7 +183,7 @@ class InputEditorDecorations extends Disposable { const onlySlashCommandAndWhitespace = slashCommandPart && parsedRequest.every(p => p instanceof ChatRequestTextPart && !p.text.trim().length || p instanceof ChatRequestSlashCommandPart); if (onlySlashCommandAndWhitespace) { // Command reference with no other text - show the placeholder - const isFollowupSlashCommand = this._previouslyUsedSlashCommands.has(slashCommandPart.slashCommand.command); + const isFollowupSlashCommand = this.previouslyUsedSlashCommands.has(slashCommandPart.slashCommand.command); const shouldRenderFollowupPlaceholder = isFollowupSlashCommand && slashCommandPart.slashCommand.followupPlaceholder; if (shouldRenderFollowupPlaceholder || slashCommandPart.slashCommand.detail) { placeholderDecoration = [{ @@ -327,7 +342,8 @@ class AgentCompletions extends Disposable { return null; } - const agents = this.chatAgentService.getAgents(); + const agents = this.chatAgentService.getAgents() + .filter(a => !a.metadata.isDefault); return { suggestions: agents.map((c, i) => { const withAt = `@${c.id}`; @@ -346,12 +362,17 @@ class AgentCompletions extends Disposable { this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { _debugDisplayName: 'chatAgentSubcommand', triggerCharacters: ['/'], - provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, _token: CancellationToken) => { + provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken) => { const widget = this.chatWidgetService.getWidgetByInputUri(model.uri); if (!widget || !widget.viewModel) { return; } + const range = computeCompletionRanges(model, position, /\/\w*/g); + if (!range) { + return null; + } + const parsedRequest = (await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(widget.viewModel.sessionId, model.getValue())).parts; const usedAgent = parsedRequest.find((p): p is ChatRequestAgentPart => p instanceof ChatRequestAgentPart); if (!usedAgent) { @@ -364,14 +385,16 @@ class AgentCompletions extends Disposable { return; } + const commands = await usedAgent.agent.provideSlashCommands(token); + return { - suggestions: usedAgent.agent.metadata.subCommands.map((c, i) => { + suggestions: commands.map((c, i) => { const withSlash = `/${c.name}`; return { label: withSlash, insertText: `${withSlash} `, detail: c.description, - range: new Range(1, position.column - 1, 1, position.column - 1), + range, kind: CompletionItemKind.Text, // The icons are disabled here anyway }; }) @@ -383,7 +406,7 @@ class AgentCompletions extends Disposable { this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { _debugDisplayName: 'chatAgentAndSubcommand', triggerCharacters: ['/'], - provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, _token: CancellationToken) => { + provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken) => { const widget = this.chatWidgetService.getWidgetByInputUri(model.uri); if (!widget) { return; @@ -395,13 +418,21 @@ class AgentCompletions extends Disposable { } const agents = this.chatAgentService.getAgents(); + const all = agents.map(agent => agent.provideSlashCommands(token)); + const commands = await raceCancellation(Promise.all(all), token); + + if (!commands) { + return; + } + return { - suggestions: agents.flatMap(a => a.metadata.subCommands.map((c, i) => { + suggestions: agents.flatMap((agent, i) => commands[i].map((c, i) => { + const agentLabel = `@${agent.id}`; const withSlash = `/${c.name}`; return { - label: withSlash, - insertText: `@${a.id} ${withSlash} `, - detail: `(@${a.id}) ${c.description}`, + label: { label: withSlash, description: agentLabel }, + insertText: `${agentLabel} ${withSlash} `, + detail: `(${agentLabel}) ${c.description}`, range: new Range(1, 1, 1, 1), kind: CompletionItemKind.Text, // The icons are disabled here anyway }; @@ -419,6 +450,7 @@ class BuiltinDynamicCompletions extends Disposable { @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, @IConfigurationService private readonly configurationService: IConfigurationService, + @IProductService private readonly productService: IProductService, ) { super(); @@ -426,7 +458,7 @@ class BuiltinDynamicCompletions extends Disposable { _debugDisplayName: 'chatDynamicCompletions', triggerCharacters: ['$'], provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, _token: CancellationToken) => { - const fileVariablesEnabled = this.configurationService.getValue('chat.experimental.fileVariables'); + const fileVariablesEnabled = this.configurationService.getValue('chat.experimental.fileVariables') ?? this.productService.quality !== 'stable'; if (!fileVariablesEnabled) { return; } diff --git a/code/src/vs/workbench/contrib/chat/browser/media/chat.css b/code/src/vs/workbench/contrib/chat/browser/media/chat.css index 7052198e920..727dcef57d6 100644 --- a/code/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/code/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -14,7 +14,7 @@ } .interactive-item-container { - padding: 16px 20px 0px 20px; + padding: 16px 20px 16px 20px; display: flex; flex-direction: column; gap: 6px; @@ -110,6 +110,7 @@ } .interactive-item-container .value .rendered-markdown a, +.interactive-item-container .value .interactive-session-followups, .interactive-item-container .value .rendered-markdown a code { color: var(--vscode-textLink-foreground); } @@ -130,18 +131,11 @@ .interactive-item-container .value { white-space: normal; - min-height: 36px; word-wrap: break-word; } -.interactive-item-container .value > :last-child:not(.rendered-markdown) { - /* The container has padding on all sides except the bottom. The last element needs to provide this margin. rendered-markdown has its own margin. - TODO Another approach could be removing the margin on the very last element inside the markdown container? */ - margin-bottom: 16px; -} - -.interactive-item-container .value > .interactive-response-error-details:not(:last-child) { - margin-bottom: 8px; +.interactive-item-container .value > :last-child.rendered-markdown > :last-child { + margin-bottom: 0px; } .interactive-item-container .value .rendered-markdown h1 { @@ -165,11 +159,11 @@ .interactive-item-container .value .rendered-markdown p { margin: 0 0 16px 0; - line-height: 1.6em; + line-height: 1.5em; } .interactive-item-container .value .rendered-markdown li { - line-height: 1.5rem; + line-height: 1.3rem; } .interactive-item-container .monaco-tokenized-source, @@ -329,6 +323,10 @@ gap: 6px; } +.interactive-response .interactive-response-error-details .rendered-markdown :last-child { + margin-bottom: 0px; +} + .interactive-response .interactive-response-error-details .codicon { margin-top: 1px; } @@ -466,7 +464,7 @@ .interactive-response-progress-tree .monaco-list, .chat-used-context-list .monaco-list { - border: 1px solid var(--vscode-input-border, transparent); + border: none; border-radius: 4px; width: auto; } @@ -483,18 +481,41 @@ display: none; } +.interactive-session .chat-used-context-list { + border: 1px solid var(--vscode-chat-requestBorder); + border-radius: 3px; + padding: 4px; +} + +.interactive-session .chat-used-context-list .monaco-list .monaco-list-row { + border-radius: 2px; +} + .interactive-session .chat-used-context-label { - font-size: 0.9em; + font-size: 12px; + color: var(--vscode-foreground); + opacity: 0.8; +} + +.interactive-session .chat-used-context-label:hover { + opacity: unset; } .interactive-session .chat-used-context-label .monaco-button { /* unset Button styles */ display: inline-flex; - width: initial; + width: 100%; border: none; padding: 0; text-align: initial; - padding-left: 4px; justify-content: initial; - margin-bottom: 3px; + margin-bottom: 6px; +} + +.interactive-session .chat-used-context-label .monaco-text-button { + outline-offset: unset !important; +} + +.interactive-session .chat-used-context .chat-used-context-label .monaco-button .codicon { + margin: 0 2px 0 0; } diff --git a/code/src/vs/workbench/contrib/chat/common/chatAgents.ts b/code/src/vs/workbench/contrib/chat/common/chatAgents.ts index dcf7c2d4f9e..886c5aa60a7 100644 --- a/code/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/code/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -4,68 +4,24 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from 'vs/base/common/cancellation'; -import { Event, Emitter } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { Iterable } from 'vs/base/common/iterator'; -import { IJSONSchema } from 'vs/base/common/jsonSchema'; -import { Disposable, DisposableStore, IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import { localize } from 'vs/nls'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IProgress } from 'vs/platform/progress/common/progress'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; -import { IChatFollowup, IChatResponseProgressFileTreeData } from 'vs/workbench/contrib/chat/common/chatService'; -import { IExtensionService, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; -import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; - -//#region extension point - -const agentItem: IJSONSchema = { - type: 'object', - required: ['agent', 'detail'], - properties: { - agent: { - type: 'string', - markdownDescription: localize('agent', "The name of the agent which will be used as prefix.") - }, - detail: { - type: 'string', - markdownDescription: localize('details', "The details of the agent.") - }, - } -}; - -const agentItems: IJSONSchema = { - description: localize('vscode.extension.contributes.slashes', "Contributes agents to chat"), - oneOf: [ - agentItem, - { - type: 'array', - items: agentItem - } - ] -}; - -export const agentsExtPoint = ExtensionsRegistry.registerExtensionPoint({ - extensionPoint: 'agents', - jsonSchema: agentItems -}); +import { IChatFollowup, IChatProgress, IChatResponseErrorDetails, IChatResponseProgressFileTreeData } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; //#region agent service, commands etc -export interface IChatAgentData { +export interface IChatAgent { id: string; metadata: IChatAgentMetadata; -} - -function isAgentData(data: any): data is IChatAgentData { - return typeof data === 'object' && data && - typeof data.id === 'string' && - typeof data.detail === 'string'; - // (typeof data.sortText === 'undefined' || typeof data.sortText === 'string') && - // (typeof data.executeImmediately === 'undefined' || typeof data.executeImmediately === 'boolean'); + invoke(request: IChatAgentRequest, progress: IProgress, history: IChatMessage[], token: CancellationToken): Promise; + provideFollowups?(sessionId: string, token: CancellationToken): Promise; + provideSlashCommands(token: CancellationToken): Promise; } export interface IChatAgentFragment { @@ -78,138 +34,122 @@ export interface IChatAgentCommand { } export interface IChatAgentMetadata { - description: string; - subCommands: IChatAgentCommand[]; + description?: string; + // subCommands: IChatAgentCommand[]; requireCommand?: boolean; // Do some agents not have a default action? - isImplicit?: boolean; // Only @workspace. slash commands get promoted to the top-level and this agent is invoked when those are used + isDefault?: boolean; // The agent invoked when no agent is specified fullName?: string; icon?: URI; } -export type IChatAgentCallback = { (prompt: string, progress: IProgress, history: IChatMessage[], token: CancellationToken): Promise<{ followUp: IChatFollowup[] } | void> }; +export interface IChatAgentRequest { + sessionId: string; + requestId: string; + command?: string; + message: string; + variables: Record; +} + +export interface IChatAgentResult { + // delete, keep while people are still using the previous API + followUp?: IChatFollowup[]; + errorDetails?: IChatResponseErrorDetails; + timings?: { + firstProgress: number; + totalElapsed: number; + }; +} export const IChatAgentService = createDecorator('chatAgentService'); export interface IChatAgentService { _serviceBrand: undefined; readonly onDidChangeAgents: Event; - registerAgentData(data: IChatAgentData): IDisposable; - registerAgentCallback(id: string, callback: IChatAgentCallback): IDisposable; - registerAgent(data: IChatAgentData, callback: IChatAgentCallback): IDisposable; - invokeAgent(id: string, prompt: string, progress: IProgress, history: IChatMessage[], token: CancellationToken): Promise<{ followUp: IChatFollowup[] } | void>; - getAgents(): Array; - getAgent(id: string): IChatAgentData | undefined; + registerAgent(agent: IChatAgent): IDisposable; + invokeAgent(id: string, request: IChatAgentRequest, progress: IProgress, history: IChatMessage[], token: CancellationToken): Promise; + getFollowups(id: string, sessionId: string, token: CancellationToken): Promise; + getAgents(): Array; + getAgent(id: string): IChatAgent | undefined; + getDefaultAgent(): IChatAgent | undefined; hasAgent(id: string): boolean; + updateAgent(id: string, updateMetadata: IChatAgentMetadata): void; } -type Tuple = { data: IChatAgentData; callback?: IChatAgentCallback }; - export class ChatAgentService extends Disposable implements IChatAgentService { public static readonly AGENT_LEADER = '@'; declare _serviceBrand: undefined; - private readonly _agents = new Map(); + private readonly _agents = new Map(); private readonly _onDidChangeAgents = this._register(new Emitter()); readonly onDidChangeAgents: Event = this._onDidChangeAgents.event; - constructor(@IExtensionService private readonly _extensionService: IExtensionService) { - super(); - } - override dispose(): void { super.dispose(); this._agents.clear(); } - registerAgentData(data: IChatAgentData): IDisposable { - if (this._agents.has(data.id)) { - throw new Error(`Already registered an agent with id ${data.id}}`); + registerAgent(agent: IChatAgent): IDisposable { + if (this._agents.has(agent.id)) { + throw new Error(`Already registered an agent with id ${agent.id}`); } - this._agents.set(data.id, { data }); + this._agents.set(agent.id, { agent }); this._onDidChangeAgents.fire(); return toDisposable(() => { - if (this._agents.delete(data.id)) { + if (this._agents.delete(agent.id)) { this._onDidChangeAgents.fire(); } }); } - registerAgentCallback(id: string, agentCallback: IChatAgentCallback): IDisposable { + updateAgent(id: string, updateMetadata: IChatAgentMetadata): void { const data = this._agents.get(id); if (!data) { throw new Error(`No agent with id ${id} registered`); } - data.callback = agentCallback; - return toDisposable(() => data.callback = undefined); + data.agent.metadata = { ...data.agent.metadata, ...updateMetadata }; + this._onDidChangeAgents.fire(); } - registerAgent(data: IChatAgentData, callback: IChatAgentCallback): IDisposable { - return combinedDisposable( - this.registerAgentData(data), - this.registerAgentCallback(data.id, callback) - ); + getDefaultAgent(): IChatAgent | undefined { + return Iterable.find(this._agents.values(), a => !!a.agent.metadata.isDefault)?.agent; } - getAgents(): Array { - return Array.from(this._agents.values(), v => v.data); + getAgents(): Array { + return Array.from(this._agents.values(), v => v.agent); } hasAgent(id: string): boolean { return this._agents.has(id); } - getAgent(id: string): IChatAgentData | undefined { + getAgent(id: string): IChatAgent | undefined { const data = this._agents.get(id); - return data?.data; + return data?.agent; } - async invokeAgent(id: string, prompt: string, progress: IProgress, history: IChatMessage[], token: CancellationToken): Promise<{ followUp: IChatFollowup[] } | void> { + async invokeAgent(id: string, request: IChatAgentRequest, progress: IProgress, history: IChatMessage[], token: CancellationToken): Promise { const data = this._agents.get(id); if (!data) { - throw new Error('No agent with id ${id} NOT registered'); - } - if (!data.callback) { - await this._extensionService.activateByEvent(`onChatAgent:${id}`); - } - if (!data.callback) { - throw new Error(`No agent with id ${id} NOT resolved`); + throw new Error(`No agent with id ${id}`); } - return await data.callback(prompt, progress, history, token); + return await data.agent.invoke(request, progress, history, token); } -} - -class ChatAgentContribution implements IWorkbenchContribution { - constructor(@IChatAgentService chatAgentService: IChatAgentService) { - const contributions = new DisposableStore(); - - agentsExtPoint.setHandler(extensions => { - contributions.clear(); - for (const entry of extensions) { - if (!isProposedApiEnabled(entry.description, 'chatAgents')) { - entry.collector.error(`The ${agentsExtPoint.name} is proposed API`); - continue; - } - - const { value } = entry; - - for (const candidate of Iterable.wrap(value)) { + async getFollowups(id: string, sessionId: string, token: CancellationToken): Promise { + const data = this._agents.get(id); + if (!data) { + throw new Error(`No agent with id ${id}`); + } - if (!isAgentData(candidate)) { - entry.collector.error(localize('invalid', "Invalid {0}: {1}", agentsExtPoint.name, JSON.stringify(candidate))); - continue; - } + if (!data.agent.provideFollowups) { + return []; + } - contributions.add(chatAgentService.registerAgentData({ ...candidate })); - } - } - }); + return data.agent.provideFollowups(sessionId, token); } } - -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ChatAgentContribution, LifecyclePhase.Restored); diff --git a/code/src/vs/workbench/contrib/chat/common/chatModel.ts b/code/src/vs/workbench/contrib/chat/common/chatModel.ts index 5e62ae84d17..43f64cd6bd7 100644 --- a/code/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/code/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -3,17 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { firstOrDefault } from 'vs/base/common/arrays'; import { DeferredPromise } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { IMarkdownString, MarkdownString, isMarkdownString } from 'vs/base/common/htmlContent'; import { Disposable } from 'vs/base/common/lifecycle'; +import { basename } from 'vs/base/common/resources'; import { URI, UriComponents } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { OffsetRange } from 'vs/editor/common/core/offsetRange'; import { ILogService } from 'vs/platform/log/common/log'; -import { IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatAgent, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatRequestTextPart, IParsedChatRequest, reviveParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChat, IChatFollowup, IChatProgress, IChatReplyFollowup, IChatResponse, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IUsedContext, InteractiveSessionVoteDirection, isIUsedContext } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChat, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatProgress, IChatReplyFollowup, IChatResponse, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IUsedContext, InteractiveSessionVoteDirection, isIUsedContext } from 'vs/workbench/contrib/chat/common/chatService'; export interface IChatRequestModel { readonly id: string; @@ -35,13 +37,14 @@ export type ResponsePart = string | IMarkdownString | { treeData: IChatResponseProgressFileTreeData } >; } - | IUsedContext; + | IUsedContext + | IChatContentReference + | IChatContentInlineReference; export interface IResponse { - readonly value: (IMarkdownString | IPlaceholderMarkdownString | IChatResponseProgressFileTreeData)[]; - onDidChangeValue: Event; - usedContext: IUsedContext | undefined; - updateContent(responsePart: ResponsePart, quiet?: boolean): void; + readonly value: ReadonlyArray; + readonly usedContext: IUsedContext | undefined; + readonly contentReferences: ReadonlyArray; asString(): string; } @@ -53,6 +56,7 @@ export interface IChatResponseModel { readonly username: string; readonly avatarIconUri?: URI; readonly session: IChatModel; + readonly agent?: IChatAgent; readonly response: IResponse; readonly isComplete: boolean; readonly isCanceled: boolean; @@ -86,7 +90,7 @@ export class ChatRequestModel implements IChatRequestModel { constructor( public readonly session: ChatModel, - public readonly message: IParsedChatRequest | IChatReplyFollowup, + public readonly message: IParsedChatRequest, private _providerRequestId?: string) { this._id = 'request_' + ChatRequestModel.nextId++; } @@ -102,6 +106,7 @@ export interface IPlaceholderMarkdownString extends IMarkdownString { type InternalResponsePart = | { string: IMarkdownString; isPlaceholder?: boolean } + | IChatContentInlineReference | { treeData: IChatResponseProgressFileTreeData; isPlaceholder?: undefined }; export class Response implements IResponse { @@ -110,6 +115,11 @@ export class Response implements IResponse { return this._onDidChangeValue.event; } + private _contentReferences: IChatContentReference[] = []; + public get contentReferences(): IChatContentReference[] { + return this._contentReferences; + } + private _usedContext: IUsedContext | undefined; public get usedContext(): IUsedContext | undefined { return this._usedContext; @@ -118,21 +128,25 @@ export class Response implements IResponse { // responseParts internally tracks all the response parts, including strings which are currently resolving, so that they can be updated when they do resolve private _responseParts: InternalResponsePart[]; // responseData externally presents the response parts with consolidated contiguous strings (including strings which were previously resolving) - private _responseData: (IMarkdownString | IPlaceholderMarkdownString | IChatResponseProgressFileTreeData)[]; + private _responseData: (IMarkdownString | IPlaceholderMarkdownString | IChatResponseProgressFileTreeData | IChatContentInlineReference)[]; // responseRepr externally presents the response parts with consolidated contiguous strings (excluding tree data) private _responseRepr: string; - get value(): (IMarkdownString | IPlaceholderMarkdownString | IChatResponseProgressFileTreeData)[] { + get value(): (IMarkdownString | IPlaceholderMarkdownString | IChatResponseProgressFileTreeData | IChatContentInlineReference)[] { return this._responseData; } - constructor(value: IMarkdownString | (IMarkdownString | IChatResponseProgressFileTreeData)[]) { + constructor(value: IMarkdownString | ReadonlyArray) { this._responseData = Array.isArray(value) ? value : [value]; this._responseParts = Array.isArray(value) ? value.map((v) => ('value' in v ? { string: v } : { treeData: v })) : [{ string: value }]; this._responseRepr = this._responseParts.map((part) => { if (isCompleteInteractiveProgressTreeData(part)) { return ''; } + // TODO duplicates _updateRepr + if ('inlineReference' in part) { + return basename('uri' in part.inlineReference ? part.inlineReference.uri : part.inlineReference); + } return part.string.value; }).join('\n'); } @@ -146,16 +160,18 @@ export class Response implements IResponse { const responsePartLength = this._responseParts.length - 1; const lastResponsePart = this._responseParts[responsePartLength]; - if (lastResponsePart.isPlaceholder === true || isCompleteInteractiveProgressTreeData(lastResponsePart)) { + if (lastResponsePart && ('inlineReference' in lastResponsePart || lastResponsePart.isPlaceholder === true || isCompleteInteractiveProgressTreeData(lastResponsePart))) { // The last part is resolving or a tree data item, start a new part this._responseParts.push({ string: typeof responsePart === 'string' ? new MarkdownString(responsePart) : responsePart }); - } else { + } else if (lastResponsePart) { // Combine this part with the last, non-resolving string part if (isMarkdownString(responsePart)) { this._responseParts[responsePartLength] = { string: new MarkdownString(lastResponsePart.string.value + responsePart.value, responsePart) }; } else { this._responseParts[responsePartLength] = { string: new MarkdownString(lastResponsePart.string.value + responsePart, lastResponsePart.string) }; } + } else { + this._responseParts.push({ string: isMarkdownString(responsePart) ? responsePart : new MarkdownString(responsePart) }); } this._updateRepr(quiet); @@ -182,12 +198,20 @@ export class Response implements IResponse { this._updateRepr(quiet); } else if ('documents' in responsePart) { this._usedContext = responsePart; + } else if ('reference' in responsePart) { + this._contentReferences.push(responsePart); + this._onDidChangeValue.fire(); + } else if ('inlineReference' in responsePart) { + this._responseParts.push(responsePart); + this._updateRepr(quiet); } } private _updateRepr(quiet?: boolean) { this._responseData = this._responseParts.map(part => { - if (isCompleteInteractiveProgressTreeData(part)) { + if ('inlineReference' in part) { + return part; + } else if (isCompleteInteractiveProgressTreeData(part)) { return part.treeData; } else if (part.isPlaceholder) { return { ...part.string, isPlaceholder: true }; @@ -199,6 +223,10 @@ export class Response implements IResponse { if (isCompleteInteractiveProgressTreeData(part)) { return ''; } + if ('inlineReference' in part) { + return basename('uri' in part.inlineReference ? part.inlineReference.uri : part.inlineReference); + } + return part.string.value; }).join('\n\n'); @@ -239,7 +267,7 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel return this._followups; } - private _response: IResponse; + private _response: Response; public get response(): IResponse { return this._response; } @@ -260,18 +288,21 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel return this.agent?.metadata.icon ?? this.session.responderAvatarIconUri; } + private _followups?: IChatFollowup[]; + constructor( - _response: IMarkdownString | (IMarkdownString | IChatResponseProgressFileTreeData)[], + _response: IMarkdownString | ReadonlyArray, public readonly session: ChatModel, - public readonly agent: IChatAgentData | undefined, + public readonly agent: IChatAgent | undefined, private _isComplete: boolean = false, private _isCanceled = false, private _vote?: InteractiveSessionVoteDirection, private _providerResponseId?: string, private _errorDetails?: IChatResponseErrorDetails, - private _followups?: IChatFollowup[] + followups?: ReadonlyArray ) { super(); + this._followups = followups ? [...followups] : undefined; this._response = new Response(_response); this._register(this._response.onDidChangeValue(() => this._onDidChange.fire())); this._id = 'response_' + ChatResponseModel.nextId++; @@ -317,7 +348,7 @@ export interface IChatModel { readonly onDidChange: Event; readonly sessionId: string; readonly providerId: string; - readonly isInitialized: boolean; + readonly initState: ChatModelInitState; readonly title: string; readonly welcomeMessage: IChatWelcomeMessageModel | undefined; readonly requestInProgress: boolean; @@ -333,7 +364,7 @@ export interface ISerializableChatsData { export interface ISerializableChatAgentData { id: string; - description: string; + description?: string; fullName?: string; icon?: UriComponents; } @@ -341,14 +372,15 @@ export interface ISerializableChatAgentData { export interface ISerializableChatRequestData { providerRequestId: string | undefined; message: string | IParsedChatRequest; - response: (IMarkdownString | IChatResponseProgressFileTreeData)[] | undefined; + response: ReadonlyArray | undefined; agent?: ISerializableChatAgentData; responseErrorDetails: IChatResponseErrorDetails | undefined; - followups: IChatFollowup[] | undefined; + followups: ReadonlyArray | undefined; isCanceled: boolean | undefined; vote: InteractiveSessionVoteDirection | undefined; /** For backward compat: should be optional */ usedContext?: IUsedContext; + contentReferences?: ReadonlyArray; } export interface IExportableChatData { @@ -408,6 +440,12 @@ export interface IChatInitEvent { kind: 'initialize'; } +export enum ChatModelInitState { + Created, + Initializing, + Initialized +} + export class ChatModel extends Disposable implements IChatModel { private readonly _onDidDispose = this._register(new Emitter()); readonly onDidDispose = this._onDidDispose.event; @@ -416,6 +454,7 @@ export class ChatModel extends Disposable implements IChatModel { readonly onDidChange = this._onDidChange.event; private _requests: ChatRequestModel[]; + private _initState: ChatModelInitState = ChatModelInitState.Created; private _isInitializedDeferred = new DeferredPromise(); private _session: IChat | undefined; @@ -472,8 +511,8 @@ export class ChatModel extends Disposable implements IChatModel { return this._session?.responderAvatarIconUri ?? this._initialResponderAvatarIconUri; } - get isInitialized(): boolean { - return this._isInitializedDeferred.isSettled; + get initState(): ChatModelInitState { + return this._initState; } private _isImported = false; @@ -482,9 +521,8 @@ export class ChatModel extends Disposable implements IChatModel { } get title(): string { - // const firstRequestMessage = this._requests[0]?.message; - // const message = typeof firstRequestMessage === 'string' ? firstRequestMessage : firstRequestMessage?.message ?? ''; - const message = ''; + const firstRequestMessage = firstOrDefault(this._requests)?.message; + const message = firstRequestMessage?.text ?? ''; return message.split('\n')[0].substring(0, 50); } @@ -515,7 +553,7 @@ export class ChatModel extends Disposable implements IChatModel { if (obj.welcomeMessage) { const content = obj.welcomeMessage.map(item => typeof item === 'string' ? new MarkdownString(item) : item); - this._welcomeMessage = new ChatWelcomeMessageModel(this, content); + this._welcomeMessage = new ChatWelcomeMessageModel(this, content, []); } try { @@ -531,6 +569,10 @@ export class ChatModel extends Disposable implements IChatModel { if (raw.usedContext) { // @ulugbekna: if this's a new vscode sessions, doc versions are incorrect anyway? request.response.updateContent(raw.usedContext); } + + if (raw.contentReferences) { + raw.contentReferences.forEach(r => request.response!.updateContent(r)); + } } return request; }); @@ -549,16 +591,26 @@ export class ChatModel extends Disposable implements IChatModel { }; } - startReinitialize(): void { + startInitialize(): void { + if (this.initState !== ChatModelInitState.Created) { + throw new Error(`ChatModel is in the wrong state for startInitialize: ${ChatModelInitState[this.initState]}`); + } + this._initState = ChatModelInitState.Initializing; + } + + deinitialize(): void { this._session = undefined; + this._initState = ChatModelInitState.Created; this._isInitializedDeferred = new DeferredPromise(); } initialize(session: IChat, welcomeMessage: ChatWelcomeMessageModel | undefined): void { - if (this._session || this._isInitializedDeferred.isSettled) { - throw new Error('ChatModel is already initialized'); + if (this.initState !== ChatModelInitState.Initializing) { + // Must call startInitialize before initialize, and only call it once + throw new Error(`ChatModel is in the wrong state for initialize: ${ChatModelInitState[this.initState]}`); } + this._initState = ChatModelInitState.Initialized; this._session = session; if (!this._welcomeMessage) { // Could also have loaded the welcome message from persisted data @@ -577,6 +629,10 @@ export class ChatModel extends Disposable implements IChatModel { } setInitializationError(error: Error): void { + if (this.initState !== ChatModelInitState.Initializing) { + throw new Error(`ChatModel is in the wrong state for setInitializationError: ${ChatModelInitState[this.initState]}`); + } + if (!this._isInitializedDeferred.isSettled) { this._isInitializedDeferred.error(error); } @@ -590,13 +646,13 @@ export class ChatModel extends Disposable implements IChatModel { return this._requests; } - addRequest(message: IParsedChatRequest | IChatReplyFollowup, chatAgent?: IChatAgentData): ChatRequestModel { + addRequest(message: IParsedChatRequest, chatAgent?: IChatAgent): ChatRequestModel { if (!this._session) { throw new Error('addRequest: No session'); } const request = new ChatRequestModel(this, message); - request.response = new ChatResponseModel(new MarkdownString(''), this, chatAgent); + request.response = new ChatResponseModel([], this, chatAgent); this._requests.push(request); this._onDidChange.fire({ kind: 'addRequest', request }); @@ -609,7 +665,7 @@ export class ChatModel extends Disposable implements IChatModel { } if (!request.response) { - request.response = new ChatResponseModel(new MarkdownString(''), this, undefined); + request.response = new ChatResponseModel([], this, undefined); } if (request.response.isComplete) { @@ -620,7 +676,7 @@ export class ChatModel extends Disposable implements IChatModel { request.response.updateContent(progress.content, quiet); } else if ('placeholder' in progress || isCompleteInteractiveProgressTreeData(progress)) { request.response.updateContent(progress, quiet); - } else if ('documents' in progress) { + } else if ('documents' in progress || 'reference' in progress || 'inlineReference' in progress) { request.response.updateContent(progress); } else { request.setProviderRequestId(progress.requestId); @@ -654,7 +710,7 @@ export class ChatModel extends Disposable implements IChatModel { } if (!request.response) { - request.response = new ChatResponseModel(new MarkdownString(''), this, undefined); + request.response = new ChatResponseModel([], this, undefined); } request.response.setErrorDetails(rawResponse.errorDetails); @@ -698,7 +754,7 @@ export class ChatModel extends Disposable implements IChatModel { requests: this._requests.map((r): ISerializableChatRequestData => { return { providerRequestId: r.providerRequestId, - message: 'text' in r.message ? r.message : r.message.message, + message: r.message, response: r.response ? r.response.response.value : undefined, responseErrorDetails: r.response?.errorDetails, followups: r.response?.followups, @@ -711,6 +767,7 @@ export class ChatModel extends Disposable implements IChatModel { icon: r.response.agent.metadata.icon } : undefined, usedContext: r.response?.response.usedContext, + contentReferences: r.response?.response.contentReferences }; }), providerId: this.providerId, @@ -731,9 +788,6 @@ export class ChatModel extends Disposable implements IChatModel { this._session?.dispose?.(); this._requests.forEach(r => r.response?.dispose()); this._onDidDispose.fire(); - if (!this._isInitializedDeferred.isSettled) { - this._isInitializedDeferred.error(new Error('model disposed before initialization')); - } super.dispose(); } @@ -744,6 +798,7 @@ export type IChatWelcomeMessageContent = IMarkdownString | IChatReplyFollowup[]; export interface IChatWelcomeMessageModel { readonly id: string; readonly content: IChatWelcomeMessageContent[]; + readonly sampleQuestions: IChatReplyFollowup[]; readonly username: string; readonly avatarIconUri?: URI; @@ -760,6 +815,7 @@ export class ChatWelcomeMessageModel implements IChatWelcomeMessageModel { constructor( private readonly session: ChatModel, public readonly content: IChatWelcomeMessageContent[], + public readonly sampleQuestions: IChatReplyFollowup[] ) { this._id = 'welcome_' + ChatWelcomeMessageModel.nextId++; } diff --git a/code/src/vs/workbench/contrib/chat/common/chatParserTypes.ts b/code/src/vs/workbench/contrib/chat/common/chatParserTypes.ts index e5df0780f4c..5c9e368d58e 100644 --- a/code/src/vs/workbench/contrib/chat/common/chatParserTypes.ts +++ b/code/src/vs/workbench/contrib/chat/common/chatParserTypes.ts @@ -6,7 +6,7 @@ import { URI } from 'vs/base/common/uri'; import { IOffsetRange, OffsetRange } from 'vs/editor/common/core/offsetRange'; import { IRange } from 'vs/editor/common/core/range'; -import { IChatAgentData, IChatAgentCommand } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatAgent, IChatAgentCommand } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService'; // These are in a separate file to avoid circular dependencies with the dependencies of the parser @@ -21,12 +21,17 @@ export interface IParsedChatRequestPart { readonly range: IOffsetRange; readonly editorRange: IRange; readonly text: string; + readonly promptText: string; } export class ChatRequestTextPart implements IParsedChatRequestPart { static readonly Kind = 'text'; readonly kind = ChatRequestTextPart.Kind; constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly text: string) { } + + get promptText(): string { + return this.text; + } } export const chatVariableLeader = '#'; // warning, this also shows up in a regex in the parser @@ -43,6 +48,10 @@ export class ChatRequestVariablePart implements IParsedChatRequestPart { const argPart = this.variableArg ? `:${this.variableArg}` : ''; return `${chatVariableLeader}${this.variableName}${argPart}`; } + + get promptText(): string { + return this.text; + } } /** @@ -51,11 +60,15 @@ export class ChatRequestVariablePart implements IParsedChatRequestPart { export class ChatRequestAgentPart implements IParsedChatRequestPart { static readonly Kind = 'agent'; readonly kind = ChatRequestAgentPart.Kind; - constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly agent: IChatAgentData) { } + constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly agent: IChatAgent) { } get text(): string { return `@${this.agent.id}`; } + + get promptText(): string { + return ''; + } } /** @@ -69,6 +82,10 @@ export class ChatRequestAgentSubcommandPart implements IParsedChatRequestPart { get text(): string { return `/${this.command.name}`; } + + get promptText(): string { + return ''; + } } /** @@ -82,6 +99,10 @@ export class ChatRequestSlashCommandPart implements IParsedChatRequestPart { get text(): string { return `/${this.slashCommand.command}`; } + + get promptText(): string { + return `/${this.slashCommand.command}`; + } } /** @@ -99,6 +120,10 @@ export class ChatRequestDynamicReferencePart implements IParsedChatRequestPart { get text(): string { return `$${this.referenceText}`; } + + get promptText(): string { + return `[${this.text}](values:${this.referenceText})`; + } } export function reviveParsedChatRequest(serialized: IParsedChatRequest): IParsedChatRequest { diff --git a/code/src/vs/workbench/contrib/chat/common/chatRequestParser.ts b/code/src/vs/workbench/contrib/chat/common/chatRequestParser.ts index 64cb389fb15..6b322bdea63 100644 --- a/code/src/vs/workbench/contrib/chat/common/chatRequestParser.ts +++ b/code/src/vs/workbench/contrib/chat/common/chatRequestParser.ts @@ -7,7 +7,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { OffsetRange } from 'vs/editor/common/core/offsetRange'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatAgent, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestDynamicReferencePart, ChatRequestSlashCommandPart, ChatRequestTextPart, ChatRequestVariablePart, IParsedChatRequest, IParsedChatRequestPart, chatVariableLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; @@ -79,6 +79,29 @@ export class ChatRequestParser { message.slice(lastPartEnd, message.length))); } + + // fix up parts: + // * only one agent at the beginning of the message + // * only one agent command after the agent or at the beginning of the message + let agentIndex = -1; + for (let i = 0; i < parts.length; i++) { + const part = parts[i]; + if (part instanceof ChatRequestAgentPart) { + if (i === 0) { + agentIndex = 0; + } else { + // agent not first -> make text part + parts[i] = new ChatRequestTextPart(part.range, part.editorRange, part.text); + } + } + if (part instanceof ChatRequestAgentSubcommandPart) { + if (!(i === 0 || agentIndex === 0 && i === 2 && /^\s+$/.test(parts[1].text))) { + // agent command not after agent nor first -> make text part + parts[i] = new ChatRequestTextPart(part.range, part.editorRange, part.text); + } + } + } + return { parts, text: message, @@ -95,7 +118,7 @@ export class ChatRequestParser { const varRange = new OffsetRange(offset, offset + full.length); const varEditorRange = new Range(position.lineNumber, position.column, position.lineNumber, position.column + full.length); - let agent: IChatAgentData | undefined; + let agent: IChatAgent | undefined; if ((agent = this.agentService.getAgent(name))) { if (parts.some(p => p instanceof ChatRequestAgentPart)) { // Only one agent allowed @@ -143,7 +166,8 @@ export class ChatRequestParser { const usedAgent = parts.find((p): p is ChatRequestAgentPart => p instanceof ChatRequestAgentPart); if (usedAgent) { - const subCommand = usedAgent.agent.metadata.subCommands.find(c => c.name === command); + const subCommands = await usedAgent.agent.provideSlashCommands(CancellationToken.None); + const subCommand = subCommands.find(c => c.name === command); if (subCommand) { // Valid agent subcommand return new ChatRequestAgentSubcommandPart(slashRange, slashEditorRange, subCommand); diff --git a/code/src/vs/workbench/contrib/chat/common/chatService.ts b/code/src/vs/workbench/contrib/chat/common/chatService.ts index 9de06eeab93..356cbe6ab58 100644 --- a/code/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/code/src/vs/workbench/contrib/chat/common/chatService.ts @@ -9,7 +9,7 @@ import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { Range, IRange } from 'vs/editor/common/core/range'; -import { ProviderResult } from 'vs/editor/common/languages'; +import { ProviderResult, Location } from 'vs/editor/common/languages'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatModel, ChatModel, ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; @@ -28,7 +28,7 @@ export interface IChat { export interface IChatRequest { session: IChat; - message: string | IChatReplyFollowup; + message: string; variables: Record; } @@ -83,12 +83,23 @@ export function isIUsedContext(obj: unknown): obj is IUsedContext { ); } +export interface IChatContentReference { + reference: URI | Location; +} + +export interface IChatContentInlineReference { + inlineReference: URI | Location; + name?: string; +} + export type IChatProgress = | { content: string | IMarkdownString } | { requestId: string } | { treeData: IChatResponseProgressFileTreeData } | { placeholder: string; resolvedContent: Promise } - | IUsedContext; + | IUsedContext + | IChatContentReference + | IChatContentInlineReference; export interface IPersistedChatState { } export interface IChatProvider { @@ -97,6 +108,7 @@ export interface IChatProvider { readonly iconUrl?: string; prepareSession(initialState: IPersistedChatState | undefined, token: CancellationToken): ProviderResult; provideWelcomeMessage?(token: CancellationToken): ProviderResult<(string | IChatReplyFollowup[])[] | undefined>; + provideSampleQuestions?(token: CancellationToken): ProviderResult; provideFollowups?(session: IChat, token: CancellationToken): ProviderResult; provideReply(request: IChatRequest, progress: (progress: IChatProgress) => void, token: CancellationToken): ProviderResult; provideSlashCommands?(session: IChat, token: CancellationToken): ProviderResult; @@ -152,8 +164,8 @@ export type IChatFollowup = IChatReplyFollowup | IChatResponseCommandFollowup; // Name has to match the one in vscode.d.ts for some reason export enum InteractiveSessionVoteDirection { - Up = 1, - Down = 2 + Down = 0, + Up = 1 } export interface IChatVoteAction { @@ -203,6 +215,8 @@ export type ChatUserAction = IChatVoteAction | IChatCopyAction | IChatInsertActi export interface IChatUserActionEvent { action: ChatUserAction; providerId: string; + agentId: string | undefined; + sessionId: string; } export interface IChatDynamicRequest { @@ -218,7 +232,7 @@ export interface IChatDynamicRequest { } export interface IChatCompleteResponse { - message: string | (IMarkdownString | IChatResponseProgressFileTreeData)[]; + message: string | ReadonlyArray; errorDetails?: IChatResponseErrorDetails; followups?: IChatFollowup[]; } @@ -256,7 +270,7 @@ export interface IChatService { /** * Returns whether the request was accepted. */ - sendRequest(sessionId: string, message: string | IChatReplyFollowup, usedSlashCommand?: ISlashCommand): Promise<{ responseCompletePromise: Promise } | undefined>; + sendRequest(sessionId: string, message: string, usedSlashCommand?: ISlashCommand): Promise<{ responseCompletePromise: Promise } | undefined>; removeRequest(sessionid: string, requestId: string): Promise; cancelCurrentRequestForSession(sessionId: string): void; getSlashCommands(sessionId: string, token: CancellationToken): Promise; @@ -268,6 +282,7 @@ export interface IChatService { onDidPerformUserAction: Event; notifyUserAction(event: IChatUserActionEvent): void; + onDidDisposeSession: Event<{ sessionId: string }>; transferChatSession(transferredSessionData: IChatTransferredSessionData, toWorkspace: URI): void; } diff --git a/code/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/code/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 6641afd88ac..8791a463620 100644 --- a/code/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/code/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -12,6 +12,7 @@ import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle' import { revive } from 'vs/base/common/marshalling'; import { StopWatch } from 'vs/base/common/stopwatch'; import { URI, UriComponents } from 'vs/base/common/uri'; +import { generateUuid } from 'vs/base/common/uuid'; import { localize } from 'vs/nls'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -21,10 +22,10 @@ import { Progress } from 'vs/platform/progress/common/progress'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { IChatAgentRequest, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; -import { ChatModel, ChatRequestModel, ChatWelcomeMessageModel, IChatModel, ISerializableChatData, ISerializableChatsData, isCompleteInteractiveProgressTreeData } from 'vs/workbench/contrib/chat/common/chatModel'; -import { ChatRequestAgentPart, ChatRequestSlashCommandPart, IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { ChatModel, ChatModelInitState, ChatRequestModel, ChatWelcomeMessageModel, IChatModel, ISerializableChatData, ISerializableChatsData, isCompleteInteractiveProgressTreeData } from 'vs/workbench/contrib/chat/common/chatModel'; +import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; import { IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatReplyFollowup, IChatRequest, IChatResponse, IChatService, IChatTransferredSessionData, IChatUserActionEvent, ISlashCommand, InteractiveSessionCopyKind, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; @@ -146,6 +147,9 @@ export class ChatService extends Disposable implements IChatService { private readonly _onDidSubmitSlashCommand = this._register(new Emitter<{ slashCommand: string; sessionId: string }>()); public readonly onDidSubmitSlashCommand = this._onDidSubmitSlashCommand.event; + private readonly _onDidDisposeSession = this._register(new Emitter<{ sessionId: string }>()); + public readonly onDidDisposeSession = this._onDidDisposeSession.event; + constructor( @IStorageService private readonly storageService: IStorageService, @ILogService private readonly logService: ILogService, @@ -324,60 +328,57 @@ export class ChatService extends Disposable implements IChatService { } private _startSession(providerId: string, someSessionHistory: ISerializableChatData | undefined, token: CancellationToken): ChatModel { + this.trace('_startSession', `providerId=${providerId}`); const model = this.instantiationService.createInstance(ChatModel, providerId, someSessionHistory); this._sessionModels.set(model.sessionId, model); - const modelInitPromise = this.initializeSession(model, token); - modelInitPromise.catch(err => { - this.trace('startSession', `initializeSession failed: ${err}`); - model.setInitializationError(err); - model.dispose(); - this._sessionModels.delete(model.sessionId); - }); - + this.initializeSession(model, token); return model; } private reinitializeModel(model: ChatModel): void { - model.startReinitialize(); - this.startSessionInit(model, CancellationToken.None); - } - - private startSessionInit(model: ChatModel, token: CancellationToken): void { - const modelInitPromise = this.initializeSession(model, token); - modelInitPromise.catch(err => { - this.trace('startSession', `initializeSession failed: ${err}`); - model.setInitializationError(err); - model.dispose(); - this._sessionModels.delete(model.sessionId); - }); + this.trace('reinitializeModel', `Start reinit`); + this.initializeSession(model, CancellationToken.None); } private async initializeSession(model: ChatModel, token: CancellationToken): Promise { - await this.extensionService.activateByEvent(`onInteractiveSession:${model.providerId}`); + try { + this.trace('initializeSession', `Initialize session ${model.sessionId}`); + model.startInitialize(); + await this.extensionService.activateByEvent(`onInteractiveSession:${model.providerId}`); - const provider = this._providers.get(model.providerId); - if (!provider) { - throw new Error(`Unknown provider: ${model.providerId}`); - } + const provider = this._providers.get(model.providerId); + if (!provider) { + throw new Error(`Unknown provider: ${model.providerId}`); + } - let session: IChat | undefined; - try { - session = await provider.prepareSession(model.providerState, token) ?? undefined; - } catch (err) { - this.trace('initializeSession', `Provider initializeSession threw: ${err}`); - } + let session: IChat | undefined; + try { + session = await provider.prepareSession(model.providerState, token) ?? undefined; + } catch (err) { + this.trace('initializeSession', `Provider initializeSession threw: ${err}`); + } - if (!session) { - throw new Error('Provider returned no session'); - } + if (!session) { + throw new Error('Provider returned no session'); + } - this.trace('startSession', `Provider returned session`); + this.trace('startSession', `Provider returned session`); - const welcomeMessage = model.welcomeMessage ? undefined : await provider.provideWelcomeMessage?.(token) ?? undefined; - const welcomeModel = welcomeMessage && new ChatWelcomeMessageModel( - model, welcomeMessage.map(item => typeof item === 'string' ? new MarkdownString(item) : item as IChatReplyFollowup[])); + const welcomeMessage = model.welcomeMessage ? undefined : await provider.provideWelcomeMessage?.(token) ?? undefined; + const welcomeModel = welcomeMessage && new ChatWelcomeMessageModel( + model, + welcomeMessage.map(item => typeof item === 'string' ? new MarkdownString(item) : item as IChatReplyFollowup[]), + await provider.provideSampleQuestions?.(token) ?? [] + ); - model.initialize(session, welcomeModel); + model.initialize(session, welcomeModel); + } catch (err) { + this.trace('startSession', `initializeSession failed: ${err}`); + model.setInitializationError(err); + model.dispose(); + this._sessionModels.delete(model.sessionId); + this._onDidDisposeSession.fire({ sessionId: model.sessionId }); + } } getSession(sessionId: string): IChatModel | undefined { @@ -389,6 +390,7 @@ export class ChatService extends Disposable implements IChatService { } getOrRestoreSession(sessionId: string): ChatModel | undefined { + this.trace('getOrRestoreSession', `sessionId: ${sessionId}`); const model = this._sessionModels.get(sessionId); if (model) { return model; @@ -410,10 +412,9 @@ export class ChatService extends Disposable implements IChatService { return this._startSession(data.providerId, data, CancellationToken.None); } - async sendRequest(sessionId: string, request: string | IChatReplyFollowup, usedSlashCommand?: ISlashCommand): Promise<{ responseCompletePromise: Promise } | undefined> { - const messageText = typeof request === 'string' ? request : request.message; - this.trace('sendRequest', `sessionId: ${sessionId}, message: ${messageText.substring(0, 20)}${messageText.length > 20 ? '[...]' : ''}}`); - if (!messageText.trim()) { + async sendRequest(sessionId: string, request: string, usedSlashCommand?: ISlashCommand): Promise<{ responseCompletePromise: Promise } | undefined> { + this.trace('sendRequest', `sessionId: ${sessionId}, message: ${request.substring(0, 20)}${request.length > 20 ? '[...]' : ''}}`); + if (!request.trim()) { this.trace('sendRequest', 'Rejected empty message'); return; } @@ -438,19 +439,16 @@ export class ChatService extends Disposable implements IChatService { return { responseCompletePromise: this._sendRequestAsync(model, sessionId, provider, request, usedSlashCommand) }; } - private async _sendRequestAsync(model: ChatModel, sessionId: string, provider: IChatProvider, message: string | IChatReplyFollowup, usedSlashCommand?: ISlashCommand): Promise { - const parsedRequest = typeof message === 'string' ? - await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(sessionId, message) : - message; // Handle the followup type along with the response + private async _sendRequestAsync(model: ChatModel, sessionId: string, provider: IChatProvider, message: string, usedSlashCommand?: ISlashCommand): Promise { + const parsedRequest = await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(sessionId, message); let request: ChatRequestModel; const agentPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestAgentPart => r instanceof ChatRequestAgentPart); + const agentSlashCommandPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestAgentSubcommandPart => r instanceof ChatRequestAgentSubcommandPart); const commandPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestSlashCommandPart => r instanceof ChatRequestSlashCommandPart); let gotProgress = false; - const requestType = typeof message === 'string' ? - commandPart ? 'slashCommand' : 'string' : - 'followup'; + const requestType = commandPart ? 'slashCommand' : 'string'; const rawResponsePromise = createCancelablePromise(async token => { const progressCallback = (progress: IChatProgress) => { @@ -469,6 +467,10 @@ export class ChatService extends Disposable implements IChatService { this.trace('sendRequest', `Provider returned tree data for session ${model.sessionId}, ${progress.treeData.label}`); } else if ('documents' in progress) { this.trace('sendRequest', `Provider returned documents for session ${model.sessionId}:\n ${JSON.stringify(progress.documents, null, '\t')}`); + } else if ('reference' in progress) { + this.trace('sendRequest', `Provider returned a reference for session ${model.sessionId}:\n ${JSON.stringify(progress.reference, null, '\t')}`); + } else if ('inlineReference' in progress) { + this.trace('sendRequest', `Provider returned an inline reference for session ${model.sessionId}:\n ${JSON.stringify(progress.inlineReference, null, '\t')}`); } else { this.trace('sendRequest', `Provider returned id for session ${model.sessionId}, ${progress.requestId}`); } @@ -498,47 +500,63 @@ export class ChatService extends Disposable implements IChatService { } let rawResponse: IChatResponse | null | undefined; - let slashCommandFollowups: IChatFollowup[] | void = []; + let agentOrCommandFollowups: Promise | undefined = undefined; - if (typeof message === 'string' && agentPart) { - request = model.addRequest(parsedRequest); + const defaultAgent = this.chatAgentService.getDefaultAgent(); + if (agentPart || defaultAgent) { + const agent = (agentPart?.agent ?? defaultAgent)!; const history: IChatMessage[] = []; for (const request of model.getRequests()) { - if (typeof request.message !== 'string' || !request.response) { + if (!request.response) { continue; } - if (isMarkdownString(request.response.response.value)) { - history.push({ role: ChatMessageRole.User, content: request.message }); - history.push({ role: ChatMessageRole.Assistant, content: request.response.response.value.value }); - } + + history.push({ role: ChatMessageRole.User, content: request.message.text }); + history.push({ role: ChatMessageRole.Assistant, content: request.response.response.asString() }); } - const agentResult = await this.chatAgentService.invokeAgent(agentPart.agent.id, message.substring(agentPart.agent.id.length + 1).trimStart(), new Progress(p => { - const { content } = p; - const data = isCompleteInteractiveProgressTreeData(content) ? content : { content }; - progressCallback(data); + + request = model.addRequest(parsedRequest, agent); + const requestProps: IChatAgentRequest = { + sessionId, + requestId: generateUuid(), + message, + variables: {}, + command: agentSlashCommandPart?.command.name ?? '', + }; + if ('parts' in parsedRequest) { + const varResult = await this.chatVariablesService.resolveVariables(parsedRequest, model, token); + requestProps.variables = varResult.variables; + requestProps.message = varResult.prompt; + } + + const agentResult = await this.chatAgentService.invokeAgent(agent.id, requestProps, new Progress(p => { + progressCallback(p); }), history, token); - slashCommandFollowups = agentResult?.followUp; - rawResponse = { session: model.session! }; - } else if (commandPart && typeof message === 'string' && this.chatSlashCommandService.hasCommand(commandPart.slashCommand.command)) { + rawResponse = { + session: model.session!, + errorDetails: agentResult.errorDetails, + timings: agentResult.timings + }; + agentOrCommandFollowups = agentResult?.followUp ? Promise.resolve(agentResult.followUp) : + this.chatAgentService.getFollowups(agent.id, sessionId, CancellationToken.None); + } else if (commandPart && this.chatSlashCommandService.hasCommand(commandPart.slashCommand.command)) { request = model.addRequest(parsedRequest); // contributed slash commands // TODO: spell this out in the UI const history: IChatMessage[] = []; for (const request of model.getRequests()) { - if (typeof request.message !== 'string' || !request.response) { + if (!request.response) { continue; } - if (isMarkdownString(request.response.response.value)) { - history.push({ role: ChatMessageRole.User, content: request.message }); - history.push({ role: ChatMessageRole.Assistant, content: request.response.response.value.value }); - } + history.push({ role: ChatMessageRole.User, content: request.message.text }); + history.push({ role: ChatMessageRole.Assistant, content: request.response.response.asString() }); } const commandResult = await this.chatSlashCommandService.executeCommand(commandPart.slashCommand.command, message.substring(commandPart.slashCommand.command.length + 1).trimStart(), new Progress(p => { const { content } = p; const data = isCompleteInteractiveProgressTreeData(content) ? content : { content }; progressCallback(data); }), history, token); - slashCommandFollowups = commandResult?.followUp; + agentOrCommandFollowups = Promise.resolve(commandResult?.followUp); rawResponse = { session: model.session! }; } else { @@ -581,15 +599,16 @@ export class ChatService extends Disposable implements IChatService { this.trace('sendRequest', `Provider returned response for session ${model.sessionId}`); // TODO refactor this or rethink the API https://github.com/microsoft/vscode-copilot/issues/593 - if (provider.provideFollowups) { + if (agentOrCommandFollowups) { + agentOrCommandFollowups.then(followups => { + model.setFollowups(request, followups); + model.completeResponse(request); + }); + } else if (provider.provideFollowups) { Promise.resolve(provider.provideFollowups(model.session!, CancellationToken.None)).then(providerFollowups => { - const allFollowups = providerFollowups?.concat(slashCommandFollowups ?? []); - model.setFollowups(request, allFollowups ?? undefined); + model.setFollowups(request, providerFollowups ?? undefined); model.completeResponse(request); }); - } else if (slashCommandFollowups?.length) { - model.setFollowups(request, slashCommandFollowups); - model.completeResponse(request); } else { model.completeResponse(request); } @@ -666,7 +685,7 @@ export class ChatService extends Disposable implements IChatService { return Array.from(this._providers.keys()); } - async addCompleteRequest(sessionId: string, message: string | IParsedChatRequest, response: IChatCompleteResponse): Promise { + async addCompleteRequest(sessionId: string, message: string, response: IChatCompleteResponse): Promise { this.trace('addCompleteRequest', `message: ${message}`); const model = this._sessionModels.get(sessionId); @@ -675,13 +694,15 @@ export class ChatService extends Disposable implements IChatService { } await model.waitForInitialization(); - const parsedRequest = typeof message === 'string' ? await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(sessionId, message) : message; + const parsedRequest = await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(sessionId, message); const request = model.addRequest(parsedRequest); if (typeof response.message === 'string') { model.acceptResponseProgress(request, { content: response.message }); } else { for (const part of response.message) { - const progress = isMarkdownString(part) ? { content: part.value } : { treeData: part }; + const progress = 'inlineReference' in part ? part : + isMarkdownString(part) ? { content: part.value } : + { treeData: part }; model.acceptResponseProgress(request, progress, true); } } @@ -712,6 +733,7 @@ export class ChatService extends Disposable implements IChatService { model.dispose(); this._sessionModels.delete(sessionId); this._pendingRequests.get(sessionId)?.cancel(); + this._onDidDisposeSession.fire({ sessionId }); } registerProvider(provider: IChatProvider): IDisposable { @@ -726,12 +748,17 @@ export class ChatService extends Disposable implements IChatService { Array.from(this._sessionModels.values()) .filter(model => model.providerId === provider.id) + // The provider may have been registered in the process of initializing this model. Only grab models that were deinitialized when the provider was unregistered + .filter(model => model.initState === ChatModelInitState.Created) .forEach(model => this.reinitializeModel(model)); return toDisposable(() => { this.trace('registerProvider', `Disposing chat provider`); this._providers.delete(provider.id); this._hasProvider.set(this._providers.size > 0); + Array.from(this._sessionModels.values()) + .filter(model => model.providerId === provider.id) + .forEach(model => model.deinitialize()); }); } diff --git a/code/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/code/src/vs/workbench/contrib/chat/common/chatViewModel.ts index 87d005c5ec4..365303eaf7b 100644 --- a/code/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/code/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -10,7 +10,8 @@ import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { IChatModel, IChatRequestModel, IChatResponseModel, IChatWelcomeMessageContent, IResponse, Response } from 'vs/workbench/contrib/chat/common/chatModel'; +import { IChatAgent } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { ChatModelInitState, IChatModel, IChatRequestModel, IChatResponseModel, IChatWelcomeMessageContent, IResponse } from 'vs/workbench/contrib/chat/common/chatModel'; import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatReplyFollowup, IChatResponseCommandFollowup, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; @@ -27,14 +28,18 @@ export function isWelcomeVM(item: unknown): item is IChatWelcomeMessageViewModel return !!item && typeof item === 'object' && 'content' in item; } -export type IChatViewModelChangeEvent = IChatAddRequestEvent | null; +export type IChatViewModelChangeEvent = IChatAddRequestEvent | IChangePlaceholderEvent | null; export interface IChatAddRequestEvent { kind: 'addRequest'; } +export interface IChangePlaceholderEvent { + kind: 'changePlaceholder'; +} + export interface IChatViewModel { - readonly isInitialized: boolean; + readonly initState: ChatModelInitState; readonly providerId: string; readonly sessionId: string; readonly onDidDisposeModel: Event; @@ -42,6 +47,8 @@ export interface IChatViewModel { readonly requestInProgress: boolean; readonly inputPlaceholder?: string; getItems(): (IChatRequestViewModel | IChatResponseViewModel | IChatWelcomeMessageViewModel)[]; + setInputPlaceholder(text: string): void; + resetInputPlaceholder(): void; } export interface IChatRequestViewModel { @@ -83,6 +90,7 @@ export interface IChatResponseViewModel { readonly providerResponseId: string | undefined; readonly username: string; readonly avatarIconUri?: URI; + readonly agent?: IChatAgent; readonly response: IResponse; readonly isComplete: boolean; readonly isCanceled: boolean; @@ -107,8 +115,19 @@ export class ChatViewModel extends Disposable implements IChatViewModel { private readonly _items: (ChatRequestViewModel | ChatResponseViewModel)[] = []; + private _inputPlaceholder: string | undefined = undefined; get inputPlaceholder(): string | undefined { - return this._model.inputPlaceholder; + return this._inputPlaceholder ?? this._model.inputPlaceholder; + } + + setInputPlaceholder(text: string): void { + this._inputPlaceholder = text; + this._onDidChange.fire({ kind: 'changePlaceholder' }); + } + + resetInputPlaceholder(): void { + this._inputPlaceholder = undefined; + this._onDidChange.fire({ kind: 'changePlaceholder' }); } get sessionId() { @@ -123,8 +142,8 @@ export class ChatViewModel extends Disposable implements IChatViewModel { return this._model.providerId; } - get isInitialized() { - return this._model.isInitialized; + get initState() { + return this._model.initState; } constructor( @@ -197,7 +216,7 @@ export class ChatRequestViewModel implements IChatRequestViewModel { } get dataId() { - return this.id + (this._model.session.isInitialized ? '' : '_initializing'); + return this.id + `_${ChatModelInitState[this._model.session.initState]}`; } get sessionId() { @@ -236,7 +255,7 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi } get dataId() { - return this._model.id + `_${this._modelChangeCount}` + (this._model.session.isInitialized ? '' : '_initializing'); + return this._model.id + `_${this._modelChangeCount}` + `_${ChatModelInitState[this._model.session.initState]}`; } get providerId() { @@ -259,9 +278,20 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi return this._model.avatarIconUri; } + get agent() { + return this._model.agent; + } + get response(): IResponse { if (this._isPlaceholder) { - return new Response(new MarkdownString(localize('thinking', "Thinking") + '\u2026')); + // TODO@roblourens- this is suspicious. We may want to separate the markdown content from other types of content? + const placeholderText = new MarkdownString(localize('thinking', "Thinking") + '\u2026'); + return { + value: [placeholderText], + contentReferences: this._model.response.contentReferences, + usedContext: this._model.response.usedContext, + asString: () => placeholderText.value, + }; } return this._model.response; @@ -300,7 +330,19 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi currentRenderedHeight: number | undefined; - usedReferencesExpanded?: boolean | undefined; + private _usedReferencesExpanded: boolean | undefined; + + get usedReferencesExpanded(): boolean | undefined { + if (typeof this._usedReferencesExpanded === 'boolean') { + return this._usedReferencesExpanded; + } + + return this.isPlaceholder; + } + + set usedReferencesExpanded(v: boolean) { + this._usedReferencesExpanded = v; + } private _contentUpdateTimings: IChatLiveUpdateData | undefined = undefined; get contentUpdateTimings(): IChatLiveUpdateData | undefined { @@ -325,7 +367,7 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi } this._register(_model.onDidChange(() => { - if (this._isPlaceholder && (_model.response.value || this.isComplete)) { + if (this._isPlaceholder && (_model.response.value.length > 0 || this.isComplete)) { this._isPlaceholder = false; } @@ -373,5 +415,6 @@ export interface IChatWelcomeMessageViewModel { readonly username: string; readonly avatarIconUri?: URI; readonly content: IChatWelcomeMessageContent[]; + readonly sampleQuestions: IChatReplyFollowup[]; currentRenderedHeight?: number; } diff --git a/code/src/vs/workbench/contrib/chat/electron-sandbox/actions/media/voiceChatActions.css b/code/src/vs/workbench/contrib/chat/electron-sandbox/actions/media/voiceChatActions.css index b081c2c8d2f..4c5f41831a2 100644 --- a/code/src/vs/workbench/contrib/chat/electron-sandbox/actions/media/voiceChatActions.css +++ b/code/src/vs/workbench/contrib/chat/electron-sandbox/actions/media/voiceChatActions.css @@ -3,12 +3,80 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled):hover, -.monaco-workbench .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled):hover { - animation: none; /* stop the running voice recording animation for showing another codicon to stop */ +/* + * Stop the running animation, we only use it as a hint to apply CSS rules. + */ +.monaco-workbench .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled), +.monaco-workbench .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled) { + animation: none; } +/* + * Clear styles and replace icon to "stop" when hovering over it. + */ .monaco-workbench .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled):hover::before, .monaco-workbench .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled):hover::before { content: "\ead7"; /* use `debug-stop` icon unicode for hovering over running voice recording */ + background-color: inherit; + border-radius: 0; + color: inherit; + outline: none; +} + +/* + * Remove ::after element to improve "stop" visuals when hovering over it. + */ +.monaco-workbench .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled):hover::after, +.monaco-workbench .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled):hover::after { + display: none; +} + +/* + * Show a "microphone" icon when recording is in progress that: + * - uses z-index:1 and applies a background color to draw over the glowing animation (below) + * - emphasizes activity by drawing with badge colors + */ +.monaco-workbench .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before, +.monaco-workbench .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::before { + content: "\ec12"; + z-index: 1; + border-radius: 50%; + background-color: var(--vscode-input-background); + color: var(--vscode-activityBarBadge-background); + outline: 1px solid var(--vscode-activityBarBadge-background); +} + +/* + * Draw an ::after element for the glowing effect over the "microphone" icon that: + * - uses badge colors to emphasize activity + * - uses a "pulseAnimation" to indicate activity + */ +.monaco-workbench .interactive-input-part .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::after, +.monaco-workbench .inline-chat .monaco-action-bar .action-label.codicon-loading.codicon-modifier-spin:not(.disabled)::after { + content: ""; + position: absolute; + top: 50%; + left: 50%; + width: 18px; + height: 18px; + background-color: var(--vscode-activityBarBadge-background); + border-radius: 50%; + animation: pulseAnimation 1s infinite; + transform: translate(-50%, -50%) scale(0); + opacity: 0; +} + +@keyframes pulseAnimation { + 0% { + transform: translate(-50%, -50%) scale(1); + opacity: 1; + } + 50% { + transform: translate(-50%, -50%) scale(1.3); + opacity: 0.5; + } + 100% { + transform: translate(-50%, -50%) scale(1); + opacity: 1; + } } diff --git a/code/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/code/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index b244992f104..100d11d79b8 100644 --- a/code/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/code/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -9,25 +9,21 @@ import { firstOrDefault } from 'vs/base/common/arrays'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Codicon } from 'vs/base/common/codicons'; import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; -import { equalsIgnoreCase } from 'vs/base/common/strings'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize } from 'vs/nls'; -import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { Action2, MenuId } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { spinningLoading } from 'vs/platform/theme/common/iconRegistry'; import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { IChatWidget, IChatWidgetService, IQuickChatService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; -import { IWorkbenchVoiceRecognitionService } from 'vs/workbench/services/voiceRecognition/electron-sandbox/workbenchVoiceRecognitionService'; import { MENU_INLINE_CHAT_WIDGET } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { getCodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; -import product from 'vs/platform/product/common/product'; import { ActiveEditorContext } from 'vs/workbench/common/contextkeys'; import { IViewsService } from 'vs/workbench/common/views'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; @@ -35,6 +31,8 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { KeyCode } from 'vs/base/common/keyCodes'; import { isExecuteActionContext } from 'vs/workbench/contrib/chat/browser/actions/chatExecuteActions'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; +import { ISpeechService, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; +import { RunOnceScheduler } from 'vs/base/common/async'; const CONTEXT_VOICE_CHAT_GETTING_READY = new RawContextKey('voiceChatGettingReady', false, { type: 'boolean', description: localize('voiceChatGettingReady', "True when getting ready for receiving voice input from the microphone for voice chat.") }); const CONTEXT_VOICE_CHAT_IN_PROGRESS = new RawContextKey('voiceChatInProgress', false, { type: 'boolean', description: localize('voiceChatInProgress', "True when voice recording from microphone is in progress for voice chat.") }); @@ -56,6 +54,9 @@ interface IVoiceChatSessionController { focusInput(): void; acceptInput(): void; updateInput(text: string): void; + + setInputPlaceholder(text: string): void; + clearInputPlaceholder(): void; } class VoiceChatSessionControllerFactory { @@ -63,8 +64,8 @@ class VoiceChatSessionControllerFactory { static create(accessor: ServicesAccessor, context: 'inline'): Promise; static create(accessor: ServicesAccessor, context: 'quick'): Promise; static create(accessor: ServicesAccessor, context: 'view'): Promise; - static create(accessor: ServicesAccessor, context: 'focussed'): Promise; - static async create(accessor: ServicesAccessor, context: 'inline' | 'quick' | 'view' | 'focussed'): Promise { + static create(accessor: ServicesAccessor, context: 'focused'): Promise; + static async create(accessor: ServicesAccessor, context: 'inline' | 'quick' | 'view' | 'focused'): Promise { const chatWidgetService = accessor.get(IChatWidgetService); const chatService = accessor.get(IChatService); const viewsService = accessor.get(IViewsService); @@ -73,8 +74,8 @@ class VoiceChatSessionControllerFactory { const quickChatService = accessor.get(IQuickChatService); const layoutService = accessor.get(IWorkbenchLayoutService); - // Currently Focussed Context - if (context === 'focussed') { + // Currently Focused Context + if (context === 'focused') { // Try with the chat widget service, which currently // only supports the chat view and quick chat @@ -159,7 +160,9 @@ class VoiceChatSessionControllerFactory { onDidCancelInput: Event.filter(viewsService.onDidChangeViewVisibility, e => e.id === chatContributionService.getViewIdForProvider(chatView.providerId)), focusInput: () => chatView.focusInput(), acceptInput: () => chatView.acceptInput(), - updateInput: text => chatView.updateInput(text) + updateInput: text => chatView.updateInput(text), + setInputPlaceholder: text => chatView.setInputPlaceholder(text), + clearInputPlaceholder: () => chatView.resetInputPlaceholder() }; } @@ -170,7 +173,9 @@ class VoiceChatSessionControllerFactory { onDidCancelInput: quickChatService.onDidClose, focusInput: () => quickChat.focusInput(), acceptInput: () => quickChat.acceptInput(), - updateInput: text => quickChat.updateInput(text) + updateInput: text => quickChat.updateInput(text), + setInputPlaceholder: text => quickChat.setInputPlaceholder(text), + clearInputPlaceholder: () => quickChat.resetInputPlaceholder() }; } @@ -186,12 +191,15 @@ class VoiceChatSessionControllerFactory { ), focusInput: () => inlineChat.focus(), acceptInput: () => inlineChat.acceptInput(), - updateInput: text => inlineChat.updateInput(text) + updateInput: text => inlineChat.updateInput(text), + setInputPlaceholder: text => inlineChat.setPlaceholder(text), + clearInputPlaceholder: () => inlineChat.resetPlaceholder() }; } } interface ActiveVoiceChatSession { + readonly id: number; readonly controller: IVoiceChatSessionController; readonly disposables: DisposableStore; } @@ -220,37 +228,64 @@ class VoiceChatSessions { constructor( @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IWorkbenchVoiceRecognitionService private readonly voiceRecognitionService: IWorkbenchVoiceRecognitionService + @ISpeechService private readonly speechService: ISpeechService ) { } async start(controller: IVoiceChatSessionController): Promise { this.stop(); - const voiceChatSessionId = ++this.voiceChatSessionIds; - this.currentVoiceChatSession = { + const sessionId = ++this.voiceChatSessionIds; + const session = this.currentVoiceChatSession = { + id: sessionId, controller, disposables: new DisposableStore() }; const cts = new CancellationTokenSource(); - this.currentVoiceChatSession.disposables.add(toDisposable(() => cts.dispose(true))); + session.disposables.add(toDisposable(() => cts.dispose(true))); - this.currentVoiceChatSession.disposables.add(controller.onDidAcceptInput(() => this.stop(voiceChatSessionId, controller.context))); - this.currentVoiceChatSession.disposables.add(controller.onDidCancelInput(() => this.stop(voiceChatSessionId, controller.context))); + session.disposables.add(controller.onDidAcceptInput(() => this.stop(sessionId, controller.context))); + session.disposables.add(controller.onDidCancelInput(() => this.stop(sessionId, controller.context))); controller.updateInput(''); controller.focusInput(); this.voiceChatGettingReadyKey.set(true); - const onDidTranscribe = await this.voiceRecognitionService.transcribe(cts.token, { - onDidCancel: () => this.stop(voiceChatSessionId, controller.context) - }); + const speechToTextSession = session.disposables.add(this.speechService.createSpeechToTextSession(cts.token)); - if (cts.token.isCancellationRequested) { - return; - } + let transcription: string = ''; + const acceptTranscriptionScheduler = session.disposables.add(new RunOnceScheduler(() => session.controller.acceptInput(), 2000)); + session.disposables.add(speechToTextSession.onDidChange(({ status, text }) => { + if (cts.token.isCancellationRequested) { + return; + } + + switch (status) { + case SpeechToTextStatus.Started: + this.onDidSpeechToTextSessionStart(controller, session.disposables); + break; + case SpeechToTextStatus.Recognizing: + if (text) { + session.controller.updateInput([transcription, text].join(' ')); + acceptTranscriptionScheduler.cancel(); + } + break; + case SpeechToTextStatus.Recognized: + if (text) { + transcription = [transcription, text].join(' '); + session.controller.updateInput(transcription); + acceptTranscriptionScheduler.schedule(); + } + break; + case SpeechToTextStatus.Stopped: + this.stop(session.id, controller.context); + break; + } + })); + } + private onDidSpeechToTextSessionStart(controller: IVoiceChatSessionController, disposables: DisposableStore): void { this.voiceChatGettingReadyKey.set(false); this.voiceChatInProgressKey.set(true); @@ -269,47 +304,16 @@ class VoiceChatSessions { break; } - this.registerTranscriptionListener(this.currentVoiceChatSession, onDidTranscribe); - } - - private registerTranscriptionListener(session: ActiveVoiceChatSession, onDidTranscribe: Event) { - let lastText: string | undefined = undefined; - let lastTextSimilarCount = 0; - - session.disposables.add(onDidTranscribe(text => { - if (!text && lastText) { - text = lastText; - } - - if (text) { - if (lastText && this.isSimilarTranscription(text, lastText)) { - lastTextSimilarCount++; - } else { - lastTextSimilarCount = 0; - lastText = text; - } - - if (lastTextSimilarCount >= 2) { - session.controller.acceptInput(); - } else { - session.controller.updateInput(text); - } - } - })); - } - - private isSimilarTranscription(textA: string, textB: string): boolean { + let dotCount = 0; - // Attempt to compare the 2 strings in a way to see - // if they are similar or not. As such we: - // - ignore trailing punctuation - // - collapse all whitespace - // - compare case insensitive + const updatePlaceholder = () => { + dotCount = (dotCount + 1) % 4; + controller.setInputPlaceholder(`${localize('listening', "I'm listening")}${'.'.repeat(dotCount)}`); + placeholderScheduler.schedule(); + }; - return equalsIgnoreCase( - textA.replace(/[.,;:!?]+$/, '').replace(/\s+/g, ''), - textB.replace(/[.,;:!?]+$/, '').replace(/\s+/g, '') - ); + const placeholderScheduler = disposables.add(new RunOnceScheduler(updatePlaceholder, 500)); + updatePlaceholder(); } stop(voiceChatSessionId = this.voiceChatSessionIds, context?: VoiceChatSessionContext): void { @@ -321,6 +325,8 @@ class VoiceChatSessions { return; } + this.currentVoiceChatSession.controller.clearInputPlaceholder(); + this.currentVoiceChatSession.disposables.dispose(); this.currentVoiceChatSession = undefined; @@ -345,7 +351,7 @@ class VoiceChatSessions { } } -class VoiceChatInChatViewAction extends Action2 { +export class VoiceChatInChatViewAction extends Action2 { static readonly ID = 'workbench.action.chat.voiceChatInChatView'; @@ -372,7 +378,7 @@ class VoiceChatInChatViewAction extends Action2 { } } -class InlineVoiceChatAction extends Action2 { +export class InlineVoiceChatAction extends Action2 { static readonly ID = 'workbench.action.chat.inlineVoiceChat'; @@ -399,7 +405,7 @@ class InlineVoiceChatAction extends Action2 { } } -class QuickVoiceChatAction extends Action2 { +export class QuickVoiceChatAction extends Action2 { static readonly ID = 'workbench.action.chat.quickVoiceChat'; @@ -426,7 +432,7 @@ class QuickVoiceChatAction extends Action2 { } } -class StartVoiceChatAction extends Action2 { +export class StartVoiceChatAction extends Action2 { static readonly ID = 'workbench.action.chat.startVoiceChat'; @@ -468,7 +474,7 @@ class StartVoiceChatAction extends Action2 { context.widget.focusInput(); } - const controller = await VoiceChatSessionControllerFactory.create(accessor, 'focussed'); + const controller = await VoiceChatSessionControllerFactory.create(accessor, 'focused'); if (controller) { VoiceChatSessions.getInstance(instantiationService).start(controller); } else { @@ -478,7 +484,7 @@ class StartVoiceChatAction extends Action2 { } } -class StopVoiceChatAction extends Action2 { +export class StopVoiceChatAction extends Action2 { static readonly ID = 'workbench.action.chat.stopVoiceChat'; @@ -505,7 +511,7 @@ class StopVoiceChatAction extends Action2 { } } -class StopVoiceChatInChatViewAction extends Action2 { +export class StopVoiceChatInChatViewAction extends Action2 { static readonly ID = 'workbench.action.chat.stopVoiceChatInChatView'; @@ -538,7 +544,7 @@ class StopVoiceChatInChatViewAction extends Action2 { } } -class StopVoiceChatInChatEditorAction extends Action2 { +export class StopVoiceChatInChatEditorAction extends Action2 { static readonly ID = 'workbench.action.chat.stopVoiceChatInChatEditor'; @@ -571,7 +577,7 @@ class StopVoiceChatInChatEditorAction extends Action2 { } } -class StopQuickVoiceChatAction extends Action2 { +export class StopQuickVoiceChatAction extends Action2 { static readonly ID = 'workbench.action.chat.stopQuickVoiceChat'; @@ -604,7 +610,7 @@ class StopQuickVoiceChatAction extends Action2 { } } -class StopInlineVoiceChatAction extends Action2 { +export class StopInlineVoiceChatAction extends Action2 { static readonly ID = 'workbench.action.chat.stopInlineVoiceChat'; @@ -637,7 +643,7 @@ class StopInlineVoiceChatAction extends Action2 { } } -class StopVoiceChatAndSubmitAction extends Action2 { +export class StopVoiceChatAndSubmitAction extends Action2 { static readonly ID = 'workbench.action.chat.stopVoiceChatAndSubmit'; @@ -658,20 +664,3 @@ class StopVoiceChatAndSubmitAction extends Action2 { VoiceChatSessions.getInstance(accessor.get(IInstantiationService)).accept(); } } - -export function registerVoiceChatActions() { - if (typeof process.env.VSCODE_VOICE_MODULE_PATH === 'string' && product.quality !== 'stable') { // TODO@bpasero package - registerAction2(VoiceChatInChatViewAction); - registerAction2(QuickVoiceChatAction); - registerAction2(InlineVoiceChatAction); - - registerAction2(StartVoiceChatAction); - registerAction2(StopVoiceChatAction); - registerAction2(StopVoiceChatAndSubmitAction); - - registerAction2(StopVoiceChatInChatViewAction); - registerAction2(StopVoiceChatInChatEditorAction); - registerAction2(StopQuickVoiceChatAction); - registerAction2(StopInlineVoiceChatAction); - } -} diff --git a/code/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts b/code/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts index 1bd74f66eff..409022e8651 100644 --- a/code/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts +++ b/code/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts @@ -3,6 +3,39 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { registerVoiceChatActions } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions'; +import { InlineVoiceChatAction, QuickVoiceChatAction, StartVoiceChatAction, StopInlineVoiceChatAction, StopQuickVoiceChatAction, StopVoiceChatAction, StopVoiceChatAndSubmitAction, StopVoiceChatInChatEditorAction, StopVoiceChatInChatViewAction, VoiceChatInChatViewAction } from 'vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { ISpeechService } from 'vs/workbench/contrib/speech/common/speechService'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { Event } from 'vs/base/common/event'; +import { registerAction2 } from 'vs/platform/actions/common/actions'; -registerVoiceChatActions(); +function registerVoiceChatActions(): void { + registerAction2(VoiceChatInChatViewAction); + registerAction2(QuickVoiceChatAction); + registerAction2(InlineVoiceChatAction); + + registerAction2(StartVoiceChatAction); + registerAction2(StopVoiceChatAction); + registerAction2(StopVoiceChatAndSubmitAction); + + registerAction2(StopVoiceChatInChatViewAction); + registerAction2(StopVoiceChatInChatEditorAction); + registerAction2(StopQuickVoiceChatAction); + registerAction2(StopInlineVoiceChatAction); +} + +class VoiceChatActionsContributor extends Disposable implements IWorkbenchContribution { + + constructor(@ISpeechService speechService: ISpeechService) { + super(); + + this._register(Event.once(speechService.onDidRegisterSpeechProvider)(() => { + registerVoiceChatActions(); + })); + } +} + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(VoiceChatActionsContributor, LifecyclePhase.Restored); diff --git a/code/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_not_first.0.snap b/code/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_not_first.0.snap index a6a8d0d1516..0ac17204ee0 100644 --- a/code/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_not_first.0.snap +++ b/code/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_not_first.0.snap @@ -25,14 +25,8 @@ endLineNumber: 1, endColumn: 17 }, - agent: { - id: "agent", - metadata: { - description: "", - subCommands: [ { name: "subCommand" } ] - } - }, - kind: "agent" + text: "@agent", + kind: "text" }, { range: { @@ -59,8 +53,8 @@ endLineNumber: 1, endColumn: 29 }, - command: { name: "subCommand" }, - kind: "subcommand" + text: "/subCommand", + kind: "text" }, { range: { diff --git a/code/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_question_mark.0.snap b/code/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_question_mark.0.snap index 9c3a2372627..65e2aa78ac0 100644 --- a/code/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_question_mark.0.snap +++ b/code/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_question_mark.0.snap @@ -3,51 +3,35 @@ { range: { start: 0, - endExclusive: 14 + endExclusive: 6 }, editorRange: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, - endColumn: 15 - }, - text: "Are you there ", - kind: "text" - }, - { - range: { - start: 14, - endExclusive: 20 - }, - editorRange: { - startLineNumber: 1, - startColumn: 15, - endLineNumber: 1, - endColumn: 21 + endColumn: 7 }, agent: { id: "agent", - metadata: { - description: "", - subCommands: [ { name: "subCommand" } ] - } + metadata: { description: "" }, + provideSlashCommands: [Function provideSlashCommands] }, kind: "agent" }, { range: { - start: 20, + start: 6, endExclusive: 21 }, editorRange: { startLineNumber: 1, - startColumn: 21, + startColumn: 7, endLineNumber: 1, endColumn: 22 }, - text: "?", + text: "? Are you there", kind: "text" } ], - text: "Are you there @agent?" + text: "@agent? Are you there" } \ No newline at end of file diff --git a/code/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents.0.snap b/code/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents.0.snap index f89e75eabf3..8a83800323f 100644 --- a/code/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents.0.snap +++ b/code/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents.0.snap @@ -13,10 +13,8 @@ }, agent: { id: "agent", - metadata: { - description: "", - subCommands: [ { name: "subCommand" } ] - } + metadata: { description: "" }, + provideSlashCommands: [Function provideSlashCommands] }, kind: "agent" }, @@ -45,8 +43,8 @@ endLineNumber: 1, endColumn: 29 }, - command: { name: "subCommand" }, - kind: "subcommand" + text: "/subCommand", + kind: "text" }, { range: { diff --git a/code/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents__subCommand.0.snap b/code/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents__subCommand.0.snap new file mode 100644 index 00000000000..ca9a0569fcd --- /dev/null +++ b/code/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents__subCommand.0.snap @@ -0,0 +1,68 @@ +{ + parts: [ + { + range: { + start: 0, + endExclusive: 6 + }, + editorRange: { + startLineNumber: 1, + startColumn: 1, + endLineNumber: 1, + endColumn: 7 + }, + agent: { + id: "agent", + metadata: { description: "" }, + provideSlashCommands: [Function provideSlashCommands] + }, + kind: "agent" + }, + { + range: { + start: 6, + endExclusive: 7 + }, + editorRange: { + startLineNumber: 1, + startColumn: 7, + endLineNumber: 1, + endColumn: 8 + }, + text: " ", + kind: "text" + }, + { + range: { + start: 7, + endExclusive: 18 + }, + editorRange: { + startLineNumber: 1, + startColumn: 8, + endLineNumber: 1, + endColumn: 19 + }, + command: { + name: "subCommand", + description: "" + }, + kind: "subcommand" + }, + { + range: { + start: 18, + endExclusive: 35 + }, + editorRange: { + startLineNumber: 1, + startColumn: 19, + endLineNumber: 1, + endColumn: 36 + }, + text: " Please do thanks", + kind: "text" + } + ], + text: "@agent /subCommand Please do thanks" +} \ No newline at end of file diff --git a/code/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline.0.snap b/code/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline.0.snap index 27a7c90ce8c..750f1bc39f6 100644 --- a/code/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline.0.snap +++ b/code/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline.0.snap @@ -13,53 +13,54 @@ }, agent: { id: "agent", - metadata: { - description: "", - subCommands: [ { name: "subCommand" } ] - } + metadata: { description: "" }, + provideSlashCommands: [Function provideSlashCommands] }, kind: "agent" }, { range: { start: 6, - endExclusive: 18 + endExclusive: 7 }, editorRange: { startLineNumber: 1, startColumn: 7, - endLineNumber: 2, - endColumn: 4 + endLineNumber: 1, + endColumn: 8 }, - text: " Please \ndo ", + text: " ", kind: "text" }, { range: { - start: 18, - endExclusive: 29 + start: 7, + endExclusive: 18 }, editorRange: { - startLineNumber: 2, - startColumn: 4, - endLineNumber: 2, - endColumn: 15 + startLineNumber: 1, + startColumn: 8, + endLineNumber: 1, + endColumn: 19 + }, + command: { + name: "subCommand", + description: "" }, - command: { name: "subCommand" }, kind: "subcommand" }, { range: { - start: 29, + start: 18, endExclusive: 35 }, editorRange: { - startLineNumber: 2, - startColumn: 15, + startLineNumber: 1, + startColumn: 19, endLineNumber: 2, - endColumn: 21 + endColumn: 16 }, - text: " with ", + text: " \nPlease do with ", kind: "text" }, { @@ -69,9 +70,9 @@ }, editorRange: { startLineNumber: 2, - startColumn: 21, + startColumn: 16, endLineNumber: 2, - endColumn: 31 + endColumn: 26 }, variableName: "selection", variableArg: "", @@ -84,7 +85,7 @@ }, editorRange: { startLineNumber: 2, - startColumn: 31, + startColumn: 26, endLineNumber: 3, endColumn: 5 }, @@ -107,5 +108,5 @@ kind: "var" } ], - text: "@agent Please \ndo /subCommand with #selection\nand #debugConsole" + text: "@agent /subCommand \nPlease do with #selection\nand #debugConsole" } \ No newline at end of file diff --git a/code/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline__part2.0.snap b/code/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline__part2.0.snap new file mode 100644 index 00000000000..3708cf78541 --- /dev/null +++ b/code/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline__part2.0.snap @@ -0,0 +1,109 @@ +{ + parts: [ + { + range: { + start: 0, + endExclusive: 6 + }, + editorRange: { + startLineNumber: 1, + startColumn: 1, + endLineNumber: 1, + endColumn: 7 + }, + agent: { + id: "agent", + metadata: { description: "" }, + provideSlashCommands: [Function provideSlashCommands] + }, + kind: "agent" + }, + { + range: { + start: 6, + endExclusive: 18 + }, + editorRange: { + startLineNumber: 1, + startColumn: 7, + endLineNumber: 2, + endColumn: 4 + }, + text: " Please \ndo ", + kind: "text" + }, + { + range: { + start: 18, + endExclusive: 29 + }, + editorRange: { + startLineNumber: 2, + startColumn: 4, + endLineNumber: 2, + endColumn: 15 + }, + text: "/subCommand", + kind: "text" + }, + { + range: { + start: 29, + endExclusive: 35 + }, + editorRange: { + startLineNumber: 2, + startColumn: 15, + endLineNumber: 2, + endColumn: 21 + }, + text: " with ", + kind: "text" + }, + { + range: { + start: 35, + endExclusive: 45 + }, + editorRange: { + startLineNumber: 2, + startColumn: 21, + endLineNumber: 2, + endColumn: 31 + }, + variableName: "selection", + variableArg: "", + kind: "var" + }, + { + range: { + start: 45, + endExclusive: 50 + }, + editorRange: { + startLineNumber: 2, + startColumn: 31, + endLineNumber: 3, + endColumn: 5 + }, + text: "\nand ", + kind: "text" + }, + { + range: { + start: 50, + endExclusive: 63 + }, + editorRange: { + startLineNumber: 3, + startColumn: 5, + endLineNumber: 3, + endColumn: 18 + }, + variableName: "debugConsole", + variableArg: "", + kind: "var" + } + ], + text: "@agent Please \ndo /subCommand with #selection\nand #debugConsole" +} \ No newline at end of file diff --git a/code/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap b/code/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap index 8a055fa5157..cdc587a8cad 100644 --- a/code/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap +++ b/code/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap @@ -26,14 +26,7 @@ } ] }, - response: [ - { - value: "", - isTrusted: false, - supportThemeIcons: false, - supportHtml: false - } - ], + response: [ ], responseErrorDetails: undefined, followups: undefined, isCanceled: false, @@ -60,7 +53,8 @@ } ] } - ] } + ] }, + contentReferences: [ ] } ], providerId: "ChatProviderWithUsedContext", diff --git a/code/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap b/code/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap index 4abd4c09014..1d0f757b587 100644 --- a/code/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap +++ b/code/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap @@ -26,14 +26,7 @@ ], text: "test request" }, - response: [ - { - value: "", - isTrusted: false, - supportThemeIcons: false, - supportHtml: false - } - ], + response: [ ], responseErrorDetails: undefined, followups: undefined, isCanceled: false, @@ -60,7 +53,8 @@ } ] } - ] } + ] }, + contentReferences: [ ] } ], providerId: "ChatProviderWithUsedContext", diff --git a/code/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts b/code/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts index 3a6bbd8843f..fdd44be5c58 100644 --- a/code/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts +++ b/code/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts @@ -38,16 +38,64 @@ suite('ChatModel', () => { await timeout(0); assert.strictEqual(hasInitialized, false); + model.startInitialize(); model.initialize({} as any, undefined); await timeout(0); assert.strictEqual(hasInitialized, true); }); + test('must call startInitialize before initialize', async () => { + const model = testDisposables.add(instantiationService.createInstance(ChatModel, 'provider', undefined)); + + let hasInitialized = false; + model.waitForInitialization().then(() => { + hasInitialized = true; + }); + + await timeout(0); + assert.strictEqual(hasInitialized, false); + + assert.throws(() => model.initialize({} as any, undefined)); + assert.strictEqual(hasInitialized, false); + }); + + test('deinitialize/reinitialize', async () => { + const model = testDisposables.add(instantiationService.createInstance(ChatModel, 'provider', undefined)); + + let hasInitialized = false; + model.waitForInitialization().then(() => { + hasInitialized = true; + }); + + model.startInitialize(); + model.initialize({} as any, undefined); + await timeout(0); + assert.strictEqual(hasInitialized, true); + + model.deinitialize(); + let hasInitialized2 = false; + model.waitForInitialization().then(() => { + hasInitialized2 = true; + }); + + model.startInitialize(); + model.initialize({} as any, undefined); + await timeout(0); + assert.strictEqual(hasInitialized2, true); + }); + + test('cannot initialize twice', async () => { + const model = testDisposables.add(instantiationService.createInstance(ChatModel, 'provider', undefined)); + + model.startInitialize(); + model.initialize({} as any, undefined); + assert.throws(() => model.initialize({} as any, undefined)); + }); + test('Initialization fails when model is disposed', async () => { const model = testDisposables.add(instantiationService.createInstance(ChatModel, 'provider', undefined)); model.dispose(); - await assert.rejects(() => model.waitForInitialization()); + assert.throws(() => model.initialize({} as any, undefined)); }); }); - diff --git a/code/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts b/code/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts index 90b31b1ace9..78317a10496 100644 --- a/code/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts +++ b/code/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts @@ -9,7 +9,7 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/uti import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { ChatAgentService, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { ChatAgentService, IChatAgent, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; @@ -111,7 +111,7 @@ suite('ChatRequestParser', () => { test('agents', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns({ id: 'agent', metadata: { description: '', subCommands: [{ name: 'subCommand' }] } }); + agentsService.getAgent.returns(>{ id: 'agent', metadata: { description: '' }, provideSlashCommands: async () => { return [{ name: 'subCommand', description: '' }]; } }); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -119,19 +119,29 @@ suite('ChatRequestParser', () => { await assertSnapshot(result); }); + test('agents, subCommand', async () => { + const agentsService = mockObject()({}); + agentsService.getAgent.returns(>{ id: 'agent', metadata: { description: '' }, provideSlashCommands: async () => { return [{ name: 'subCommand', description: '' }]; } }); + instantiationService.stub(IChatAgentService, agentsService as any); + + parser = instantiationService.createInstance(ChatRequestParser); + const result = await parser.parseChatRequest('1', '@agent /subCommand Please do thanks'); + await assertSnapshot(result); + }); + test('agent with question mark', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns({ id: 'agent', metadata: { description: '', subCommands: [{ name: 'subCommand' }] } }); + agentsService.getAgent.returns(>{ id: 'agent', metadata: { description: '' }, provideSlashCommands: async () => { return [{ name: 'subCommand', description: '' }]; } }); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); - const result = await parser.parseChatRequest('1', 'Are you there @agent?'); + const result = await parser.parseChatRequest('1', '@agent? Are you there'); await assertSnapshot(result); }); test('agent not first', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns({ id: 'agent', metadata: { description: '', subCommands: [{ name: 'subCommand' }] } }); + agentsService.getAgent.returns(>{ id: 'agent', metadata: { description: '' }, provideSlashCommands: async () => { return [{ name: 'subCommand', description: '' }]; } }); instantiationService.stub(IChatAgentService, agentsService as any); parser = instantiationService.createInstance(ChatRequestParser); @@ -141,7 +151,21 @@ suite('ChatRequestParser', () => { test('agents and variables and multiline', async () => { const agentsService = mockObject()({}); - agentsService.getAgent.returns({ id: 'agent', metadata: { description: '', subCommands: [{ name: 'subCommand' }] } }); + agentsService.getAgent.returns(>{ id: 'agent', metadata: { description: '' }, provideSlashCommands: async () => { return [{ name: 'subCommand', description: '' }]; } }); + instantiationService.stub(IChatAgentService, agentsService as any); + + const variablesService = mockObject()({}); + variablesService.hasVariable.returns(true); + instantiationService.stub(IChatVariablesService, variablesService as any); + + parser = instantiationService.createInstance(ChatRequestParser); + const result = await parser.parseChatRequest('1', '@agent /subCommand \nPlease do with #selection\nand #debugConsole'); + await assertSnapshot(result); + }); + + test('agents and variables and multiline, part2', async () => { + const agentsService = mockObject()({}); + agentsService.getAgent.returns(>{ id: 'agent', metadata: { description: '' }, provideSlashCommands: async () => { return [{ name: 'subCommand', description: '' }]; } }); instantiationService.stub(IChatAgentService, agentsService as any); const variablesService = mockObject()({}); @@ -153,4 +177,3 @@ suite('ChatRequestParser', () => { await assertSnapshot(result); }); }); - diff --git a/code/src/vs/workbench/contrib/codeActions/browser/codeActionsContribution.ts b/code/src/vs/workbench/contrib/codeActions/browser/codeActionsContribution.ts index 7778d948f17..8adbf6243a2 100644 --- a/code/src/vs/workbench/contrib/codeActions/browser/codeActionsContribution.ts +++ b/code/src/vs/workbench/contrib/codeActions/browser/codeActionsContribution.ts @@ -51,8 +51,8 @@ const codeActionsOnSaveSchema: IConfigurationPropertySchema = { items: { type: 'string' } } ], - markdownDescription: nls.localize('editor.codeActionsOnSave', 'Run CodeActions for the editor on save. CodeActions must be specified and the editor must not be shutting down. Example: `"source.organizeImports": "explicit" `'), - type: 'object', + markdownDescription: nls.localize('editor.codeActionsOnSave', 'Run Code Actions for the editor on save. Code Actions must be specified and the editor must not be shutting down. Example: `"source.organizeImports": "explicit" `'), + type: ['object', 'array'], additionalProperties: { type: ['string', 'boolean'], enum: ['always', 'explicit', 'never', true, false], diff --git a/code/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.css b/code/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.css index f8044ced3b2..8b7c1096017 100644 --- a/code/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.css +++ b/code/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.css @@ -52,3 +52,9 @@ background-repeat: no-repeat; padding: 2px; } + +.accessible-view.hide { + position: fixed; + top: -2000px; + left:-2000px; +} diff --git a/code/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts b/code/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts index 0a16cc4822a..6d5868c8d33 100644 --- a/code/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts +++ b/code/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts @@ -31,6 +31,8 @@ import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IOutlineModelService } from 'vs/editor/contrib/documentSymbols/browser/outlineModel'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { accessibilityHelpIsShown, accessibleViewIsShown } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccessProvider { @@ -248,7 +250,7 @@ class GotoSymbolAction extends Action2 { }, f1: true, keybinding: { - when: undefined, + when: ContextKeyExpr.and(accessibleViewIsShown.negate(), accessibilityHelpIsShown.negate()), weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyO }, diff --git a/code/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts b/code/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts index 3ff57c9ceb3..83a75496d49 100644 --- a/code/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts +++ b/code/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts @@ -281,7 +281,7 @@ class CodeActionOnSaveParticipant implements ITextFileSaveParticipant { const settingsOverrides = { overrideIdentifier: textEditorModel.getLanguageId(), resource: textEditorModel.uri }; // Convert boolean values to strings - const setting = this.configurationService.getValue<{ [kind: string]: string | boolean }>('editor.codeActionsOnSave', settingsOverrides); + const setting = this.configurationService.getValue<{ [kind: string]: string | boolean } | string[]>('editor.codeActionsOnSave', settingsOverrides); if (!setting) { return undefined; } @@ -290,16 +290,15 @@ class CodeActionOnSaveParticipant implements ITextFileSaveParticipant { return undefined; } - const convertedSetting: { [kind: string]: string } = {}; - for (const key in setting) { - if (typeof setting[key] === 'boolean') { - convertedSetting[key] = setting[key] ? 'explicit' : 'never'; - } else if (typeof setting[key] === 'string') { - convertedSetting[key] = setting[key] as string; - } + if (env.reason !== SaveReason.EXPLICIT && Array.isArray(setting)) { + return undefined; } - const codeActionsOnSave = this.createCodeActionsOnSave(Object.keys(convertedSetting)); + const settingItems: string[] = Array.isArray(setting) + ? setting + : Object.keys(setting).filter(x => setting[x] && setting[x] !== 'never'); + + const codeActionsOnSave = this.createCodeActionsOnSave(settingItems); if (!Array.isArray(setting)) { codeActionsOnSave.sort((a, b) => { @@ -319,14 +318,15 @@ class CodeActionOnSaveParticipant implements ITextFileSaveParticipant { if (!codeActionsOnSave.length) { return undefined; } - - const excludedActions = Object.keys(setting) - .filter(x => convertedSetting[x] === 'never' || false) - .map(x => new CodeActionKind(x)); + const excludedActions = Array.isArray(setting) + ? [] + : Object.keys(setting) + .filter(x => setting[x] === 'never' || false) + .map(x => new CodeActionKind(x)); progress.report({ message: localize('codeaction', "Quick Fixes") }); - const filteredSaveList = codeActionsOnSave.filter(x => convertedSetting[x.value] === 'always' || (convertedSetting[x.value] === 'explicit') && env.reason === SaveReason.EXPLICIT); + const filteredSaveList = Array.isArray(setting) ? codeActionsOnSave : codeActionsOnSave.filter(x => setting[x.value] === 'always' || ((setting[x.value] === 'explicit' || setting[x.value] === true) && env.reason === SaveReason.EXPLICIT)); await this.applyOnSaveActions(textEditorModel, filteredSaveList, excludedActions, progress, token); } diff --git a/code/src/vs/workbench/contrib/comments/browser/commentReply.ts b/code/src/vs/workbench/contrib/comments/browser/commentReply.ts index 18bf8527b6c..d4a74b20f40 100644 --- a/code/src/vs/workbench/contrib/comments/browser/commentReply.ts +++ b/code/src/vs/workbench/contrib/comments/browser/commentReply.ts @@ -106,7 +106,7 @@ export class CommentReply extends Disposable { // Only add the additional step of clicking a reply button to expand the textarea when there are existing comments if (hasExistingComments) { this.createReplyButton(this.commentEditor, this.form); - } else if (this._commentThread.comments && this._commentThread.comments.length === 0) { + } else if ((this._commentThread.comments && this._commentThread.comments.length === 0) || this._pendingComment) { this.expandReplyArea(); } this._error = dom.append(this.form, dom.$('.validation-error.hidden')); @@ -152,6 +152,12 @@ export class CommentReply extends Disposable { return undefined; } + public setPendingComment(comment: string) { + this._pendingComment = comment; + this.expandReplyArea(); + this.commentEditor.setValue(comment); + } + public layout(widthInPixel: number) { this.commentEditor.layout({ height: this._editorHeight, width: widthInPixel - 54 /* margin 20px * 10 + scrollbar 14px*/ }); } @@ -278,9 +284,9 @@ export class CommentReply extends Disposable { })); this._commentFormActions = new CommentFormActions(container, async (action: IAction) => { - this._actionRunDelegate?.(); + await this._actionRunDelegate?.(); - action.run({ + await action.run({ thread: this._commentThread, text: this.commentEditor.getValue(), $mid: MarshalledId.CommentThreadReply diff --git a/code/src/vs/workbench/contrib/comments/browser/commentService.ts b/code/src/vs/workbench/contrib/comments/browser/commentService.ts index a1547de0921..63b91ec40ad 100644 --- a/code/src/vs/workbench/contrib/comments/browser/commentService.ts +++ b/code/src/vs/workbench/contrib/comments/browser/commentService.ts @@ -58,7 +58,7 @@ export interface ICommentController { }; options?: CommentOptions; contextValue?: string; - createCommentThreadTemplate(resource: UriComponents, range: IRange | undefined): void; + createCommentThreadTemplate(resource: UriComponents, range: IRange | undefined): Promise; updateCommentThreadTemplate(threadHandle: number, range: IRange): Promise; deleteCommentThreadMain(commentThreadId: string): void; toggleReaction(uri: URI, thread: CommentThread, comment: Comment, reaction: CommentReaction, token: CancellationToken): Promise; @@ -90,7 +90,7 @@ export interface ICommentService { registerCommentController(owner: string, commentControl: ICommentController): void; unregisterCommentController(owner?: string): void; getCommentController(owner: string): ICommentController | undefined; - createCommentThreadTemplate(owner: string, resource: URI, range: Range | undefined): void; + createCommentThreadTemplate(owner: string, resource: URI, range: Range | undefined): Promise; updateCommentThreadTemplate(owner: string, threadHandle: number, range: Range): Promise; getCommentMenus(owner: string): CommentMenus; updateComments(ownerId: string, event: CommentThreadChangedEvent): void; @@ -105,7 +105,7 @@ export interface ICommentService { setCurrentCommentThread(commentThread: CommentThread | undefined): void; enableCommenting(enable: boolean): void; registerContinueOnCommentProvider(provider: IContinueOnCommentProvider): IDisposable; - removeContinueOnComment(pendingComment: { range: IRange; uri: URI; owner: string }): PendingCommentThread | undefined; + removeContinueOnComment(pendingComment: { range: IRange; uri: URI; owner: string; isReply?: boolean }): PendingCommentThread | undefined; } const CONTINUE_ON_COMMENTS = 'comments.continueOnComments'; @@ -174,10 +174,8 @@ export class CommentService extends Disposable implements ICommentService { this._workspaceHasCommenting = CommentContextKeys.WorkspaceHasCommenting.bindTo(contextKeyService); const storageListener = this._register(new DisposableStore()); - storageListener.add(this.storageService.onDidChangeValue(StorageScope.WORKSPACE, CONTINUE_ON_COMMENTS, storageListener)((v) => { - if (!this.configurationService.getValue(COMMENTS_SECTION)?.experimentalContinueOn) { - return; - } + const storageEvent = Event.debounce(this.storageService.onDidChangeValue(StorageScope.WORKSPACE, CONTINUE_ON_COMMENTS, storageListener), (last, event) => last?.external ? last : event, 500); + storageListener.add(storageEvent(v => { if (!v.external) { return; } @@ -186,7 +184,7 @@ export class CommentService extends Disposable implements ICommentService { return; } this.logService.debug(`Comments: URIs of continue on comments from storage ${commentsToRestore.map(thread => thread.uri.toString()).join(', ')}.`); - const changedOwners = this._addContinueOnComments(commentsToRestore); + const changedOwners = this._addContinueOnComments(commentsToRestore, this._continueOnComments); for (const owner of changedOwners) { const evt: ICommentThreadChangedEvent = { owner, @@ -199,14 +197,12 @@ export class CommentService extends Disposable implements ICommentService { } })); this._register(storageService.onWillSaveState(() => { - if (!this.configurationService.getValue(COMMENTS_SECTION)?.experimentalContinueOn) { - return; - } + const map: Map = new Map(); for (const provider of this._continueOnCommentProviders) { const pendingComments = provider.provideContinueOnComments(); - this._addContinueOnComments(pendingComments); + this._addContinueOnComments(pendingComments, map); } - this._saveContinueOnComments(); + this._saveContinueOnComments(map); })); } @@ -298,14 +294,14 @@ export class CommentService extends Disposable implements ICommentService { return this._commentControls.get(owner); } - createCommentThreadTemplate(owner: string, resource: URI, range: Range | undefined): void { + async createCommentThreadTemplate(owner: string, resource: URI, range: Range | undefined): Promise { const commentController = this._commentControls.get(owner); if (!commentController) { return; } - commentController.createCommentThreadTemplate(resource, range); + return commentController.createCommentThreadTemplate(resource, range); } async updateCommentThreadTemplate(owner: string, threadHandle: number, range: Range) { @@ -415,19 +411,19 @@ export class CommentService extends Disposable implements ICommentService { }; } - private _saveContinueOnComments() { + private _saveContinueOnComments(map: Map) { const commentsToSave: PendingCommentThread[] = []; - for (const pendingComments of this._continueOnComments.values()) { + for (const pendingComments of map.values()) { commentsToSave.push(...pendingComments); } this.logService.debug(`Comments: URIs of continue on comments to add to storage ${commentsToSave.map(thread => thread.uri.toString()).join(', ')}.`); this.storageService.store(CONTINUE_ON_COMMENTS, commentsToSave, StorageScope.WORKSPACE, StorageTarget.USER); } - removeContinueOnComment(pendingComment: { range: IRange; uri: URI; owner: string }): PendingCommentThread | undefined { + removeContinueOnComment(pendingComment: { range: IRange; uri: URI; owner: string; isReply?: boolean }): PendingCommentThread | undefined { const pendingComments = this._continueOnComments.get(pendingComment.owner); if (pendingComments) { - const commentIndex = pendingComments.findIndex(comment => comment.uri.toString() === pendingComment.uri.toString() && Range.equalsRange(comment.range, pendingComment.range)); + const commentIndex = pendingComments.findIndex(comment => comment.uri.toString() === pendingComment.uri.toString() && Range.equalsRange(comment.range, pendingComment.range) && (pendingComment.isReply === undefined || comment.isReply === pendingComment.isReply)); if (commentIndex > -1) { return pendingComments.splice(commentIndex, 1)[0]; } @@ -435,14 +431,14 @@ export class CommentService extends Disposable implements ICommentService { return undefined; } - private _addContinueOnComments(pendingComments: PendingCommentThread[]): Set { + private _addContinueOnComments(pendingComments: PendingCommentThread[], map: Map): Set { const changedOwners = new Set(); for (const pendingComment of pendingComments) { - if (!this._continueOnComments.has(pendingComment.owner)) { - this._continueOnComments.set(pendingComment.owner, [pendingComment]); + if (!map.has(pendingComment.owner)) { + map.set(pendingComment.owner, [pendingComment]); changedOwners.add(pendingComment.owner); } else { - const commentsForOwner = this._continueOnComments.get(pendingComment.owner)!; + const commentsForOwner = map.get(pendingComment.owner)!; if (commentsForOwner.every(comment => (comment.uri.toString() !== pendingComment.uri.toString()) || !Range.equalsRange(comment.range, pendingComment.range))) { commentsForOwner.push(pendingComment); changedOwners.add(pendingComment.owner); diff --git a/code/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/code/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index 6fb56aa03d2..7e8ad06e90a 100644 --- a/code/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/code/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -332,6 +332,11 @@ export class CommentThreadWidget extends return undefined; } + setPendingComment(comment: string) { + this._pendingComment = comment; + this._commentReply?.setPendingComment(comment); + } + getDimensions() { return this._body.getDimensions(); } diff --git a/code/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts b/code/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts index e543daf3720..2582bfd77b4 100644 --- a/code/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts +++ b/code/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts @@ -141,8 +141,9 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._commentOptions = controller.options; } - this._initialCollapsibleState = _commentThread.initialCollapsibleState; - this._isExpanded = _commentThread.initialCollapsibleState === languages.CommentThreadCollapsibleState.Expanded; + this._initialCollapsibleState = _pendingComment ? languages.CommentThreadCollapsibleState.Expanded : _commentThread.initialCollapsibleState; + _commentThread.initialCollapsibleState = this._initialCollapsibleState; + this._isExpanded = this._initialCollapsibleState === languages.CommentThreadCollapsibleState.Expanded; this._commentThreadDisposables = []; this.create(); @@ -215,6 +216,12 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget }; } + public setPendingComment(comment: string) { + this._pendingComment = comment; + this.expand(); + this._commentThreadWidget.setPendingComment(comment); + } + protected _fillContainer(container: HTMLElement): void { this.setCssClass('review-widget'); this._commentThreadWidget = this._scopedInstantiationService.createInstance( @@ -231,7 +238,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget { editor: this.editor, codeBlockFontSize: '', codeBlockFontFamily: this.configurationService.getValue('editor').fontFamily || EDITOR_FONT_DEFAULTS.fontFamily }, this._commentOptions, { - actionRunner: () => { + actionRunner: async () => { if (!this._commentThread.comments || !this._commentThread.comments.length) { const newPosition = this.getPosition(); @@ -250,7 +257,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } else { range = new Range(originalRange.startLineNumber, originalRange.startColumn, originalRange.endLineNumber, originalRange.endColumn); } - this.commentService.updateCommentThreadTemplate(this.owner, this._commentThread.commentThreadHandle, range); + await this.commentService.updateCommentThreadTemplate(this.owner, this._commentThread.commentThreadHandle, range); } } }, @@ -467,8 +474,17 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } override show(rangeOrPos: IRange | IPosition | undefined, heightInLines: number): void { + const glyphPosition = this._commentGlyph?.getPosition(); + let range = Range.isIRange(rangeOrPos) ? rangeOrPos : (rangeOrPos ? Range.fromPositions(rangeOrPos) : undefined); + if (glyphPosition?.position && range && glyphPosition.position.lineNumber !== range.endLineNumber) { + // The widget could have moved as a result of editor changes. + // We need to try to calculate the new, more correct, range for the comment. + const distance = glyphPosition.position.lineNumber - range.endLineNumber; + range = new Range(range.startLineNumber + distance, range.startColumn, range.endLineNumber + distance, range.endColumn); + } + this._isExpanded = true; - super.show(rangeOrPos ?? new Range(0, 0, 0, 0), heightInLines); + super.show(range ?? new Range(0, 0, 0, 0), heightInLines); this._commentThread.collapsibleState = languages.CommentThreadCollapsibleState.Expanded; this._refresh(this._commentThreadWidget.getDimensions()); } diff --git a/code/src/vs/workbench/contrib/comments/browser/commentsController.ts b/code/src/vs/workbench/contrib/comments/browser/commentsController.ts index f05d745a668..2eb6c4c38bb 100644 --- a/code/src/vs/workbench/contrib/comments/browser/commentsController.ts +++ b/code/src/vs/workbench/contrib/comments/browser/commentsController.ts @@ -44,6 +44,7 @@ import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibil import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { URI } from 'vs/base/common/uri'; export const ID = 'editor.contrib.review'; @@ -369,6 +370,7 @@ export class CommentController implements IEditorContribution { private _computeCommentingRangeScheduler!: Delayer> | null; private _pendingNewCommentCache: { [key: string]: { [key: string]: string } }; private _pendingEditsCache: { [key: string]: { [key: string]: { [key: number]: string } } }; // owner -> threadId -> uniqueIdInThread -> pending comment + private _inProcessContinueOnComments: Map = new Map(); private _editorDisposables: IDisposable[] = []; private _activeCursorHasCommentingRange: IContextKey; private _activeEditorHasCommentingRange: IContextKey; @@ -481,7 +483,8 @@ export class CommentController implements IEditorContribution { owner: zone.owner, uri: zone.editor.getModel()!.uri, range: zone.commentThread.range, - body: pendingNewComment + body: pendingNewComment, + isReply: (zone.commentThread.comments !== undefined) && (zone.commentThread.comments.length > 0) }); } } @@ -826,7 +829,7 @@ export class CommentController implements IEditorContribution { this.openCommentsView(thread); } }); - added.forEach(thread => { + for (const thread of added) { const matchedZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === e.owner && zoneWidget.commentThread.threadId === thread.threadId); if (matchedZones.length) { return; @@ -839,23 +842,48 @@ export class CommentController implements IEditorContribution { return; } - const continueOnCommentText = (thread.range ? this.commentService.removeContinueOnComment({ owner: e.owner, uri: editorURI, range: thread.range })?.body : undefined); + const continueOnCommentIndex = this._inProcessContinueOnComments.get(e.owner)?.findIndex(pending => Range.lift(pending.range).equalsRange(thread.range)); + let continueOnCommentText: string | undefined; + if ((continueOnCommentIndex !== undefined) && continueOnCommentIndex >= 0) { + continueOnCommentText = this._inProcessContinueOnComments.get(e.owner)?.splice(continueOnCommentIndex, 1)[0].body; + } + const pendingCommentText = (this._pendingNewCommentCache[e.owner] && this._pendingNewCommentCache[e.owner][thread.threadId!]) ?? continueOnCommentText; const pendingEdits = this._pendingEditsCache[e.owner] && this._pendingEditsCache[e.owner][thread.threadId!]; this.displayCommentThread(e.owner, thread, pendingCommentText, pendingEdits); this._commentInfos.filter(info => info.owner === e.owner)[0].threads.push(thread); this.tryUpdateReservedSpace(); - }); - pending.forEach(thread => { - this.commentService.createCommentThreadTemplate(thread.owner, thread.uri, Range.lift(thread.range)); - }); + } + + for (const thread of pending) { + await this.resumePendingComment(editorURI, thread); + } this._commentThreadRangeDecorator.update(this.editor, commentInfo); })); this.beginComputeAndHandleEditorChange(); } + private async resumePendingComment(editorURI: URI, thread: languages.PendingCommentThread) { + const matchedZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === thread.owner && Range.lift(zoneWidget.commentThread.range)?.equalsRange(thread.range)); + if (thread.isReply && matchedZones.length) { + this.commentService.removeContinueOnComment({ owner: thread.owner, uri: editorURI, range: thread.range, isReply: true }); + const matchedZone = matchedZones[0]; + matchedZone.setPendingComment(thread.body); + } else if (!thread.isReply) { + const threadStillAvailable = this.commentService.removeContinueOnComment({ owner: thread.owner, uri: editorURI, range: thread.range, isReply: false }); + if (!threadStillAvailable) { + return; + } + if (!this._inProcessContinueOnComments.has(thread.owner)) { + this._inProcessContinueOnComments.set(thread.owner, []); + } + this._inProcessContinueOnComments.get(thread.owner)?.push(thread); + await this.commentService.createCommentThreadTemplate(thread.owner, thread.uri, Range.lift(thread.range)); + } + } + private beginComputeAndHandleEditorChange(): void { this.beginCompute().then(() => { if (!this._hasRespondedToEditorChange) { @@ -893,13 +921,19 @@ export class CommentController implements IEditorContribution { } private displayCommentThread(owner: string, thread: languages.CommentThread, pendingComment: string | undefined, pendingEdits: { [key: number]: string } | undefined): void { - if (!this.editor?.getModel()) { + const editor = this.editor?.getModel(); + if (!editor) { return; } - if (this.isEditorInlineOriginal(this.editor)) { + if (!this.editor || this.isEditorInlineOriginal(this.editor)) { return; } - const zoneWidget = this.instantiationService.createInstance(ReviewZoneWidget, this.editor, owner, thread, pendingComment, pendingEdits); + + let continueOnCommentReply: languages.PendingCommentThread | undefined; + if (thread.range && !pendingComment) { + continueOnCommentReply = this.commentService.removeContinueOnComment({ owner, uri: editor.uri, range: thread.range, isReply: true }); + } + const zoneWidget = this.instantiationService.createInstance(ReviewZoneWidget, this.editor, owner, thread, pendingComment ?? continueOnCommentReply?.body, pendingEdits); zoneWidget.display(thread.range); this._commentWidgets.push(zoneWidget); this.openCommentsView(thread); @@ -1156,15 +1190,11 @@ export class CommentController implements IEditorContribution { pendingEdits = providerEditsCacheStore[thread.threadId!]; } - if (pendingComment || pendingEdits) { - thread.collapsibleState = languages.CommentThreadCollapsibleState.Expanded; - } - this.displayCommentThread(info.owner, thread, pendingComment, pendingEdits); }); - info.pendingCommentThreads?.forEach(thread => { - this.commentService.createCommentThreadTemplate(thread.owner, thread.uri, Range.lift(thread.range)); - }); + for (const thread of info.pendingCommentThreads ?? []) { + this.resumePendingComment(this.editor!.getModel()!.uri, thread); + } }); this._commentingRangeDecorator.update(this.editor, this._commentInfos); diff --git a/code/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/code/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts index 6db24044ed3..2cf42660bc9 100644 --- a/code/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts +++ b/code/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts @@ -30,11 +30,11 @@ import { basename } from 'vs/base/common/resources'; import { openLinkFromMarkdown } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; import { IStyleOverride } from 'vs/platform/theme/browser/defaultStyles'; import { IListStyles } from 'vs/base/browser/ui/list/listWidget'; +import { ILocalizedString } from 'vs/platform/action/common/action'; export const COMMENTS_VIEW_ID = 'workbench.panel.comments'; export const COMMENTS_VIEW_STORAGE_ID = 'Comments'; -export const COMMENTS_VIEW_ORIGINAL_TITLE = 'Comments'; -export const COMMENTS_VIEW_TITLE = nls.localize('comments.view.title', "Comments"); +export const COMMENTS_VIEW_TITLE: ILocalizedString = nls.localize2('comments.view.title', "Comments"); interface IResourceTemplateData { resourceLabel: IResourceLabel; diff --git a/code/src/vs/workbench/contrib/comments/browser/commentsView.ts b/code/src/vs/workbench/contrib/comments/browser/commentsView.ts index 582b6877ec5..3ef5c946841 100644 --- a/code/src/vs/workbench/contrib/comments/browser/commentsView.ts +++ b/code/src/vs/workbench/contrib/comments/browser/commentsView.ts @@ -353,7 +353,7 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView { return ''; }, getWidgetAriaLabel(): string { - return COMMENTS_VIEW_TITLE; + return COMMENTS_VIEW_TITLE.value; } } })); diff --git a/code/src/vs/workbench/contrib/comments/common/commentsConfiguration.ts b/code/src/vs/workbench/contrib/comments/common/commentsConfiguration.ts index 6894e066602..44004fe47d7 100644 --- a/code/src/vs/workbench/contrib/comments/common/commentsConfiguration.ts +++ b/code/src/vs/workbench/contrib/comments/common/commentsConfiguration.ts @@ -9,7 +9,6 @@ export interface ICommentsConfiguration { visible: boolean; maxHeight: boolean; collapseOnResolve: boolean; - experimentalContinueOn: boolean; } export const COMMENTS_SECTION = 'comments'; diff --git a/code/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/code/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index 964a6a0443b..4e677142d69 100644 --- a/code/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/code/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -135,6 +135,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { let capabilities = EditorInputCapabilities.None; capabilities |= EditorInputCapabilities.CanDropIntoEditor; + capabilities |= EditorInputCapabilities.AuxWindowUnsupported; if (!this.customEditorService.getCustomEditorCapabilities(this.viewType)?.supportsMultipleEditorsPerDocument) { capabilities |= EditorInputCapabilities.Singleton; diff --git a/code/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/code/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 60e8ef89a10..1e03c9c6355 100644 --- a/code/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/code/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -111,7 +111,7 @@ const registerDebugCommandPaletteItem = (id: string, title: ICommandActionTitle, }; registerDebugCommandPaletteItem(RESTART_SESSION_ID, RESTART_LABEL); -registerDebugCommandPaletteItem(TERMINATE_THREAD_ID, { value: nls.localize('terminateThread', "Terminate Thread"), original: 'Terminate Thread' }, CONTEXT_IN_DEBUG_MODE); +registerDebugCommandPaletteItem(TERMINATE_THREAD_ID, nls.localize2('terminateThread', "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 +121,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, { 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' }, CONTEXT_DEBUGGERS_AVAILABLE); -registerDebugCommandPaletteItem(SelectionToReplAction.ID, { value: SelectionToReplAction.LABEL, original: 'Evaluate in Debug Console' }, CONTEXT_IN_DEBUG_MODE); -registerDebugCommandPaletteItem(SelectionToWatchExpressionsAction.ID, { value: SelectionToWatchExpressionsAction.LABEL, original: 'Add to Watch' }); -registerDebugCommandPaletteItem(TOGGLE_INLINE_BREAKPOINT_ID, { value: nls.localize('inlineBreakpoint', "Inline Breakpoint"), original: 'Inline Breakpoint' }); +registerDebugCommandPaletteItem(FOCUS_REPL_ID, nls.localize2({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugFocusConsole' }, "Focus on Debug Console View")); +registerDebugCommandPaletteItem(JUMP_TO_CURSOR_ID, nls.localize2('jumpToCursor', "Jump to Cursor"), CONTEXT_JUMP_TO_CURSOR_SUPPORTED); +registerDebugCommandPaletteItem(JUMP_TO_CURSOR_ID, nls.localize2('SetNextStatement', "Set Next Statement"), CONTEXT_JUMP_TO_CURSOR_SUPPORTED); +registerDebugCommandPaletteItem(RunToCursorAction.ID, RunToCursorAction.LABEL, CONTEXT_DEBUGGERS_AVAILABLE); +registerDebugCommandPaletteItem(SelectionToReplAction.ID, SelectionToReplAction.LABEL, CONTEXT_IN_DEBUG_MODE); +registerDebugCommandPaletteItem(SelectionToWatchExpressionsAction.ID, SelectionToWatchExpressionsAction.LABEL); +registerDebugCommandPaletteItem(TOGGLE_INLINE_BREAKPOINT_ID, nls.localize2('inlineBreakpoint', "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)))); @@ -216,7 +216,7 @@ if (isMacintosh) { // Editor Title Menu's "Run/Debug" dropdown item -MenuRegistry.appendMenuItem(MenuId.EditorTitle, { submenu: MenuId.EditorTitleRun, rememberDefaultAction: true, title: { value: nls.localize('run', "Run or Debug..."), original: 'Run or Debug...', }, icon: icons.debugRun, group: 'navigation', order: -1 }); +MenuRegistry.appendMenuItem(MenuId.EditorTitle, { submenu: MenuId.EditorTitleRun, rememberDefaultAction: true, title: nls.localize2('run', "Run or Debug..."), icon: icons.debugRun, group: 'navigation', order: -1 }); // Debug menu @@ -365,7 +365,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { const VIEW_CONTAINER: ViewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ id: DEBUG_PANEL_ID, - title: { value: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugPanel' }, "Debug Console"), original: 'Debug Console' }, + title: nls.localize2({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugPanel' }, "Debug Console"), icon: icons.debugConsoleViewIcon, ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [DEBUG_PANEL_ID, { mergeViewWithContainerWhenSingleView: true }]), storageId: DEBUG_PANEL_ID, @@ -375,7 +375,7 @@ const VIEW_CONTAINER: ViewContainer = Registry.as(ViewE Registry.as(ViewExtensions.ViewsRegistry).registerViews([{ id: REPL_VIEW_ID, - name: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugPanel' }, "Debug Console"), + name: nls.localize2({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugPanel' }, "Debug Console"), containerIcon: icons.debugConsoleViewIcon, canToggleVisibility: false, canMoveView: true, @@ -392,7 +392,7 @@ Registry.as(ViewExtensions.ViewsRegistry).registerViews([{ const viewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, - title: { value: nls.localize('run and debug', "Run and Debug"), original: 'Run and Debug' }, + title: nls.localize2('run and debug', "Run and Debug"), openCommandActionDescriptor: { id: VIEWLET_ID, mnemonicTitle: nls.localize({ key: 'miViewRun', comment: ['&& denotes a mnemonic'] }, "&&Run"), @@ -407,12 +407,12 @@ const viewContainer = Registry.as(ViewExtensions.ViewCo // Register default debug views const viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); -viewsRegistry.registerViews([{ id: VARIABLES_VIEW_ID, name: nls.localize('variables', "Variables"), containerIcon: icons.variablesViewIcon, ctorDescriptor: new SyncDescriptor(VariablesView), order: 10, weight: 40, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusVariablesView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); -viewsRegistry.registerViews([{ id: WATCH_VIEW_ID, name: nls.localize('watch', "Watch"), containerIcon: icons.watchViewIcon, ctorDescriptor: new SyncDescriptor(WatchExpressionsView), order: 20, weight: 10, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusWatchView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); -viewsRegistry.registerViews([{ id: CALLSTACK_VIEW_ID, name: nls.localize('callStack', "Call Stack"), containerIcon: icons.callStackViewIcon, ctorDescriptor: new SyncDescriptor(CallStackView), order: 30, weight: 30, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusCallStackView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); -viewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize('breakpoints', "Breakpoints"), containerIcon: icons.breakpointsViewIcon, ctorDescriptor: new SyncDescriptor(BreakpointsView), order: 40, weight: 20, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusBreakpointsView' }, when: ContextKeyExpr.or(CONTEXT_BREAKPOINTS_EXIST, CONTEXT_DEBUG_UX.isEqualTo('default'), CONTEXT_HAS_DEBUGGED) }], viewContainer); +viewsRegistry.registerViews([{ id: VARIABLES_VIEW_ID, name: nls.localize2('variables', "Variables"), containerIcon: icons.variablesViewIcon, ctorDescriptor: new SyncDescriptor(VariablesView), order: 10, weight: 40, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusVariablesView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); +viewsRegistry.registerViews([{ id: WATCH_VIEW_ID, name: nls.localize2('watch', "Watch"), containerIcon: icons.watchViewIcon, ctorDescriptor: new SyncDescriptor(WatchExpressionsView), order: 20, weight: 10, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusWatchView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); +viewsRegistry.registerViews([{ id: CALLSTACK_VIEW_ID, name: nls.localize2('callStack', "Call Stack"), containerIcon: icons.callStackViewIcon, ctorDescriptor: new SyncDescriptor(CallStackView), order: 30, weight: 30, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusCallStackView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); +viewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize2('breakpoints', "Breakpoints"), containerIcon: icons.breakpointsViewIcon, ctorDescriptor: new SyncDescriptor(BreakpointsView), order: 40, weight: 20, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusBreakpointsView' }, when: ContextKeyExpr.or(CONTEXT_BREAKPOINTS_EXIST, CONTEXT_DEBUG_UX.isEqualTo('default'), CONTEXT_HAS_DEBUGGED) }], viewContainer); viewsRegistry.registerViews([{ id: WelcomeView.ID, name: WelcomeView.LABEL, containerIcon: icons.runViewIcon, ctorDescriptor: new SyncDescriptor(WelcomeView), order: 1, weight: 40, canToggleVisibility: true, when: CONTEXT_DEBUG_UX.isEqualTo('simple') }], viewContainer); -viewsRegistry.registerViews([{ id: LOADED_SCRIPTS_VIEW_ID, name: nls.localize('loadedScripts', "Loaded Scripts"), containerIcon: icons.loadedScriptsViewIcon, ctorDescriptor: new SyncDescriptor(LoadedScriptsView), order: 35, weight: 5, canToggleVisibility: true, canMoveView: true, collapsed: true, when: ContextKeyExpr.and(CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_DEBUG_UX.isEqualTo('default')) }], viewContainer); +viewsRegistry.registerViews([{ id: LOADED_SCRIPTS_VIEW_ID, name: nls.localize2('loadedScripts', "Loaded Scripts"), containerIcon: icons.loadedScriptsViewIcon, ctorDescriptor: new SyncDescriptor(LoadedScriptsView), order: 35, weight: 5, canToggleVisibility: true, canMoveView: true, collapsed: true, when: ContextKeyExpr.and(CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_DEBUG_UX.isEqualTo('default')) }], viewContainer); // Register disassembly view @@ -588,7 +588,7 @@ configurationRegistry.registerConfiguration({ }, 'debug.enableStatusBarColor': { type: 'boolean', - description: nls.localize('debug.enableStatusBarColor', "Color status bar when debugger is active"), + description: nls.localize('debug.enableStatusBarColor', "Color of the Status bar when debugger is active."), default: true } } diff --git a/code/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts b/code/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts index 3abdc920606..415a2df0083 100644 --- a/code/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts +++ b/code/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts @@ -7,7 +7,7 @@ import { getDomNodePagePosition } from 'vs/base/browser/dom'; import { Action } from 'vs/base/common/actions'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorAction, EditorAction2, IActionOptions, registerEditorAction } from 'vs/editor/browser/editorExtensions'; +import { EditorAction, IActionOptions, registerEditorAction } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { Position } from 'vs/editor/common/core/position'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; @@ -29,6 +29,7 @@ import { BREAKPOINT_EDITOR_CONTRIBUTION_ID, BreakpointWidgetContext, CONTEXT_CAL import { getEvaluatableExpressionAtPosition } from 'vs/workbench/contrib/debug/common/debugUtils'; import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassemblyViewInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ILocalizedString } from 'vs/platform/action/common/action'; class ToggleBreakpointAction extends Action2 { constructor() { @@ -195,9 +196,9 @@ class EditBreakpointAction extends EditorAction { } } -class OpenDisassemblyViewAction extends EditorAction2 { +class OpenDisassemblyViewAction extends Action2 { - public static readonly ID = 'editor.debug.action.openDisassemblyView'; + public static readonly ID = 'debug.action.openDisassemblyView'; constructor() { super({ @@ -229,11 +230,9 @@ class OpenDisassemblyViewAction extends EditorAction2 { }); } - runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]): void { - if (editor.hasModel()) { - const editorService = accessor.get(IEditorService); - editorService.openEditor(DisassemblyViewInput.instance, { pinned: true, revealIfOpened: true }); - } + run(accessor: ServicesAccessor): void { + const editorService = accessor.get(IEditorService); + editorService.openEditor(DisassemblyViewInput.instance, { pinned: true, revealIfOpened: true }); } } @@ -266,12 +265,12 @@ class ToggleDisassemblyViewSourceCodeAction extends Action2 { export class RunToCursorAction extends EditorAction { public static readonly ID = 'editor.debug.action.runToCursor'; - public static readonly LABEL = nls.localize('runToCursor', "Run to Cursor"); + public static readonly LABEL: ILocalizedString = nls.localize2('runToCursor', "Run to Cursor"); constructor() { super({ id: RunToCursorAction.ID, - label: RunToCursorAction.LABEL, + label: RunToCursorAction.LABEL.value, alias: 'Debug: Run to Cursor', precondition: ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, PanelFocusContext.toNegated(), ContextKeyExpr.or(EditorContextKeys.editorTextFocus, CONTEXT_DISASSEMBLY_VIEW_FOCUS)), contextMenuOpts: { @@ -307,12 +306,12 @@ export class RunToCursorAction extends EditorAction { export class SelectionToReplAction extends EditorAction { public static readonly ID = 'editor.debug.action.selectionToRepl'; - public static readonly LABEL = nls.localize('evaluateInDebugConsole', "Evaluate in Debug Console"); + public static readonly LABEL: ILocalizedString = nls.localize2('evaluateInDebugConsole', "Evaluate in Debug Console"); constructor() { super({ id: SelectionToReplAction.ID, - label: SelectionToReplAction.LABEL, + label: SelectionToReplAction.LABEL.value, alias: 'Debug: Evaluate in Console', precondition: ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, EditorContextKeys.editorTextFocus), contextMenuOpts: { @@ -347,12 +346,12 @@ export class SelectionToReplAction extends EditorAction { export class SelectionToWatchExpressionsAction extends EditorAction { public static readonly ID = 'editor.debug.action.selectionToWatch'; - public static readonly LABEL = nls.localize('addToWatch', "Add to Watch"); + public static readonly LABEL: ILocalizedString = nls.localize2('addToWatch', "Add to Watch"); constructor() { super({ id: SelectionToWatchExpressionsAction.ID, - label: SelectionToWatchExpressionsAction.LABEL, + label: SelectionToWatchExpressionsAction.LABEL.value, alias: 'Debug: Add to Watch', precondition: ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, EditorContextKeys.editorTextFocus), contextMenuOpts: { diff --git a/code/src/vs/workbench/contrib/debug/browser/repl.ts b/code/src/vs/workbench/contrib/debug/browser/repl.ts index 3966e663acc..7be4e8eb0bd 100644 --- a/code/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/code/src/vs/workbench/contrib/debug/browser/repl.ts @@ -69,7 +69,7 @@ import { Variable } from 'vs/workbench/contrib/debug/common/debugModel'; import { ReplEvaluationResult, ReplGroup } from 'vs/workbench/contrib/debug/common/replModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { registerNavigableContainer } from 'vs/workbench/browser/actions/widgetNavigationCommands'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; const $ = dom.$; @@ -976,9 +976,9 @@ registerAction2(class extends ViewAction { } runInView(_accessor: ServicesAccessor, view: Repl): void { - const accessibilityService = _accessor.get(IAccessibilityService); + const accessibleNotificationService = _accessor.get(IAccessibleNotificationService); view.clearRepl(); - accessibilityService.alertCleared(); + accessibleNotificationService.notify(AccessibleNotificationEvent.Clear); } }); diff --git a/code/src/vs/workbench/contrib/debug/browser/welcomeView.ts b/code/src/vs/workbench/contrib/debug/browser/welcomeView.ts index 78d741bf5ff..cdff715b9bf 100644 --- a/code/src/vs/workbench/contrib/debug/browser/welcomeView.ts +++ b/code/src/vs/workbench/contrib/debug/browser/welcomeView.ts @@ -9,7 +9,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService, RawContextKey, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { IDebugService, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_EXTENSION_AVAILABLE } from 'vs/workbench/contrib/debug/common/debug'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; @@ -25,6 +25,7 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { SELECT_AND_START_ID, DEBUG_CONFIGURE_COMMAND_ID, DEBUG_START_COMMAND_ID } from 'vs/workbench/contrib/debug/browser/debugCommands'; +import { ILocalizedString } from 'vs/platform/action/common/action'; const debugStartLanguageKey = 'debugStartLanguage'; const CONTEXT_DEBUG_START_LANGUAGE = new RawContextKey(debugStartLanguageKey, undefined); @@ -33,7 +34,7 @@ const CONTEXT_DEBUGGER_INTERESTED_IN_ACTIVE_EDITOR = new RawContextKey( export class WelcomeView extends ViewPane { static readonly ID = 'workbench.debug.welcome'; - static readonly LABEL = localize('run', "Run"); + static readonly LABEL: ILocalizedString = localize2('run', "Run"); private debugStartLanguageContext: IContextKey; private debuggerInterestedContext: IContextKey; diff --git a/code/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts b/code/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts index 886278071b0..bac592ba3a6 100644 --- a/code/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts +++ b/code/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts @@ -10,7 +10,7 @@ import { ILifecycleService, LifecyclePhase, ShutdownReason } from 'vs/workbench/ import { Action2, IAction2Options, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize } from 'vs/nls'; -import { IEditSessionsStorageService, Change, ChangeType, Folder, EditSession, FileType, EDIT_SESSION_SYNC_CATEGORY, EDIT_SESSIONS_CONTAINER_ID, EditSessionSchemaVersion, IEditSessionsLogService, EDIT_SESSIONS_VIEW_ICON, EDIT_SESSIONS_TITLE, EDIT_SESSIONS_ORIGINAL_TITLE, EDIT_SESSIONS_SHOW_VIEW, EDIT_SESSIONS_DATA_VIEW_ID, decodeEditSessionFileContent, hashedEditSessionId, editSessionsLogId, EDIT_SESSIONS_PENDING } from 'vs/workbench/contrib/editSessions/common/editSessions'; +import { IEditSessionsStorageService, Change, ChangeType, Folder, EditSession, FileType, EDIT_SESSION_SYNC_CATEGORY, EDIT_SESSIONS_CONTAINER_ID, EditSessionSchemaVersion, IEditSessionsLogService, EDIT_SESSIONS_VIEW_ICON, EDIT_SESSIONS_TITLE, EDIT_SESSIONS_SHOW_VIEW, EDIT_SESSIONS_DATA_VIEW_ID, decodeEditSessionFileContent, hashedEditSessionId, editSessionsLogId, EDIT_SESSIONS_PENDING } 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, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace'; @@ -274,7 +274,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo const container = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer( { id: EDIT_SESSIONS_CONTAINER_ID, - title: { value: EDIT_SESSIONS_TITLE, original: EDIT_SESSIONS_ORIGINAL_TITLE }, + title: EDIT_SESSIONS_TITLE, ctorDescriptor: new SyncDescriptor( ViewPaneContainer, [EDIT_SESSIONS_CONTAINER_ID, { mergeViewWithContainerWhenSingleView: true }] diff --git a/code/src/vs/workbench/contrib/editSessions/browser/editSessionsViews.ts b/code/src/vs/workbench/contrib/editSessions/browser/editSessionsViews.ts index a043ec34b84..5793dde0157 100644 --- a/code/src/vs/workbench/contrib/editSessions/browser/editSessionsViews.ts +++ b/code/src/vs/workbench/contrib/editSessions/browser/editSessionsViews.ts @@ -38,8 +38,7 @@ export class EditSessionsDataViews extends Disposable { private registerViews(container: ViewContainer): void { const viewId = EDIT_SESSIONS_DATA_VIEW_ID; - const name = EDIT_SESSIONS_TITLE; - const treeView = this.instantiationService.createInstance(TreeView, viewId, name); + const treeView = this.instantiationService.createInstance(TreeView, viewId, EDIT_SESSIONS_TITLE.value); treeView.showCollapseAllAction = true; treeView.showRefreshAction = true; treeView.dataProvider = this.instantiationService.createInstance(EditSessionDataViewDataProvider); @@ -47,7 +46,7 @@ export class EditSessionsDataViews extends Disposable { const viewsRegistry = Registry.as(Extensions.ViewsRegistry); viewsRegistry.registerViews([{ id: viewId, - name, + name: EDIT_SESSIONS_TITLE, ctorDescriptor: new SyncDescriptor(TreeViewPane), canToggleVisibility: true, canMoveView: false, @@ -128,7 +127,7 @@ export class EditSessionsDataViews extends Disposable { message: localize('confirm delete.v2', 'Are you sure you want to permanently delete your working changes with ref {0}?', editSessionId), detail: localize('confirm delete detail.v2', ' You cannot undo this action.'), type: 'warning', - title: EDIT_SESSIONS_TITLE + title: EDIT_SESSIONS_TITLE.value }); if (result.confirmed) { await editSessionStorageService.delete('editSessions', editSessionId); @@ -157,7 +156,7 @@ export class EditSessionsDataViews extends Disposable { message: localize('confirm delete all', 'Are you sure you want to permanently delete all stored changes from the cloud?'), detail: localize('confirm delete all detail', ' You cannot undo this action.'), type: 'warning', - title: EDIT_SESSIONS_TITLE + title: EDIT_SESSIONS_TITLE.value }); if (result.confirmed) { await editSessionStorageService.delete('editSessions', null); diff --git a/code/src/vs/workbench/contrib/editSessions/common/editSessions.ts b/code/src/vs/workbench/contrib/editSessions/common/editSessions.ts index 4cb53dc45f7..6484748e057 100644 --- a/code/src/vs/workbench/contrib/editSessions/common/editSessions.ts +++ b/code/src/vs/workbench/contrib/editSessions/common/editSessions.ts @@ -5,7 +5,7 @@ import { decodeBase64, VSBuffer } from 'vs/base/common/buffer'; import { Codicon } from 'vs/base/common/codicons'; -import { localize } from 'vs/nls'; +import { localize, localize2 } 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'; @@ -98,8 +98,7 @@ export const EDIT_SESSIONS_PENDING = new RawContextKey(EDIT_SESSIONS_PE export const EDIT_SESSIONS_CONTAINER_ID = 'workbench.view.editSessions'; export const EDIT_SESSIONS_DATA_VIEW_ID = 'workbench.views.editSessions.data'; -export const EDIT_SESSIONS_ORIGINAL_TITLE = 'Cloud Changes'; -export const EDIT_SESSIONS_TITLE = localize('cloud changes', 'Cloud Changes'); +export const EDIT_SESSIONS_TITLE: ILocalizedString = localize2('cloud changes', 'Cloud Changes'); export const EDIT_SESSIONS_VIEW_ICON = registerIcon('edit-sessions-view-icon', Codicon.cloudDownload, localize('editSessionViewIcon', 'View icon of the cloud changes view.')); diff --git a/code/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/code/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 0abbb01f1a7..9ee8986fd53 100644 --- a/code/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/code/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -1225,18 +1225,20 @@ export class ExtensionEditor extends EditorPane { $('th', undefined, localize('description', "Description")), $('th', undefined, localize('default', "Default")) ), - ...contrib.map(key => { - let description: (Node | string) = properties[key].description || ''; - if (properties[key].markdownDescription) { - const { element, dispose } = renderMarkdown({ value: properties[key].markdownDescription }, { actionHandler: { callback: (content) => this.openerService.open(content).catch(onUnexpectedError), disposables: this.contentDisposables } }); - description = element; - this.contentDisposables.add(toDisposable(dispose)); - } - return $('tr', undefined, - $('td', undefined, $('code', undefined, key)), - $('td', undefined, description), - $('td', undefined, $('code', undefined, `${isUndefined(properties[key].default) ? getDefaultValue(properties[key].type) : properties[key].default}`))); - }) + ...contrib + .sort((a, b) => a.localeCompare(b)) + .map(key => { + let description: (Node | string) = properties[key].description || ''; + if (properties[key].markdownDescription) { + const { element, dispose } = renderMarkdown({ value: properties[key].markdownDescription }, { actionHandler: { callback: (content) => this.openerService.open(content).catch(onUnexpectedError), disposables: this.contentDisposables } }); + description = element; + this.contentDisposables.add(toDisposable(dispose)); + } + return $('tr', undefined, + $('td', undefined, $('code', undefined, key)), + $('td', undefined, description), + $('td', undefined, $('code', undefined, `${isUndefined(properties[key].default) ? getDefaultValue(properties[key].type) : properties[key].default}`))); + }) ) ); @@ -1257,9 +1259,11 @@ export class ExtensionEditor extends EditorPane { $('th', undefined, localize('debugger name', "Name")), $('th', undefined, localize('debugger type', "Type")), ), - ...contrib.map(d => $('tr', undefined, - $('td', undefined, d.label!), - $('td', undefined, d.type))) + ...contrib + .sort((a, b) => a.label!.localeCompare(b.label!)) + .map(d => $('tr', undefined, + $('td', undefined, d.label!), + $('td', undefined, d.type))) ) ); @@ -1284,7 +1288,9 @@ export class ExtensionEditor extends EditorPane { $('summary', { tabindex: '0' }, localize('viewContainers', "View Containers ({0})", viewContainers.length)), $('table', undefined, $('tr', undefined, $('th', undefined, localize('view container id', "ID")), $('th', undefined, localize('view container title', "Title")), $('th', undefined, localize('view container location', "Where"))), - ...viewContainers.map(viewContainer => $('tr', undefined, $('td', undefined, viewContainer.id), $('td', undefined, viewContainer.title), $('td', undefined, viewContainer.location))) + ...viewContainers + .sort((a, b) => a.id.localeCompare(b.id)) + .map(viewContainer => $('tr', undefined, $('td', undefined, viewContainer.id), $('td', undefined, viewContainer.title), $('td', undefined, viewContainer.location))) ) ); @@ -1309,7 +1315,9 @@ export class ExtensionEditor extends EditorPane { $('summary', { tabindex: '0' }, localize('views', "Views ({0})", views.length)), $('table', undefined, $('tr', undefined, $('th', undefined, localize('view id', "ID")), $('th', undefined, localize('view name', "Name")), $('th', undefined, localize('view location', "Where"))), - ...views.map(view => $('tr', undefined, $('td', undefined, view.id), $('td', undefined, view.name), $('td', undefined, view.location))) + ...views + .sort((a, b) => a.id.localeCompare(b.id)) + .map(view => $('tr', undefined, $('td', undefined, view.id), $('td', undefined, view.name), $('td', undefined, view.location))) ) ); @@ -1327,7 +1335,9 @@ export class ExtensionEditor extends EditorPane { $('summary', { tabindex: '0' }, localize('localizations', "Localizations ({0})", localizations.length)), $('table', undefined, $('tr', undefined, $('th', undefined, localize('localizations language id', "Language ID")), $('th', undefined, localize('localizations language name', "Language Name")), $('th', undefined, localize('localizations localized language name', "Language Name (Localized)"))), - ...localizations.map(localization => $('tr', undefined, $('td', undefined, localization.languageId), $('td', undefined, localization.languageName || ''), $('td', undefined, localization.localizedLanguageName || ''))) + ...localizations + .sort((a, b) => a.languageId.localeCompare(b.languageId)) + .map(localization => $('tr', undefined, $('td', undefined, localization.languageId), $('td', undefined, localization.languageName || ''), $('td', undefined, localization.localizedLanguageName || ''))) ) ); @@ -1340,15 +1350,15 @@ export class ExtensionEditor extends EditorPane { if (!webviewEditors.length) { return false; } - + const renderEditors = Array.from(webviewEditors).sort((a, b) => a.viewType.localeCompare(b.viewType)); const details = $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', { tabindex: '0' }, localize('customEditors', "Custom Editors ({0})", webviewEditors.length)), + $('summary', { tabindex: '0' }, localize('customEditors', "Custom Editors ({0})", renderEditors.length)), $('table', undefined, $('tr', undefined, $('th', undefined, localize('customEditors view type', "View Type")), $('th', undefined, localize('customEditors priority', "Priority")), $('th', undefined, localize('customEditors filenamePattern', "Filename Pattern"))), - ...webviewEditors.map(webviewEditor => + ...renderEditors.map(webviewEditor => $('tr', undefined, $('td', undefined, webviewEditor.viewType), $('td', undefined, webviewEditor.priority), @@ -1378,12 +1388,14 @@ export class ExtensionEditor extends EditorPane { $('th', undefined, localize('codeActions.kind', "Kind")), $('th', undefined, localize('codeActions.description', "Description")), $('th', undefined, localize('codeActions.languages', "Languages"))), - ...flatActions.map(action => - $('tr', undefined, - $('td', undefined, action.title), - $('td', undefined, $('code', undefined, action.kind)), - $('td', undefined, action.description ?? ''), - $('td', undefined, ...action.languages.map(language => $('code', undefined, language))))) + ...flatActions + .sort((a, b) => a.title.localeCompare(b.title)) + .map(action => + $('tr', undefined, + $('td', undefined, action.title), + $('td', undefined, $('code', undefined, action.kind)), + $('td', undefined, action.description ?? ''), + $('td', undefined, ...action.languages.map(language => $('code', undefined, language))))) ) ); @@ -1404,12 +1416,14 @@ export class ExtensionEditor extends EditorPane { $('th', undefined, localize('authentication.label', "Label")), $('th', undefined, localize('authentication.id', "ID")) ), - ...authentication.map(action => - $('tr', undefined, - $('td', undefined, action.label), - $('td', undefined, action.id) + ...authentication + .sort((a, b) => a.label.localeCompare(b.label)) + .map(action => + $('tr', undefined, + $('td', undefined, action.label), + $('td', undefined, action.id) + ) ) - ) ) ); @@ -1425,7 +1439,10 @@ export class ExtensionEditor extends EditorPane { const details = $('details', { open: true, ontoggle: onDetailsToggle }, $('summary', { tabindex: '0' }, localize('colorThemes', "Color Themes ({0})", contrib.length)), - $('ul', undefined, ...contrib.map(theme => $('li', undefined, theme.label))) + $('ul', undefined, + ...contrib + .sort((a, b) => a.label.localeCompare(b.label)) + .map(theme => $('li', undefined, theme.label))) ); append(container, details); @@ -1440,7 +1457,10 @@ export class ExtensionEditor extends EditorPane { const details = $('details', { open: true, ontoggle: onDetailsToggle }, $('summary', { tabindex: '0' }, localize('iconThemes', "File Icon Themes ({0})", contrib.length)), - $('ul', undefined, ...contrib.map(theme => $('li', undefined, theme.label))) + $('ul', undefined, + ...contrib + .sort((a, b) => a.label.localeCompare(b.label)) + .map(theme => $('li', undefined, theme.label))) ); append(container, details); @@ -1455,7 +1475,10 @@ export class ExtensionEditor extends EditorPane { const details = $('details', { open: true, ontoggle: onDetailsToggle }, $('summary', { tabindex: '0' }, localize('productThemes', "Product Icon Themes ({0})", contrib.length)), - $('ul', undefined, ...contrib.map(theme => $('li', undefined, theme.label))) + $('ul', undefined, + ...contrib + .sort((a, b) => a.label.localeCompare(b.label)) + .map(theme => $('li', undefined, theme.label))) ); append(container, details); @@ -1490,13 +1513,15 @@ export class ExtensionEditor extends EditorPane { $('th', undefined, localize('defaultLight', "Light Default")), $('th', undefined, localize('defaultHC', "High Contrast Default")) ), - ...colors.map(color => $('tr', undefined, - $('td', undefined, $('code', undefined, color.id)), - $('td', undefined, color.description), - $('td', undefined, ...colorPreview(color.defaults.dark)), - $('td', undefined, ...colorPreview(color.defaults.light)), - $('td', undefined, ...colorPreview(color.defaults.highContrast)) - )) + ...colors + .sort((a, b) => a.id.localeCompare(b.id)) + .map(color => $('tr', undefined, + $('td', undefined, $('code', undefined, color.id)), + $('td', undefined, color.description), + $('td', undefined, ...colorPreview(color.defaults.dark)), + $('td', undefined, ...colorPreview(color.defaults.light)), + $('td', undefined, ...colorPreview(color.defaults.highContrast)) + )) ) ); @@ -1595,12 +1620,14 @@ export class ExtensionEditor extends EditorPane { $('th', undefined, localize('keyboard shortcuts', "Keyboard Shortcuts")), $('th', undefined, localize('menuContexts', "Menu Contexts")) ), - ...commands.map(c => $('tr', undefined, - $('td', undefined, $('code', undefined, c.id)), - $('td', undefined, typeof c.title === 'string' ? c.title : c.title.value), - $('td', undefined, ...c.keybindings.map(keybinding => renderKeybinding(keybinding))), - $('td', undefined, ...c.menus.map(context => $('code', undefined, context))) - )) + ...commands + .sort((a, b) => a.id.localeCompare(b.id)) + .map(c => $('tr', undefined, + $('td', undefined, $('code', undefined, c.id)), + $('td', undefined, typeof c.title === 'string' ? c.title : c.title.value), + $('td', undefined, ...c.keybindings.map(keybinding => renderKeybinding(keybinding))), + $('td', undefined, ...c.menus.map(context => $('code', undefined, context))) + )) ) ); @@ -1661,13 +1688,15 @@ export class ExtensionEditor extends EditorPane { $('th', undefined, localize('grammar', "Grammar")), $('th', undefined, localize('snippets', "Snippets")) ), - ...languages.map(l => $('tr', undefined, - $('td', undefined, l.id), - $('td', undefined, l.name), - $('td', undefined, ...join(l.extensions.map(ext => $('code', undefined, ext)), ' ')), - $('td', undefined, document.createTextNode(l.hasGrammar ? '✔︎' : '\u2014')), - $('td', undefined, document.createTextNode(l.hasSnippets ? '✔︎' : '\u2014')) - )) + ...languages + .sort((a, b) => a.id.localeCompare(b.id)) + .map(l => $('tr', undefined, + $('td', undefined, l.id), + $('td', undefined, l.name), + $('td', undefined, ...join(l.extensions.map(ext => $('code', undefined, ext)), ' ')), + $('td', undefined, document.createTextNode(l.hasGrammar ? '✔︎' : '\u2014')), + $('td', undefined, document.createTextNode(l.hasSnippets ? '✔︎' : '\u2014')) + )) ) ); @@ -1683,7 +1712,10 @@ export class ExtensionEditor extends EditorPane { const details = $('details', { open: true, ontoggle: onDetailsToggle }, $('summary', { tabindex: '0' }, localize('activation events', "Activation Events ({0})", activationEvents.length)), - $('ul', undefined, ...activationEvents.map(activationEvent => $('li', undefined, $('code', undefined, activationEvent)))) + $('ul', undefined, + ...activationEvents + .sort((a, b) => a.localeCompare(b)) + .map(activationEvent => $('li', undefined, $('code', undefined, activationEvent)))) ); append(container, details); @@ -1704,9 +1736,11 @@ export class ExtensionEditor extends EditorPane { $('th', undefined, localize('Notebook id', "ID")), $('th', undefined, localize('Notebook name', "Name")), ), - ...contrib.map(d => $('tr', undefined, - $('td', undefined, d.type), - $('td', undefined, d.displayName))) + ...contrib + .sort((a, b) => a.type.localeCompare(b.type)) + .map(d => $('tr', undefined, + $('td', undefined, d.type), + $('td', undefined, d.displayName))) ) ); @@ -1728,9 +1762,11 @@ export class ExtensionEditor extends EditorPane { $('th', undefined, localize('Notebook renderer name', "Name")), $('th', undefined, localize('Notebook mimetypes', "Mimetypes")), ), - ...contrib.map(d => $('tr', undefined, - $('td', undefined, d.displayName), - $('td', undefined, d.mimeTypes.join(',')))) + ...contrib + .sort((a, b) => a.displayName.localeCompare(b.displayName)) + .map(d => $('tr', undefined, + $('td', undefined, d.displayName), + $('td', undefined, d.mimeTypes.join(',')))) ) ); diff --git a/code/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/code/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 8f4b29443a9..94a6279bcdb 100644 --- a/code/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/code/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/extensionsViewlet'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { timeout, Delayer, Promises } from 'vs/base/common/async'; import { isCancellationError } from 'vs/base/common/errors'; import { createErrorWithActions } from 'vs/base/common/errorMessage'; @@ -154,7 +154,12 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio /* Installed extensions view */ viewDescriptors.push({ id, - get name() { return getInstalledViewName(); }, + get name() { + return { + value: getInstalledViewName(), + original: getViewName('Installed', server) + }; + }, weight: 100, order: 1, when: ContextKeyExpr.and(DefaultViewsContext), @@ -214,7 +219,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio */ viewDescriptors.push({ id: 'workbench.views.extensions.popular', - name: localize('popularExtensions', "Popular"), + name: localize2('popularExtensions', "Popular"), ctorDescriptor: new SyncDescriptor(DefaultPopularExtensionsView, [{ hideBadge: true }]), when: ContextKeyExpr.and(DefaultViewsContext, ContextKeyExpr.not('hasInstalledExtensions'), CONTEXT_HAS_GALLERY), weight: 60, @@ -229,7 +234,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio */ viewDescriptors.push({ id: 'extensions.recommendedList', - name: localize('recommendedExtensions', "Recommended"), + name: localize2('recommendedExtensions', "Recommended"), ctorDescriptor: new SyncDescriptor(DefaultRecommendedExtensionsView, [{ flexibleHeight: true }]), when: ContextKeyExpr.and(DefaultViewsContext, SortByUpdateDateContext.negate(), ContextKeyExpr.not('config.extensions.showRecommendationsOnlyOnDemand'), CONTEXT_HAS_GALLERY), weight: 40, @@ -245,7 +250,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio */ viewDescriptors.push({ id: 'workbench.views.extensions.enabled', - name: localize('enabledExtensions', "Enabled"), + name: localize2('enabledExtensions', "Enabled"), ctorDescriptor: new SyncDescriptor(EnabledExtensionsView, [{}]), when: ContextKeyExpr.and(DefaultViewsContext, ContextKeyExpr.has('hasInstalledExtensions')), hideByDefault: true, @@ -260,7 +265,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio */ viewDescriptors.push({ id: 'workbench.views.extensions.disabled', - name: localize('disabledExtensions', "Disabled"), + name: localize2('disabledExtensions', "Disabled"), ctorDescriptor: new SyncDescriptor(DisabledExtensionsView, [{}]), when: ContextKeyExpr.and(DefaultViewsContext, ContextKeyExpr.has('hasInstalledExtensions')), hideByDefault: true, @@ -282,7 +287,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio */ viewDescriptors.push({ id: 'workbench.views.extensions.marketplace', - name: localize('marketPlace', "Marketplace"), + name: localize2('marketPlace', "Marketplace"), ctorDescriptor: new SyncDescriptor(SearchMarketplaceExtensionsView, [{}]), when: ContextKeyExpr.and(ContextKeyExpr.has('searchMarketplaceExtensions')), }); @@ -292,7 +297,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio */ viewDescriptors.push({ id: 'workbench.views.extensions.searchInstalled', - name: localize('installed', "Installed"), + name: localize2('installed', "Installed"), ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]), when: ContextKeyExpr.and(ContextKeyExpr.has('searchInstalledExtensions')), }); @@ -302,7 +307,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio */ viewDescriptors.push({ id: 'workbench.views.extensions.searchRecentlyUpdated', - name: localize('recently updated', "Recently Updated"), + name: localize2('recently updated', "Recently Updated"), ctorDescriptor: new SyncDescriptor(RecentlyUpdatedExtensionsView, [{}]), when: ContextKeyExpr.or(SearchExtensionUpdatesContext, ContextKeyExpr.has('searchRecentlyUpdatedExtensions')), order: 2, @@ -313,7 +318,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio */ viewDescriptors.push({ id: 'workbench.views.extensions.searchEnabled', - name: localize('enabled', "Enabled"), + name: localize2('enabled', "Enabled"), ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]), when: ContextKeyExpr.and(ContextKeyExpr.has('searchEnabledExtensions')), }); @@ -323,7 +328,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio */ viewDescriptors.push({ id: 'workbench.views.extensions.searchDisabled', - name: localize('disabled', "Disabled"), + name: localize2('disabled', "Disabled"), ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]), when: ContextKeyExpr.and(ContextKeyExpr.has('searchDisabledExtensions')), }); @@ -333,7 +338,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio */ viewDescriptors.push({ id: OUTDATED_EXTENSIONS_VIEW_ID, - name: localize('availableUpdates', "Available Updates"), + name: localize2('availableUpdates', "Available Updates"), ctorDescriptor: new SyncDescriptor(OutdatedExtensionsView, [{}]), when: ContextKeyExpr.or(SearchExtensionUpdatesContext, ContextKeyExpr.has('searchOutdatedExtensions')), order: 1, @@ -344,7 +349,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio */ viewDescriptors.push({ id: 'workbench.views.extensions.searchBuiltin', - name: localize('builtin', "Builtin"), + name: localize2('builtin', "Builtin"), ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]), when: ContextKeyExpr.and(ContextKeyExpr.has('searchBuiltInExtensions')), }); @@ -354,7 +359,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio */ viewDescriptors.push({ id: 'workbench.views.extensions.searchWorkspaceUnsupported', - name: localize('workspaceUnsupported', "Workspace Unsupported"), + name: localize2('workspaceUnsupported', "Workspace Unsupported"), ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]), when: ContextKeyExpr.and(ContextKeyExpr.has('searchWorkspaceUnsupportedExtensions')), }); @@ -367,7 +372,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio viewDescriptors.push({ id: WORKSPACE_RECOMMENDATIONS_VIEW_ID, - name: localize('workspaceRecommendedExtensions', "Workspace Recommendations"), + name: localize2('workspaceRecommendedExtensions', "Workspace Recommendations"), ctorDescriptor: new SyncDescriptor(WorkspaceRecommendedExtensionsView, [{}]), when: ContextKeyExpr.and(ContextKeyExpr.has('recommendedExtensions'), WorkbenchStateContext.notEqualsTo('empty')), order: 1 @@ -375,7 +380,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio viewDescriptors.push({ id: 'workbench.views.extensions.otherRecommendations', - name: localize('otherRecommendedExtensions', "Other Recommendations"), + name: localize2('otherRecommendedExtensions', "Other Recommendations"), ctorDescriptor: new SyncDescriptor(RecommendedExtensionsView, [{}]), when: ContextKeyExpr.has('recommendedExtensions'), order: 2 @@ -389,21 +394,21 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio viewDescriptors.push({ id: 'workbench.views.extensions.builtinFeatureExtensions', - name: localize('builtinFeatureExtensions', "Features"), + name: localize2('builtinFeatureExtensions', "Features"), ctorDescriptor: new SyncDescriptor(BuiltInFeatureExtensionsView, [{}]), when: ContextKeyExpr.has('builtInExtensions'), }); viewDescriptors.push({ id: 'workbench.views.extensions.builtinThemeExtensions', - name: localize('builtInThemesExtensions', "Themes"), + name: localize2('builtInThemesExtensions', "Themes"), ctorDescriptor: new SyncDescriptor(BuiltInThemesExtensionsView, [{}]), when: ContextKeyExpr.has('builtInExtensions'), }); viewDescriptors.push({ id: 'workbench.views.extensions.builtinProgrammingLanguageExtensions', - name: localize('builtinProgrammingLanguageExtensions', "Programming Languages"), + name: localize2('builtinProgrammingLanguageExtensions', "Programming Languages"), ctorDescriptor: new SyncDescriptor(BuiltInProgrammingLanguageExtensionsView, [{}]), when: ContextKeyExpr.has('builtInExtensions'), }); @@ -416,28 +421,28 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio viewDescriptors.push({ id: 'workbench.views.extensions.untrustedUnsupportedExtensions', - name: localize('untrustedUnsupportedExtensions', "Disabled in Restricted Mode"), + name: localize2('untrustedUnsupportedExtensions', "Disabled in Restricted Mode"), ctorDescriptor: new SyncDescriptor(UntrustedWorkspaceUnsupportedExtensionsView, [{}]), when: ContextKeyExpr.and(SearchUnsupportedWorkspaceExtensionsContext), }); viewDescriptors.push({ id: 'workbench.views.extensions.untrustedPartiallySupportedExtensions', - name: localize('untrustedPartiallySupportedExtensions', "Limited in Restricted Mode"), + name: localize2('untrustedPartiallySupportedExtensions', "Limited in Restricted Mode"), ctorDescriptor: new SyncDescriptor(UntrustedWorkspacePartiallySupportedExtensionsView, [{}]), when: ContextKeyExpr.and(SearchUnsupportedWorkspaceExtensionsContext), }); viewDescriptors.push({ id: 'workbench.views.extensions.virtualUnsupportedExtensions', - name: localize('virtualUnsupportedExtensions', "Disabled in Virtual Workspaces"), + name: localize2('virtualUnsupportedExtensions', "Disabled in Virtual Workspaces"), ctorDescriptor: new SyncDescriptor(VirtualWorkspaceUnsupportedExtensionsView, [{}]), when: ContextKeyExpr.and(VirtualWorkspaceContext, SearchUnsupportedWorkspaceExtensionsContext), }); viewDescriptors.push({ id: 'workbench.views.extensions.virtualPartiallySupportedExtensions', - name: localize('virtualPartiallySupportedExtensions', "Limited in Virtual Workspaces"), + name: localize2('virtualPartiallySupportedExtensions', "Limited in Virtual Workspaces"), ctorDescriptor: new SyncDescriptor(VirtualWorkspacePartiallySupportedExtensionsView, [{}]), when: ContextKeyExpr.and(VirtualWorkspaceContext, SearchUnsupportedWorkspaceExtensionsContext), }); @@ -450,7 +455,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio viewDescriptors.push({ id: 'workbench.views.extensions.deprecatedExtensions', - name: localize('deprecated', "Deprecated"), + name: localize2('deprecated', "Deprecated"), ctorDescriptor: new SyncDescriptor(DeprecatedExtensionsView, [{}]), when: ContextKeyExpr.and(SearchDeprecatedExtensionsContext), }); @@ -734,14 +739,14 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE break; case 1: if (view) { - alert(localize('extensionFoundInSection', "1 extension found in the {0} section.", view.name)); + alert(localize('extensionFoundInSection', "1 extension found in the {0} section.", view.name.value)); } else { alert(localize('extensionFound', "1 extension found.")); } break; default: if (view) { - alert(localize('extensionsFoundInSection', "{0} extensions found in the {1} section.", count, view.name)); + alert(localize('extensionsFoundInSection', "{0} extensions found in the {1} section.", count, view.name.value)); } else { alert(localize('extensionsFound', "{0} extensions found.", count)); } @@ -846,7 +851,7 @@ export class StatusUpdater extends Disposable implements IWorkbenchContribution msg += extensionsReloadRequired.length === 1 ? localize('extensionToReload', '{0} requires reload', extensionsReloadRequired.length) : localize('extensionsToReload', '{0} require reload', extensionsReloadRequired.length); } const badge = new NumberBadge(newBadgeNumber, () => msg); - this.badgeHandle.value = this.activityService.showViewContainerActivity(VIEWLET_ID, { badge, clazz: 'extensions-badge count-badge' }); + this.badgeHandle.value = this.activityService.showViewContainerActivity(VIEWLET_ID, { badge }); } } } diff --git a/code/src/vs/workbench/contrib/extensions/browser/media/extension.css b/code/src/vs/workbench/contrib/extensions/browser/media/extension.css index 12e898060f4..c9622b3b43c 100644 --- a/code/src/vs/workbench/contrib/extensions/browser/media/extension.css +++ b/code/src/vs/workbench/contrib/extensions/browser/media/extension.css @@ -260,7 +260,7 @@ margin-left: 6px; } -.extension-list-item .monaco-action-bar > .actions-container > .action-item.action-dropdown-item > .extension-action.label { +.extension-list-item .monaco-action-bar > .actions-container > .action-item.action-dropdown-item:not(.empty) > .extension-action.label { border-right-width: 0; } diff --git a/code/src/vs/workbench/contrib/extensions/common/extensionsInput.ts b/code/src/vs/workbench/contrib/extensions/common/extensionsInput.ts index b5c0ed689b5..180c12ab35b 100644 --- a/code/src/vs/workbench/contrib/extensions/common/extensionsInput.ts +++ b/code/src/vs/workbench/contrib/extensions/common/extensionsInput.ts @@ -28,7 +28,7 @@ export class ExtensionsInput extends EditorInput { } override get capabilities(): EditorInputCapabilities { - return EditorInputCapabilities.Readonly | EditorInputCapabilities.Singleton; + return EditorInputCapabilities.Readonly | EditorInputCapabilities.Singleton | EditorInputCapabilities.AuxWindowUnsupported; } override get resource() { diff --git a/code/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/code/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 581f71ad2e6..5b064b6c620 100644 --- a/code/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/code/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/explorerviewlet'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { mark } from 'vs/base/common/performance'; import { VIEWLET_ID, VIEW_ID, IFilesConfiguration, ExplorerViewletVisibleContext } from 'vs/workbench/contrib/files/common/files'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; @@ -137,7 +137,7 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor private createExplorerViewDescriptor(): IViewDescriptor { return { id: VIEW_ID, - name: localize('folders', "Folders"), + name: localize2('folders', "Folders"), containerIcon: explorerViewIcon, ctorDescriptor: new SyncDescriptor(ExplorerView), order: 1, @@ -253,7 +253,7 @@ const viewContainerRegistry = Registry.as(Extensions.Vi */ export const VIEW_CONTAINER: ViewContainer = viewContainerRegistry.registerViewContainer({ id: VIEWLET_ID, - title: { value: localize('explore', "Explorer"), original: 'Explorer' }, + title: localize2('explore', "Explorer"), ctorDescriptor: new SyncDescriptor(ExplorerViewPaneContainer), storageId: 'workbench.explorer.views.state', icon: explorerViewIcon, @@ -262,7 +262,7 @@ export const VIEW_CONTAINER: ViewContainer = viewContainerRegistry.registerViewC order: 0, openCommandActionDescriptor: { id: VIEWLET_ID, - title: { value: localize('explore', "Explorer"), original: 'Explorer' }, + title: localize2('explore', "Explorer"), mnemonicTitle: localize({ key: 'miViewExplorer', comment: ['&& denotes a mnemonic'] }, "&&Explorer"), keybindings: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyE }, order: 0 diff --git a/code/src/vs/workbench/contrib/files/browser/views/emptyView.ts b/code/src/vs/workbench/contrib/files/browser/views/emptyView.ts index b899155fe46..4f450ff50e0 100644 --- a/code/src/vs/workbench/contrib/files/browser/views/emptyView.ts +++ b/code/src/vs/workbench/contrib/files/browser/views/emptyView.ts @@ -21,11 +21,12 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { isWeb } from 'vs/base/common/platform'; import { DragAndDropObserver } from 'vs/base/browser/dom'; +import { ILocalizedString } from 'vs/platform/action/common/action'; export class EmptyView extends ViewPane { static readonly ID: string = 'workbench.explorer.emptyView'; - static readonly NAME = nls.localize('noWorkspace', "No Folder Opened"); + static readonly NAME: ILocalizedString = nls.localize2('noWorkspace', "No Folder Opened"); private _disposed: boolean = false; constructor( @@ -87,7 +88,7 @@ export class EmptyView extends ViewPane { } if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { - this.updateTitle(EmptyView.NAME); + this.updateTitle(EmptyView.NAME.value); } else { this.updateTitle(this.title); } diff --git a/code/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/code/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 64d9b2cf1a7..e2decbe31f4 100644 --- a/code/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/code/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -51,6 +51,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { ICommandService } from 'vs/platform/commands/common/commands'; import { Schemas } from 'vs/base/common/network'; import { extUriIgnorePathCase } from 'vs/base/common/resources'; +import { ILocalizedString } from 'vs/platform/action/common/action'; const $ = dom.$; @@ -59,7 +60,7 @@ export class OpenEditorsView extends ViewPane { private static readonly DEFAULT_VISIBLE_OPEN_EDITORS = 9; private static readonly DEFAULT_MIN_VISIBLE_OPEN_EDITORS = 0; static readonly ID = 'workbench.explorer.openEditorsView'; - static readonly NAME = nls.localize({ key: 'openEditors', comment: ['Open is an adjective'] }, "Open Editors"); + static readonly NAME: ILocalizedString = nls.localize2({ key: 'openEditors', comment: ['Open is an adjective'] }, "Open Editors"); private dirtyCountElement!: HTMLElement; private listRefreshScheduler: RunOnceScheduler | undefined; diff --git a/code/src/vs/workbench/contrib/files/browser/workspaceWatcher.ts b/code/src/vs/workbench/contrib/files/browser/workspaceWatcher.ts index afedb54aecd..f9100a0eb4d 100644 --- a/code/src/vs/workbench/contrib/files/browser/workspaceWatcher.ts +++ b/code/src/vs/workbench/contrib/files/browser/workspaceWatcher.ts @@ -7,7 +7,7 @@ import { localize } from 'vs/nls'; import { IDisposable, Disposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; -import { IFilesConfiguration } from 'vs/platform/files/common/files'; +import { IFileService, IFilesConfiguration } from 'vs/platform/files/common/files'; import { IWorkspaceContextService, IWorkspaceFolder, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace'; import { ResourceMap } from 'vs/base/common/map'; import { INotificationService, Severity, NeverShowAgainScope, NotificationPriority } from 'vs/platform/notification/common/notification'; @@ -15,14 +15,13 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { isAbsolute } from 'vs/base/common/path'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { IWorkbenchFileService } from 'vs/workbench/services/files/common/files'; export class WorkspaceWatcher extends Disposable { private readonly watchedWorkspaces = new ResourceMap(resource => this.uriIdentityService.extUri.getComparisonKey(resource)); constructor( - @IWorkbenchFileService private readonly fileService: IWorkbenchFileService, + @IFileService private readonly fileService: IFileService, @IConfigurationService private readonly configurationService: IConfigurationService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @INotificationService private readonly notificationService: INotificationService, diff --git a/code/src/vs/workbench/contrib/files/common/dirtyFilesIndicator.ts b/code/src/vs/workbench/contrib/files/common/dirtyFilesIndicator.ts index 6dfdf2560f2..3c8a4afffa1 100644 --- a/code/src/vs/workbench/contrib/files/common/dirtyFilesIndicator.ts +++ b/code/src/vs/workbench/contrib/files/common/dirtyFilesIndicator.ts @@ -60,7 +60,6 @@ export class DirtyFilesIndicator extends Disposable implements IWorkbenchContrib VIEWLET_ID, { badge: new NumberBadge(dirtyCount, num => num === 1 ? nls.localize('dirtyFile', "1 unsaved file") : nls.localize('dirtyFiles', "{0} unsaved files", dirtyCount)), - clazz: 'explorer-viewlet-label' } ); } else { diff --git a/code/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts b/code/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts index 1f89d3dc33c..185bab56c8e 100644 --- a/code/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts +++ b/code/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts @@ -23,6 +23,8 @@ import { DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor'; import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; +import { IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; +import { TestAccessibleNotificationService } from 'vs/platform/accessibility/browser/accessibleNotificationService'; suite('EditorAutoSave', () => { @@ -42,7 +44,7 @@ suite('EditorAutoSave', () => { const configurationService = new TestConfigurationService(); configurationService.setUserConfiguration('files', autoSaveConfig); instantiationService.stub(IConfigurationService, configurationService); - + instantiationService.stub(IAccessibleNotificationService, disposables.add(new TestAccessibleNotificationService())); instantiationService.stub(IFilesConfigurationService, disposables.add(new TestFilesConfigurationService( instantiationService.createInstance(MockContextKeyService), configurationService, diff --git a/code/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css b/code/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css index cc4c61f55a8..12d60096fcb 100644 --- a/code/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css +++ b/code/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css @@ -14,6 +14,7 @@ .monaco-editor .inline-chat { color: inherit; padding: 6px; + margin-top: 6px; border-radius: 6px; border: 1px solid var(--vscode-inlineChat-border); box-shadow: 0 4px 8px var(--vscode-inlineChat-shadow); @@ -143,6 +144,14 @@ align-self: center; } +.monaco-editor .inline-chat .status .label .slash-command-pill CODE { + border-radius: 3px; + padding: 0 1px; + background-color: var(--vscode-chat-slashCommandBackground); + color: var(--vscode-chat-slashCommandForeground); +} + + .monaco-editor .inline-chat .markdownMessage { padding: 10px 5px; } @@ -288,10 +297,6 @@ /* diff zone */ -.monaco-editor .inline-chat-diff-widget { - padding: 6px 0; -} - .monaco-editor .inline-chat-diff-widget .monaco-diff-editor .monaco-editor-background, .monaco-editor .inline-chat-diff-widget .monaco-diff-editor .monaco-editor .margin-view-overlays { background-color: var(--vscode-inlineChat-regionHighlight); diff --git a/code/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/code/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 0d09c9134f5..68555b96521 100644 --- a/code/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/code/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -237,13 +237,13 @@ export class InlineChatController implements IEditorContribution { let widgetPosition: Position; if (initialRender) { - widgetPosition = position ? Position.lift(position) : this._editor.getSelection().getEndPosition(); + widgetPosition = position ? Position.lift(position) : this._editor.getSelection().getEndPosition().delta(-1); this._zone.value.setContainerMargins(); this._zone.value.setWidgetMargins(widgetPosition); } else { assertType(this._activeSession); assertType(this._strategy); - widgetPosition = this._strategy.getWidgetPosition() ?? this._zone.value.position ?? this._activeSession.wholeRange.value.getEndPosition(); + widgetPosition = this._strategy.getWidgetPosition() ?? this._zone.value.position ?? this._activeSession.wholeRange.value.getStartPosition(); const needsMargin = this._strategy.needsMargin(); if (!needsMargin) { this._zone.value.setWidgetMargins(widgetPosition, 0); @@ -271,7 +271,7 @@ export class InlineChatController implements IEditorContribution { this._showWidget(true, options.position); this._zone.value.widget.updateInfo(localize('welcome.1', "AI-generated code may be incorrect")); - this._zone.value.widget.placeholder = this._getPlaceholderText(); + this._updatePlaceholder(); if (!session) { const createSessionCts = new CancellationTokenSource(); @@ -346,7 +346,7 @@ export class InlineChatController implements IEditorContribution { updateWholeRangeDecoration(); this._zone.value.widget.updateSlashCommands(this._activeSession.session.slashCommands ?? []); - this._zone.value.widget.placeholder = this._getPlaceholderText(); + this._updatePlaceholder(); this._zone.value.widget.updateInfo(this._activeSession.session.message ?? localize('welcome.1', "AI-generated code may be incorrect")); this._zone.value.widget.preferredExpansionState = this._activeSession.lastExpansionState; this._zone.value.widget.value = this._activeSession.lastInput?.value ?? this._zone.value.widget.value; @@ -409,8 +409,23 @@ export class InlineChatController implements IEditorContribution { } } + private _placeholder: string | undefined = undefined; + setPlaceholder(text: string): void { + this._placeholder = text; + this._updatePlaceholder(); + } + + resetPlaceholder(): void { + this._placeholder = undefined; + this._updatePlaceholder(); + } + + private _updatePlaceholder(): void { + this._zone.value.widget.placeholder = this._getPlaceholderText(); + } + private _getPlaceholderText(): string { - let result = this._activeSession?.session.placeholder ?? localize('default.placeholder', "Ask a question"); + let result = this._placeholder ?? this._activeSession?.session.placeholder ?? localize('default.placeholder', "Ask a question"); if (InlineChatController._promptHistory.length > 0) { const kb1 = this._keybindingService.lookupKeybinding('inlineChat.previousFromHistory')?.getLabel(); const kb2 = this._keybindingService.lookupKeybinding('inlineChat.nextFromHistory')?.getLabel(); @@ -427,7 +442,7 @@ export class InlineChatController implements IEditorContribution { assertType(this._activeSession); assertType(this._strategy); - this._zone.value.widget.placeholder = this._getPlaceholderText(); + this._updatePlaceholder(); if (options.message) { this.updateInput(options.message); @@ -520,9 +535,7 @@ export class InlineChatController implements IEditorContribution { requestCts.cancel(); }); - const typeListener = this._zone.value.widget.onDidChangeInput(() => { - requestCts.cancel(); - }); + const typeListener = this._zone.value.widget.onDidChangeInput(() => requestCts.cancel()); const sw = StopWatch.create(); const request: IInlineChatRequest = { @@ -538,14 +551,20 @@ export class InlineChatController implements IEditorContribution { const progressEdits: TextEdit[][] = []; const progress = new Progress(async data => { this._log('received chunk', data, request); - if (!request.live) { - throw new Error('Progress in NOT supported in non-live mode'); - } if (data.message) { this._zone.value.widget.updateToolbar(false); this._zone.value.widget.updateInfo(data.message); } + if (data.slashCommand) { + const valueNow = this._zone.value.widget.value; + if (!valueNow.startsWith('/')) { + this._zone.value.widget.updateSlashCommandUsed(data.slashCommand); + } + } if (data.edits) { + if (!request.live) { + throw new Error('Progress in NOT supported in non-live mode'); + } progressEdits.push(data.edits); await this._makeChanges(progressEdits, false); } diff --git a/code/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts b/code/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts index 2d2a2a7a5bb..e4a8b22b0aa 100644 --- a/code/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts +++ b/code/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts @@ -244,7 +244,7 @@ export class InlineChatLivePreviewWidget extends ZoneWidget { originalDiffHidden, modifiedHidden: modifiedLineRange, modifiedDiffHidden, - anchor: new Position(modifiedLineRange.endLineNumberExclusive - 1, Number.MAX_SAFE_INTEGER) + anchor: new Position(modifiedLineRange.startLineNumber - 1, 1) }; } diff --git a/code/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/code/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 5ffa49b8217..f14ad6adf0c 100644 --- a/code/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/code/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -436,7 +436,7 @@ export class LivePreviewStrategy extends LiveStrategy { override getWidgetPosition(): Position | undefined { if (this._session.lastTextModelChanges.length) { - return this._session.wholeRange.value.getEndPosition(); + return this._session.wholeRange.value.getStartPosition().delta(-1); } return; } diff --git a/code/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/code/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index f925df2421d..a03c1f0c7dc 100644 --- a/code/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/code/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -54,6 +54,8 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; +import { assertType } from 'vs/base/common/types'; +import { renderFormattedText } from 'vs/base/browser/formattedTextRenderer'; const defaultAriaLabel = localize('aria-label', "Inline Chat Input"); @@ -557,6 +559,20 @@ export class InlineChatWidget { this._elements.message.setAttribute('state', expansionState); } + updateSlashCommandUsed(command: string): void { + const details = this._slashCommandDetails.find(candidate => candidate.command === command); + if (!details) { + return; + } + + this._elements.infoLabel.classList.toggle('hidden', false); + const label = localize('slashCommandUsed', "Using {0} to generate response...", `\`\`/${details.command}\`\``); + + const e = renderFormattedText(label, { inline: true, renderCodeSegments: true, className: 'slash-command-pill' }); + reset(this._elements.infoLabel, e); + this._onDidChangeHeight.fire(); + } + updateInfo(message: string): void { this._elements.infoLabel.classList.toggle('hidden', !message); const renderedMessage = renderLabelWithIcons(message); @@ -793,7 +809,7 @@ export class InlineChatZoneWidget extends ZoneWidget { @IInstantiationService private readonly _instaService: IInstantiationService, @IContextKeyService contextKeyService: IContextKeyService, ) { - super(editor, { showFrame: false, showArrow: false, isAccessible: true, className: 'inline-chat-widget', keepEditorSelection: true, showInHiddenAreas: true, ordinal: 10000 + 3 }); + super(editor, { showFrame: false, showArrow: false, isAccessible: true, className: 'inline-chat-widget', keepEditorSelection: true, showInHiddenAreas: true, ordinal: 10000 }); this._ctxVisible = CTX_INLINE_CHAT_VISIBLE.bindTo(contextKeyService); this._ctxCursorPosition = CTX_INLINE_CHAT_OUTER_CURSOR_POSITION.bindTo(contextKeyService); @@ -873,12 +889,10 @@ export class InlineChatZoneWidget extends ZoneWidget { return info.width - info.minimap.minimapWidth; } - updateBackgroundColor(position: Position, selection: IRange) { - if (!this.container) { - return; - } - const widgetLineNumber = position.lineNumber; - this.container.classList.toggle('inside-selection', widgetLineNumber >= selection.startLineNumber && widgetLineNumber < selection.endLineNumber); + updateBackgroundColor(newPosition: Position, wholeRange: IRange) { + assertType(this.container); + const widgetLineNumber = newPosition.lineNumber; + this.container.classList.toggle('inside-selection', widgetLineNumber > wholeRange.startLineNumber && widgetLineNumber < wholeRange.endLineNumber); } private _calculateIndentationWidth(position: Position): number { @@ -903,9 +917,8 @@ export class InlineChatZoneWidget extends ZoneWidget { } setContainerMargins(): void { - if (!this.container) { - return; - } + assertType(this.container); + const info = this.editor.getLayoutInfo(); const marginWithoutIndentation = info.glyphMarginWidth + info.decorationsWidth + info.lineNumbersWidth; this.container.style.marginLeft = `${marginWithoutIndentation}px`; diff --git a/code/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/code/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index 8643d6c62c9..e358d1368b8 100644 --- a/code/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/code/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -86,6 +86,7 @@ export interface IInlineChatMessageResponse { export interface IInlineChatProgressItem { edits?: TextEdit[]; message?: string; + slashCommand?: string; } export const enum InlineChatResponseFeedbackKind { diff --git a/code/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts b/code/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts index aece1b21e47..534443553ba 100644 --- a/code/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts +++ b/code/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts @@ -132,6 +132,7 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot override get capabilities(): EditorInputCapabilities { return EditorInputCapabilities.Untitled | EditorInputCapabilities.Readonly + | EditorInputCapabilities.AuxWindowUnsupported | EditorInputCapabilities.Scratchpad; } diff --git a/code/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts b/code/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts index c5900bbf721..b2af996d57e 100644 --- a/code/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts +++ b/code/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts @@ -30,6 +30,7 @@ import { URI } from 'vs/base/common/uri'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; +import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility'; class LanguageStatusViewModel { @@ -182,7 +183,7 @@ class EditorStatusContribution implements IWorkbenchContribution { for (const status of model.combined) { const isPinned = model.dedicated.includes(status); element.appendChild(this._renderStatus(status, showSeverity, isPinned, this._renderDisposables)); - ariaLabels.push(this._asAriaLabel(status)); + ariaLabels.push(EditorStatusContribution._accessibilityInformation(status).label); isOneBusy = isOneBusy || (!isPinned && status.busy); // unpinned items contribute to the busy-indicator of the composite status item } const props: IStatusbarEntry = { @@ -275,7 +276,8 @@ class EditorStatusContribution implements IWorkbenchContribution { const label = document.createElement('span'); label.classList.add('label'); - dom.append(label, ...renderLabelWithIcons(status.busy ? `$(sync~spin)\u00A0\u00A0${status.label}` : status.label)); + const labelValue = typeof status.label === 'string' ? status.label : status.label.value; + dom.append(label, ...renderLabelWithIcons(status.busy ? `$(sync~spin)\u00A0\u00A0${labelValue}` : labelValue)); left.appendChild(label); const detail = document.createElement('span'); @@ -351,13 +353,15 @@ class EditorStatusContribution implements IWorkbenchContribution { } } - private _asAriaLabel(status: ILanguageStatus): string { + private static _accessibilityInformation(status: ILanguageStatus): IAccessibilityInformation { if (status.accessibilityInfo) { - return status.accessibilityInfo.label; - } else if (status.detail) { - return localize('aria.1', '{0}, {1}', status.label, status.detail); + return status.accessibilityInfo; + } + const textValue = typeof status.label === 'string' ? status.label : status.label.value; + if (status.detail) { + return { label: localize('aria.1', '{0}, {1}', textValue, status.detail) }; } else { - return localize('aria.2', '{0}', status.label); + return { label: localize('aria.2', '{0}', textValue) }; } } @@ -372,10 +376,12 @@ class EditorStatusContribution implements IWorkbenchContribution { kind = 'error'; } + const textValue = typeof item.label === 'string' ? item.label : item.label.shortValue; + return { name: localize('name.pattern', '{0} (Language Status)', item.name), - text: item.busy ? `${item.label}\u00A0\u00A0$(sync~spin)` : item.label, - ariaLabel: item.accessibilityInfo?.label ?? item.label, + text: item.busy ? `${textValue}\u00A0\u00A0$(sync~spin)` : textValue, + ariaLabel: EditorStatusContribution._accessibilityInformation(item).label, role: item.accessibilityInfo?.role, tooltip: item.command?.tooltip || new MarkdownString(item.detail, { isTrusted: true, supportThemeIcons: true }), kind, diff --git a/code/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/code/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index ef2f37cee8c..275a84b8c9a 100644 --- a/code/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/code/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -128,7 +128,7 @@ const markersViewIcon = registerIcon('markers-view-icon', Codicon.warning, local // markers view container const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: Markers.MARKERS_CONTAINER_ID, - title: { value: Messages.MARKERS_PANEL_TITLE_PROBLEMS, original: Messages.MARKERS_PANEL_ORIGINAL_TITLE_PROBLEMS }, + title: Messages.MARKERS_PANEL_TITLE_PROBLEMS, icon: markersViewIcon, hideIfEmpty: true, order: 0, diff --git a/code/src/vs/workbench/contrib/markers/browser/markersView.ts b/code/src/vs/workbench/contrib/markers/browser/markersView.ts index 1e7bf9e7073..5845464df98 100644 --- a/code/src/vs/workbench/contrib/markers/browser/markersView.ts +++ b/code/src/vs/workbench/contrib/markers/browser/markersView.ts @@ -222,7 +222,7 @@ export class MarkersView extends FilterViewPane implements IMarkersView { } public getTitle(): string { - return Messages.MARKERS_PANEL_TITLE_PROBLEMS; + return Messages.MARKERS_PANEL_TITLE_PROBLEMS.value; } protected layoutBodyContent(height: number = this.currentHeight, width: number = this.currentWidth): void { diff --git a/code/src/vs/workbench/contrib/markers/browser/messages.ts b/code/src/vs/workbench/contrib/markers/browser/messages.ts index 767485b3692..e6a387f0ce2 100644 --- a/code/src/vs/workbench/contrib/markers/browser/messages.ts +++ b/code/src/vs/workbench/contrib/markers/browser/messages.ts @@ -6,6 +6,7 @@ import * as nls from 'vs/nls'; import { basename } from 'vs/base/common/resources'; import { MarkerSeverity, IRelatedInformation } from 'vs/platform/markers/common/markers'; +import { ILocalizedString } from 'vs/platform/action/common/action'; import { Marker } from './markersModel'; export default class Messages { @@ -21,8 +22,7 @@ export default class Messages { public static PROBLEMS_PANEL_CONFIGURATION_COMPARE_ORDER_SEVERITY: string = nls.localize('problems.panel.configuration.compareOrder.severity', "Navigate problems ordered by severity"); public static PROBLEMS_PANEL_CONFIGURATION_COMPARE_ORDER_POSITION: string = nls.localize('problems.panel.configuration.compareOrder.position', "Navigate problems ordered by position"); - public static MARKERS_PANEL_ORIGINAL_TITLE_PROBLEMS: string = 'Problems'; - public static MARKERS_PANEL_TITLE_PROBLEMS: string = nls.localize('markers.panel.title.problems', "Problems"); + public static MARKERS_PANEL_TITLE_PROBLEMS: ILocalizedString = nls.localize2('markers.panel.title.problems', "Problems"); public static MARKERS_PANEL_NO_PROBLEMS_BUILT: string = nls.localize('markers.panel.no.problems.build', "No problems have been detected in the workspace."); public static MARKERS_PANEL_NO_PROBLEMS_ACTIVE_FILE_BUILT: string = nls.localize('markers.panel.no.problems.activeFile.build', "No problems have been detected in the current file."); diff --git a/code/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts b/code/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts index d93af0a7a1c..4996b79f268 100644 --- a/code/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts +++ b/code/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts @@ -419,7 +419,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis [NotebookSetting.gotoSymbolsAllSymbols]: { type: 'boolean', default: false, - markdownDescription: localize('notebook.gotoSymbols.showAllSymbols', "When enabled goto symbol quickpick will display full code symbols from the notebook, as well as markdown headers.") + markdownDescription: localize('notebook.gotoSymbols.showAllSymbols', "When enabled the Go to Symbol Quick Pick will display full code symbols from the notebook, as well as Markdown headers.") }, } }); diff --git a/code/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts b/code/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts index aca17332c77..3b3ed7da385 100644 --- a/code/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts +++ b/code/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts @@ -383,7 +383,8 @@ export function parseMultiCellExecutionArgs(accessor: ServicesAccessor, ...args: return context ? { ui: false, notebookEditor: context.notebookEditor, - selectedCells: context.selectedCells ?? [] + selectedCells: context.selectedCells ?? [], + cell: context.cell } : undefined; } diff --git a/code/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts b/code/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts index f69513cb94e..d9c99e9cd17 100644 --- a/code/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts +++ b/code/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts @@ -81,9 +81,10 @@ async function runCell(editorGroupsService: IEditorGroupsService, context: INote const cellIndex = context.notebookEditor.getCellIndex(context.cell); context.notebookEditor.revealCellRangeInView({ start: cellIndex, end: cellIndex + 1 }); } - } else if (context.selectedCells) { - await context.notebookEditor.executeNotebookCells(context.selectedCells); - const firstCell = context.selectedCells[0]; + } else if (context.selectedCells?.length || context.cell) { + const selectedCells = context.selectedCells?.length ? context.selectedCells : [context.cell!]; + await context.notebookEditor.executeNotebookCells(selectedCells); + const firstCell = selectedCells[0]; if (firstCell && context.autoReveal) { const cellIndex = context.notebookEditor.getCellIndex(firstCell); diff --git a/code/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/code/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 1c534d746b2..c1dcf42d739 100644 --- a/code/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/code/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -946,7 +946,7 @@ configurationRegistry.registerConfiguration({ tags: ['notebookLayout', 'notebookOutputLayout'] }, [NotebookSetting.outputScrolling]: { - markdownDescription: nls.localize('notebook.outputScrolling', "Initially render notebook outputs in a scrollable region when longer than the limit"), + markdownDescription: nls.localize('notebook.outputScrolling', "Initially render notebook outputs in a scrollable region when longer than the limit."), type: 'boolean', tags: ['notebookLayout', 'notebookOutputLayout'], default: typeof product.quality === 'string' && product.quality !== 'stable' // only enable as default in insiders @@ -964,7 +964,7 @@ configurationRegistry.registerConfiguration({ default: false }, [NotebookSetting.codeActionsOnSave]: { - markdownDescription: nls.localize('notebook.codeActionsOnSave', 'Run a series of CodeActions for a notebook on save. CodeActions must be specified, the file must not be saved after delay, and the editor must not be shutting down. Example: `"notebook.source.organizeImports": "explicit"`'), + markdownDescription: nls.localize('notebook.codeActionsOnSave', 'Run a series of Code Actions for a notebook on save. Code Actions must be specified, the file must not be saved after delay, and the editor must not be shutting down. Example: `"notebook.source.organizeImports": "explicit"`'), type: 'object', additionalProperties: { type: ['string', 'boolean'], diff --git a/code/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/code/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 43ffa341971..64d4a6cb34f 100644 --- a/code/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/code/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -2951,9 +2951,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD const cell = this.viewModel?.viewCells.find(vc => vc.handle === cellInfo.cellHandle); if (cell && cell instanceof CodeCellViewModel) { const outputIndex = cell.outputsViewModels.indexOf(output); - if (outputHeight !== 0) { - cell.updateOutputMinHeight(0); - } this._debug('update cell output', cell.handle, outputHeight); cell.updateOutputHeight(outputIndex, outputHeight, source); this.layoutNotebookCell(cell, cell.layoutInfo.totalHeight); diff --git a/code/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts b/code/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts index 55f6fff83ba..58249689286 100644 --- a/code/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts +++ b/code/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts @@ -444,7 +444,7 @@ export class NotebookOptions extends Disposable { configuration.fontSize = this.configurationService.getValue('editor.fontSize'); } - if (outputFontSize) { + if (outputFontSize || fontSize) { configuration.outputFontSize = this.configurationService.getValue(NotebookSetting.outputFontSize) || configuration.fontSize; } diff --git a/code/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput.ts b/code/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput.ts index 01fd2d2d227..39491a8f5c9 100644 --- a/code/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput.ts +++ b/code/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput.ts @@ -28,7 +28,7 @@ import { CellContentPart } from 'vs/workbench/contrib/notebook/browser/view/cell import { CodeCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { CellUri, IOrderedMimeType, NotebookCellOutputsSplice, RENDERER_NOT_AVAILABLE, isTextStreamMime } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellUri, IOrderedMimeType, NotebookCellExecutionState, NotebookCellOutputsSplice, RENDERER_NOT_AVAILABLE, isTextStreamMime } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { INotebookKernel } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; @@ -590,12 +590,14 @@ export class CellOutputContainer extends CellContentPart { clearTimeout(this._outputHeightTimer); } + const executionState = this._notebookExecutionStateService.getCellExecution(this.viewCell.uri); + if (synchronous) { this.viewCell.unlockOutputHeight(); - } else { + } else if (executionState?.state !== NotebookCellExecutionState.Executing) { this._outputHeightTimer = setTimeout(() => { this.viewCell.unlockOutputHeight(); - }, 1000); + }, 200); } } @@ -714,9 +716,6 @@ export class CellOutputContainer extends CellContentPart { DOM.hide(this.templateData.outputShowMoreContainer.domNode); } - const editorHeight = this.templateData.editor.getContentHeight(); - this.viewCell.editorHeight = editorHeight; - this._relayoutCell(); // if it's clearing all outputs, or outputs are all rendered synchronously // shrink immediately as the final output height will be zero. diff --git a/code/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/code/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index ee9b0ad3f36..1b3ad2962c3 100644 --- a/code/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/code/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -1207,14 +1207,20 @@ export class NotebookCellList extends WorkbenchList implements ID const focused = this.getFocus(); const focus = focused.length ? focused[0] : null; - const anchorFocusedSetting = this.configurationService.getValue(NotebookSetting.anchorToFocusedCell); - const allowScrolling = this.configurationService.getValue(NotebookSetting.scrollToRevealCell) !== 'none'; - const scrollHeuristic = allowScrolling && anchorFocusedSetting === 'auto' && this.view.elementTop(index) < this.view.getScrollTop(); - if (focused && (anchorFocusedSetting === 'on' || scrollHeuristic)) { - this.view.updateElementHeight(index, size, focus); + // If the cell is growing, we should favor anchoring to the focused cell + if (focus) { + const cellEditorIsFocused = this.view.element(focused[0]).focusMode === CellFocusMode.Editor; + const anchorFocusedSetting = this.configurationService.getValue(NotebookSetting.anchorToFocusedCell); + const growing = this.view.elementHeight(index) < size; + const allowScrolling = this.configurationService.getValue(NotebookSetting.scrollToRevealCell) !== 'none'; + const autoAnchor = allowScrolling && growing && anchorFocusedSetting !== 'off'; + + if (cellEditorIsFocused || autoAnchor || anchorFocusedSetting === 'on') { + return this.view.updateElementHeight(index, size, focus); + } } - this.view.updateElementHeight(index, size, null); + return this.view.updateElementHeight(index, size, null); } // override diff --git a/code/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts b/code/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts index 0524f62bde4..1ac7010369c 100644 --- a/code/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts +++ b/code/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts @@ -111,7 +111,7 @@ export class NotebookEditorInput extends AbstractResourceEditorInput { } override get capabilities(): EditorInputCapabilities { - let capabilities = EditorInputCapabilities.None; + let capabilities = EditorInputCapabilities.AuxWindowUnsupported; if (this.resource.scheme === Schemas.untitled) { capabilities |= EditorInputCapabilities.Untitled; diff --git a/code/src/vs/workbench/contrib/notebook/test/browser/notebookCellList.test.ts b/code/src/vs/workbench/contrib/notebook/test/browser/notebookCellList.test.ts index 2137c0421c9..2573533424e 100644 --- a/code/src/vs/workbench/contrib/notebook/test/browser/notebookCellList.test.ts +++ b/code/src/vs/workbench/contrib/notebook/test/browser/notebookCellList.test.ts @@ -22,12 +22,14 @@ suite('NotebookCellList', () => { ensureNoDisposablesAreLeakedInTestSuite(); + let config: TestConfigurationService; setup(() => { testDisposables = new DisposableStore(); instantiationService = setupInstantiationService(testDisposables); - const config = new TestConfigurationService({ + config = new TestConfigurationService({ [NotebookSetting.anchorToFocusedCell]: 'auto' }); + instantiationService.stub(IConfigurationService, config); }); @@ -201,7 +203,7 @@ suite('NotebookCellList', () => { }); }); - test('updateElementHeight with anchor #121723', async function () { + test('updateElementHeight with anchor', async function () { await withTestNotebook( [ ['# header a', 'markdown', CellKind.Markup, [], {}], @@ -231,6 +233,7 @@ suite('NotebookCellList', () => { assert.deepStrictEqual(cellList.getViewScrollBottom(), 210); // scroll to 5 + cellList.updateElementHeight2(viewModel.cellAt(0)!, 50); cellList.scrollTop = 5; assert.deepStrictEqual(cellList.scrollTop, 5); assert.deepStrictEqual(cellList.getViewScrollBottom(), 215); @@ -239,13 +242,14 @@ suite('NotebookCellList', () => { cellList.updateElementHeight2(viewModel.cellAt(0)!, 100); assert.deepStrictEqual(cellList.scrollHeight, 400); - // the first cell grows, but it's partially visible, so we won't push down the focused cell + // the first cell grows, but we anchor to the focused cell, so the notebook will scroll down assert.deepStrictEqual(cellList.scrollTop, 55); assert.deepStrictEqual(cellList.getViewScrollBottom(), 265); + // We don't anchor to the focused cell when cells shrink cellList.updateElementHeight2(viewModel.cellAt(0)!, 50); - assert.deepStrictEqual(cellList.scrollTop, 5); - assert.deepStrictEqual(cellList.getViewScrollBottom(), 215); + assert.deepStrictEqual(cellList.scrollTop, 55); + assert.deepStrictEqual(cellList.getViewScrollBottom(), 265); // focus won't be visible after cell 0 grow to 250, so let's try to keep the focused cell visible cellList.updateElementHeight2(viewModel.cellAt(0)!, 250); @@ -254,7 +258,103 @@ suite('NotebookCellList', () => { }); }); - test('updateElementHeight with anchor #121723: focus element out of viewport', async function () { + test('updateElementHeight with no scrolling', async function () { + config.setUserConfiguration(NotebookSetting.scrollToRevealCell, 'none'); + await withTestNotebook( + [ + ['# header a', 'markdown', CellKind.Markup, [], {}], + ['var b = 1;', 'javascript', CellKind.Code, [], {}], + ['# header b', 'markdown', CellKind.Markup, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}], + ['# header c', 'markdown', CellKind.Markup, [], {}] + ], + async (editor, viewModel, disposables) => { + viewModel.restoreEditorViewState({ + editingCells: [false, false, false, false, false], + editorViewStates: [null, null, null, null, null], + cellTotalHeights: [50, 100, 50, 100, 50], + cellLineNumberStates: {}, + collapsedInputCells: {}, + collapsedOutputCells: {}, + }); + const cellList = createNotebookCellList(instantiationService, disposables); + cellList.attachViewModel(viewModel); + + // render height 210, it can render 3 full cells and 1 partial cell + cellList.layout(210, 100); + + // init scrollTop and scrollBottom + assert.deepStrictEqual(cellList.scrollTop, 0); + assert.deepStrictEqual(cellList.getViewScrollBottom(), 210); + + // scroll to 5 + cellList.updateElementHeight2(viewModel.cellAt(0)!, 50); + cellList.scrollTop = 5; + assert.deepStrictEqual(cellList.scrollTop, 5); + assert.deepStrictEqual(cellList.getViewScrollBottom(), 215); + + cellList.setFocus([1]); + cellList.updateElementHeight2(viewModel.cellAt(0)!, 100); + assert.deepStrictEqual(cellList.scrollHeight, 400); + + // Any change in cell size should not affect the scroll height with scrollToReveal set to none + assert.deepStrictEqual(cellList.scrollTop, 5); + + cellList.updateElementHeight2(viewModel.cellAt(0)!, 50); + assert.deepStrictEqual(cellList.scrollTop, 5); + + cellList.updateElementHeight2(viewModel.cellAt(0)!, 250); + assert.deepStrictEqual(cellList.scrollTop, 5); + }); + }); + + test('updateElementHeight with no scroll setting and cell editor focused', async function () { + config.setUserConfiguration(NotebookSetting.scrollToRevealCell, 'none'); + await withTestNotebook( + [ + ['# header a', 'markdown', CellKind.Markup, [], {}], + ['var b = 1;', 'javascript', CellKind.Code, [], {}], + ['# header b', 'markdown', CellKind.Markup, [], {}], + ['var b = 2;', 'javascript', CellKind.Code, [], {}], + ['# header c', 'markdown', CellKind.Markup, [], {}] + ], + async (editor, viewModel, disposables) => { + viewModel.restoreEditorViewState({ + editingCells: [false, false, false, false, false], + editorViewStates: [null, null, null, null, null], + cellTotalHeights: [50, 100, 50, 100, 50], + cellLineNumberStates: {}, + collapsedInputCells: {}, + collapsedOutputCells: {}, + }); + const cellList = createNotebookCellList(instantiationService, disposables); + cellList.attachViewModel(viewModel); + + // render height 210, it can render 3 full cells and 1 partial cell + cellList.layout(210, 100); + + // init scrollTop and scrollBottom + assert.deepStrictEqual(cellList.scrollTop, 0); + assert.deepStrictEqual(cellList.getViewScrollBottom(), 210); + + cellList.setFocus([1]); + + editor.focusNotebookCell(cellList.viewModel?.cellAt(1)!, 'editor'); + cellList.updateElementHeight2(viewModel.cellAt(0)!, 100); + assert.deepStrictEqual(cellList.scrollHeight, 400); + + // We have the cell editor focused, so we should anchor to that cell + assert.deepStrictEqual(cellList.scrollTop, 50); + + cellList.updateElementHeight2(viewModel.cellAt(0)!, 50); + assert.deepStrictEqual(cellList.scrollTop, 0); + + cellList.updateElementHeight2(viewModel.cellAt(0)!, 250); + assert.deepStrictEqual(cellList.scrollTop, 250 + 100 - cellList.renderHeight); + }); + }); + + test('updateElementHeight with focused element out of viewport', async function () { await withTestNotebook( [ ['# header a', 'markdown', CellKind.Markup, [], {}], diff --git a/code/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts b/code/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts index d20676cf47e..a4c615cd48e 100644 --- a/code/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts +++ b/code/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts @@ -41,7 +41,7 @@ import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { EditorModel } from 'vs/workbench/common/editor/editorModel'; -import { CellFindMatchWithIndex, IActiveNotebookEditorDelegate, IBaseCellEditorOptions, ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellFindMatchWithIndex, CellFocusMode, IActiveNotebookEditorDelegate, IBaseCellEditorOptions, ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookCellStateChangedEvent, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents'; import { NotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/browser/services/notebookCellStatusBarServiceImpl'; import { ListViewInfoAccessor, NotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList'; @@ -276,7 +276,11 @@ function _createTestNotebookEditor(instantiationService: TestInstantiationServic override async revealRangeInCenterIfOutsideViewportAsync() { } override async layoutNotebookCell() { } override async removeInset() { } - override async focusNotebookCell() { } + override async focusNotebookCell(cell: ICellViewModel, focusItem: 'editor' | 'container' | 'output') { + cell.focusMode = focusItem === 'editor' ? CellFocusMode.Editor + : focusItem === 'output' ? CellFocusMode.Output + : CellFocusMode.Container; + } override cellAt(index: number) { return viewModel.cellAt(index)!; } override getCellIndex(cell: ICellViewModel) { return viewModel.getCellIndex(cell); } override getCellsInRange(range?: ICellRange) { return viewModel.getCellsInRange(range); } diff --git a/code/src/vs/workbench/contrib/outline/browser/outline.contribution.ts b/code/src/vs/workbench/contrib/outline/browser/outline.contribution.ts index 51085e4a660..1564652e5fa 100644 --- a/code/src/vs/workbench/contrib/outline/browser/outline.contribution.ts +++ b/code/src/vs/workbench/contrib/outline/browser/outline.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { IViewsRegistry, Extensions as ViewExtensions } from 'vs/workbench/common/views'; import { OutlinePane } from './outlinePane'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -25,7 +25,7 @@ const outlineViewIcon = registerIcon('outline-view-icon', Codicon.symbolClass, l Registry.as(ViewExtensions.ViewsRegistry).registerViews([{ id: IOutlinePane.Id, - name: localize('name', "Outline"), + name: localize2('name', "Outline"), containerIcon: outlineViewIcon, ctorDescriptor: new SyncDescriptor(OutlinePane), canToggleVisibility: true, diff --git a/code/src/vs/workbench/contrib/output/browser/output.contribution.ts b/code/src/vs/workbench/contrib/output/browser/output.contribution.ts index 45dbbb0fb66..358b67f43e6 100644 --- a/code/src/vs/workbench/contrib/output/browser/output.contribution.ts +++ b/code/src/vs/workbench/contrib/output/browser/output.contribution.ts @@ -28,7 +28,7 @@ import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { Disposable, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; // Register Service registerSingleton(IOutputService, OutputService, InstantiationType.Delayed); @@ -51,7 +51,7 @@ ModesRegistry.registerLanguage({ const outputViewIcon = registerIcon('output-view-icon', Codicon.output, nls.localize('outputViewIcon', 'View icon of the output view.')); const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: OUTPUT_VIEW_ID, - title: { value: nls.localize('output', "Output"), original: 'Output' }, + title: nls.localize2('output', "Output"), icon: outputViewIcon, order: 1, ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [OUTPUT_VIEW_ID, { mergeViewWithContainerWhenSingleView: true }]), @@ -61,7 +61,7 @@ const VIEW_CONTAINER: ViewContainer = Registry.as(ViewC Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([{ id: OUTPUT_VIEW_ID, - name: nls.localize('output', "Output"), + name: nls.localize2('output', "Output"), containerIcon: outputViewIcon, canMoveView: true, canToggleVisibility: false, @@ -164,8 +164,8 @@ class OutputContribution extends Disposable implements IWorkbenchContribution { constructor() { super({ id: 'workbench.action.showOutputChannels', - title: { value: nls.localize('showOutputChannels', "Show Output Channels..."), original: 'Show Output Channels...' }, - category: { value: nls.localize('output', "Output"), original: 'Output' }, + title: nls.localize2('showOutputChannels', "Show Output Channels..."), + category: nls.localize2('output', "Output"), f1: true }); } @@ -203,7 +203,7 @@ class OutputContribution extends Disposable implements IWorkbenchContribution { constructor() { super({ id: `workbench.output.action.clearOutput`, - title: { value: nls.localize('clearOutput.label', "Clear Output"), original: 'Clear Output' }, + title: nls.localize2('clearOutput.label', "Clear Output"), category: Categories.View, menu: [{ id: MenuId.ViewTitle, @@ -221,11 +221,11 @@ class OutputContribution extends Disposable implements IWorkbenchContribution { } async run(accessor: ServicesAccessor): Promise { const outputService = accessor.get(IOutputService); - const accesibilityService = accessor.get(IAccessibilityService); + const accessibleNotificationService = accessor.get(IAccessibleNotificationService); const activeChannel = outputService.getActiveChannel(); if (activeChannel) { activeChannel.clear(); - accesibilityService.alertCleared(); + accessibleNotificationService.notify(AccessibleNotificationEvent.Clear); } } })); @@ -236,7 +236,7 @@ class OutputContribution extends Disposable implements IWorkbenchContribution { constructor() { super({ id: `workbench.output.action.toggleAutoScroll`, - title: { value: nls.localize('toggleAutoScroll', "Toggle Auto Scrolling"), original: 'Toggle Auto Scrolling' }, + title: nls.localize2('toggleAutoScroll', "Toggle Auto Scrolling"), tooltip: nls.localize('outputScrollOff', "Turn Auto Scrolling Off"), menu: { id: MenuId.ViewTitle, @@ -264,7 +264,7 @@ class OutputContribution extends Disposable implements IWorkbenchContribution { constructor() { super({ id: `workbench.action.openActiveLogOutputFile`, - title: { value: nls.localize('openActiveLogOutputFile', "Open Log Output File"), original: 'Open Log Output File' }, + title: nls.localize2('openActiveLogOutputFile', "Open Log Output File"), menu: [{ id: MenuId.ViewTitle, when: ContextKeyExpr.equals('view', OUTPUT_VIEW_ID), @@ -308,7 +308,7 @@ class OutputContribution extends Disposable implements IWorkbenchContribution { constructor() { super({ id: 'workbench.action.showLogs', - title: { value: nls.localize('showLogs', "Show Logs..."), original: 'Show Logs...' }, + title: nls.localize2('showLogs', "Show Logs..."), category: Categories.Developer, menu: { id: MenuId.CommandPalette, @@ -354,7 +354,7 @@ class OutputContribution extends Disposable implements IWorkbenchContribution { constructor() { super({ id: 'workbench.action.openLogFile', - title: { value: nls.localize('openLogFile', "Open Log File..."), original: 'Open Log File...' }, + title: nls.localize2('openLogFile', "Open Log File..."), category: Categories.Developer, menu: { id: MenuId.CommandPalette, diff --git a/code/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/code/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css index dfbb7fe26a1..e5dd093c6bd 100644 --- a/code/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/code/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -179,7 +179,6 @@ text-decoration: underline; } -.settings-editor.no-toc-search > .settings-body .settings-tree-container .monaco-list-row .monaco-tl-contents, .settings-editor.narrow-width > .settings-body .settings-tree-container .monaco-list-row .monaco-tl-contents { padding-left: 33px; } @@ -248,7 +247,6 @@ pointer-events: initial; } -.settings-editor.no-toc-search > .settings-body .settings-toc-container, .settings-editor.narrow-width > .settings-body .settings-toc-container { display: none; } @@ -308,7 +306,6 @@ margin: auto; } -.settings-editor.no-toc-search > .settings-body .settings-tree-container, .settings-editor.narrow-width > .settings-body .settings-tree-container { margin-left: 0px; } diff --git a/code/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/code/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 7f30da60612..68435481031 100644 --- a/code/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/code/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -10,7 +10,7 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Button } from 'vs/base/browser/ui/button/button'; import { ITreeElement } from 'vs/base/browser/ui/tree/tree'; import { Action } from 'vs/base/common/actions'; -import { Delayer, IntervalTimer, ThrottledDelayer, timeout } from 'vs/base/common/async'; +import { Delayer, IntervalTimer, ThrottledDelayer } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { fromNow } from 'vs/base/common/date'; import { isCancellationError } from 'vs/base/common/errors'; @@ -94,6 +94,7 @@ interface IFocusEventFromScroll extends KeyboardEvent { } const searchBoxLabel = localize('SearchSettings.AriaLabel', "Search settings"); +const SEARCH_TOC_BEHAVIOR_KEY = 'workbench.settings.settingsSearchTocBehavior'; const SETTINGS_EDITOR_STATE_KEY = 'settingsEditorState'; export class SettingsEditor2 extends EditorPane { @@ -359,7 +360,6 @@ export class SettingsEditor2 extends EditorPane { override async setInput(input: SettingsEditor2Input, options: ISettingsEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { this.inSettingsEditorContextKey.set(true); await super.setInput(input, options, context, token); - await timeout(0); // Force setInput to be async if (!this.input) { return; } @@ -1183,13 +1183,6 @@ export class SettingsEditor2 extends EditorPane { this.telemetryService.publicLog2('settingsEditor.settingModified', data); } - private onSearchModeToggled(): void { - this.rootElement.classList.remove('no-toc-search'); - if (this.configurationService.getValue('workbench.settings.settingsSearchTocBehavior') === 'hide') { - this.rootElement.classList.toggle('no-toc-search', !!this.searchResultModel); - } - } - private scheduleRefresh(element: HTMLElement, key = ''): void { if (key && this.scheduledRefreshes.has(key)) { return; @@ -1299,7 +1292,7 @@ export class SettingsEditor2 extends EditorPane { const additionalGroups: ISettingsGroup[] = []; const toggleData = await getExperimentalExtensionToggleData(this.workbenchAssignmentService, this.environmentService, this.productService); - if (toggleData && groups.filter(g => g.extensionInfo).length) { + if (this.extensionGalleryService.isEnabled() && toggleData && groups.filter(g => g.extensionInfo).length) { for (const key in toggleData.settingsEditorRecommendedExtensions) { const extensionId = key; // Recommend prerelease if not on Stable. @@ -1520,7 +1513,27 @@ export class SettingsEditor2 extends EditorPane { return match && match[1]; } - private triggerSearch(query: string): Promise { + /** + * Toggles the visibility of the Settings editor table of contents during a search + * depending on the behavior. + */ + private toggleTocBySearchBehaviorType() { + const tocBehavior = this.configurationService.getValue<'filter' | 'hide'>(SEARCH_TOC_BEHAVIOR_KEY); + const hideToc = tocBehavior === 'hide'; + if (hideToc) { + this.splitView.setViewVisible(0, false); + this.splitView.style({ + separatorBorder: Color.transparent + }); + } else { + this.splitView.setViewVisible(0, true); + this.splitView.style({ + separatorBorder: this.theme.getColor(settingsSashBorder)! + }); + } + } + + private async triggerSearch(query: string): Promise { this.viewState.tagFilters = new Set(); this.viewState.extensionFilters = new Set(); this.viewState.featureFilters = new Set(); @@ -1540,7 +1553,8 @@ export class SettingsEditor2 extends EditorPane { if (query && query !== '@') { query = this.parseSettingFromJSON(query) || query; - return this.triggerFilterPreferences(query); + await this.triggerFilterPreferences(query); + this.toggleTocBySearchBehaviorType(); } else { if (this.viewState.tagFilters.size || this.viewState.extensionFilters.size || this.viewState.featureFilters.size || this.viewState.idFilters.size || this.viewState.languageFilter) { this.searchResultModel = this.createFilterModel(); @@ -1559,7 +1573,6 @@ export class SettingsEditor2 extends EditorPane { this.tocTree.setFocus([]); this.viewState.filterToCategory = undefined; this.tocTreeModel.currentSearchModel = this.searchResultModel; - this.onSearchModeToggled(); if (this.searchResultModel) { // Added a filter model @@ -1568,16 +1581,17 @@ export class SettingsEditor2 extends EditorPane { this.refreshTOCTree(); this.renderResultCountMessages(); this.refreshTree(); + this.toggleTocBySearchBehaviorType(); } else if (!this.tocTreeDisposed) { // Leaving search mode this.tocTree.collapseAll(); this.refreshTOCTree(); this.renderResultCountMessages(); this.refreshTree(); + // Always show the ToC when leaving search mode + this.splitView.setViewVisible(0, true); } } - - return Promise.resolve(); } /** @@ -1598,7 +1612,6 @@ export class SettingsEditor2 extends EditorPane { } filterModel.setResult(0, fullResult); - return filterModel; } @@ -1693,7 +1706,6 @@ export class SettingsEditor2 extends EditorPane { // to make sure the search results count is set. this.searchResultModel.setResult(type, result); this.tocTreeModel.currentSearchModel = this.searchResultModel; - this.onSearchModeToggled(); } else { this.searchResultModel.setResult(type, result); this.tocTreeModel.update(); @@ -1791,18 +1803,22 @@ export class SettingsEditor2 extends EditorPane { // opens for the first time. this.splitView.layout(this.bodyContainer.clientWidth, listHeight); - const firstViewWasVisible = this.splitView.isViewVisible(0); - const firstViewVisible = this.bodyContainer.clientWidth >= SettingsEditor2.NARROW_TOTAL_WIDTH; - - this.splitView.setViewVisible(0, firstViewVisible); - // If the first view is again visible, and we have enough space, immediately set the - // editor to use the reset width rather than the cached min width - if (!firstViewWasVisible && firstViewVisible && this.bodyContainer.clientWidth >= SettingsEditor2.EDITOR_MIN_WIDTH + SettingsEditor2.TOC_RESET_WIDTH) { - this.splitView.resizeView(0, SettingsEditor2.TOC_RESET_WIDTH); + const tocBehavior = this.configurationService.getValue<'filter' | 'hide'>(SEARCH_TOC_BEHAVIOR_KEY); + const hideTocForSearch = tocBehavior === 'hide' && this.searchResultModel; + if (!hideTocForSearch) { + const firstViewWasVisible = this.splitView.isViewVisible(0); + const firstViewVisible = this.bodyContainer.clientWidth >= SettingsEditor2.NARROW_TOTAL_WIDTH; + + this.splitView.setViewVisible(0, firstViewVisible); + // If the first view is again visible, and we have enough space, immediately set the + // editor to use the reset width rather than the cached min width + if (!firstViewWasVisible && firstViewVisible && this.bodyContainer.clientWidth >= SettingsEditor2.EDITOR_MIN_WIDTH + SettingsEditor2.TOC_RESET_WIDTH) { + this.splitView.resizeView(0, SettingsEditor2.TOC_RESET_WIDTH); + } + this.splitView.style({ + separatorBorder: firstViewVisible ? this.theme.getColor(settingsSashBorder)! : Color.transparent + }); } - this.splitView.style({ - separatorBorder: firstViewVisible ? this.theme.getColor(settingsSashBorder)! : Color.transparent - }); } protected override saveState(): void { diff --git a/code/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts b/code/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index 2cc75e4d5b0..c2be5d08e38 100644 --- a/code/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts +++ b/code/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -911,7 +911,10 @@ export class SearchResultModel extends SettingsTreeModel { return compareTwoNullableNumbers(a.setting.internalOrder, b.setting.internalOrder); } }); - return filterMatches; + + // Remove duplicates, which sometimes occur with settings + // such as the experimental toggle setting. + return arrays.distinct(filterMatches, (match) => match.setting.key); } getUniqueResults(): ISearchResult | null { diff --git a/code/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts b/code/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts index c1c1e3fa5c2..22ff960b224 100644 --- a/code/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts +++ b/code/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts @@ -155,7 +155,7 @@ registry.registerConfiguration({ nls.localize('settingsSearchTocBehavior.hide', "Hide the Table of Contents while searching. The search results will not be grouped by category, and instead will be sorted by similarity to the query, with exact keyword matches coming first."), nls.localize('settingsSearchTocBehavior.filter', "Filter the Table of Contents to just categories that have matching settings. Clicking a category will filter the results to that category. The search results will be grouped by category."), ], - 'description': nls.localize('settingsSearchTocBehavior', "Controls the behavior of the settings editor Table of Contents while searching."), + 'description': nls.localize('settingsSearchTocBehavior', "Controls the behavior of the Settings editor Table of Contents while searching. If this setting is being changed in the Settings editor, the setting will take effect after the search query is modified."), 'default': 'filter', 'scope': ConfigurationScope.WINDOW }, diff --git a/code/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts b/code/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts index 66c38b0fb91..3f7e8e9147c 100644 --- a/code/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts +++ b/code/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts @@ -104,7 +104,7 @@ export class ViewQuickAccessProvider extends PickerQuickAccessProvider this.viewsService.openView(view.id, true) }); diff --git a/code/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts b/code/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts index cc6ee6169a0..1951afb77ba 100644 --- a/code/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts +++ b/code/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts @@ -134,7 +134,7 @@ export class SwitchRemoteViewItem extends Disposable { thisCapture.select(authority); } }); - this.completedRemotes.set(authority[0], { text, authority, virtualWorkspace: view.virtualWorkspace, dispose: () => action.dispose() }); + this.completedRemotes.set(authority[0], { text: text.value, authority, virtualWorkspace: view.virtualWorkspace, dispose: () => action.dispose() }); } } if (this.completedRemotes.size > startingCount) { diff --git a/code/src/vs/workbench/contrib/remote/browser/remote.ts b/code/src/vs/workbench/contrib/remote/browser/remote.ts index 5afcd3d3398..2eea0b9461a 100644 --- a/code/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/code/src/vs/workbench/contrib/remote/browser/remote.ts @@ -444,7 +444,7 @@ class IssueReporterItem extends HelpItemBase { class HelpPanel extends ViewPane { static readonly ID = '~remote.helpPanel'; - static readonly TITLE = nls.localize('remote.help', "Help and feedback"); + static readonly TITLE = nls.localize2('remote.help', "Help and feedback"); private tree!: WorkbenchAsyncDataTree; constructor( diff --git a/code/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/code/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 6b0b9db37aa..a7f32f80b22 100644 --- a/code/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/code/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -742,7 +742,7 @@ const PortChangableContextKey = new RawContextKey('portChangable', fals export class TunnelPanel extends ViewPane { static readonly ID = TUNNEL_VIEW_ID; - static readonly TITLE = nls.localize('remote.tunnel', "Ports"); + static readonly TITLE: ILocalizedString = nls.localize2('remote.tunnel', "Ports"); private panelContainer: HTMLElement | undefined; private table!: WorkbenchTable; @@ -1083,7 +1083,7 @@ export class TunnelPanel extends ViewPane { export class TunnelPanelDescriptor implements IViewDescriptor { readonly id = TunnelPanel.ID; - readonly name = TunnelPanel.TITLE; + readonly name: ILocalizedString = TunnelPanel.TITLE; readonly ctorDescriptor: SyncDescriptor; readonly canToggleVisibility = true; readonly hideByDefault = false; diff --git a/code/src/vs/workbench/contrib/scm/browser/activity.ts b/code/src/vs/workbench/contrib/scm/browser/activity.ts index b33088511af..b4edaa7f64e 100644 --- a/code/src/vs/workbench/contrib/scm/browser/activity.ts +++ b/code/src/vs/workbench/contrib/scm/browser/activity.ts @@ -197,7 +197,7 @@ export class SCMStatusController implements IWorkbenchContribution { if (count > 0) { const badge = new NumberBadge(count, num => localize('scmPendingChangesBadge', '{0} pending changes', num)); - this.badgeDisposable.value = this.activityService.showViewActivity(VIEW_PANE_ID, { badge, clazz: 'scm-viewlet-label' }); + this.badgeDisposable.value = this.activityService.showViewActivity(VIEW_PANE_ID, { badge }); } else { this.badgeDisposable.value = undefined; } diff --git a/code/src/vs/workbench/contrib/scm/browser/media/scm.css b/code/src/vs/workbench/contrib/scm/browser/media/scm.css index f92c54db113..6ccd5dbf96d 100644 --- a/code/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/code/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -199,12 +199,16 @@ flex-grow: 100; } +.scm-view .monaco-list .monaco-list-row .scm-input > .actions, .scm-view .monaco-list .monaco-list-row .resource-group > .actions, .scm-view .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions { display: none; max-width: fit-content; } +.scm-view .monaco-list .monaco-list-row:hover .scm-input > .actions, +.scm-view .monaco-list .monaco-list-row.selected .scm-input > .actions, +.scm-view .monaco-list .monaco-list-row.focused .scm-input > .actions, .scm-view .monaco-list .monaco-list-row:hover .resource-group > .actions, .scm-view .monaco-list .monaco-list-row.selected .resource-group > .actions, .scm-view .monaco-list .monaco-list-row.focused .resource-group > .actions, @@ -227,6 +231,7 @@ } .scm-view.show-actions .scm-provider > .actions, +.scm-view.show-actions > .monaco-list .monaco-list-row .scm-input > .actions, .scm-view.show-actions > .monaco-list .monaco-list-row .resource-group > .actions, .scm-view.show-actions > .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions { display: block; @@ -242,6 +247,18 @@ border-radius: 2px; } +.scm-view .scm-input .actions { + position: absolute; + top: 7px; + right: 20px; +} + +.scm-view .scm-input .actions .action-label { + border-radius: 5px; + outline: 1px dashed var(--vscode-toolbar-hoverOutline); + outline-offset: -1px; +} + .scm-view .scm-editor-container .monaco-editor { border-radius: 2px; } diff --git a/code/src/vs/workbench/contrib/scm/browser/menus.ts b/code/src/vs/workbench/contrib/scm/browser/menus.ts index 9918414d4c0..d1bde83d780 100644 --- a/code/src/vs/workbench/contrib/scm/browser/menus.ts +++ b/code/src/vs/workbench/contrib/scm/browser/menus.ts @@ -161,6 +161,16 @@ export class SCMRepositoryMenus implements ISCMRepositoryMenus, IDisposable { return this._repositoryMenu; } + private _inputBoxMenu: IMenu | undefined; + get inputBoxMenu(): IMenu { + if (!this._inputBoxMenu) { + this._inputBoxMenu = this.menuService.createMenu(MenuId.SCMInputBox, this.contextKeyService); + this.disposables.add(this._inputBoxMenu); + } + + return this._inputBoxMenu; + } + private readonly disposables = new DisposableStore(); constructor( diff --git a/code/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/code/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index cf9b19fb22a..377191ab525 100644 --- a/code/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/code/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { DirtyDiffWorkbenchController } from './dirtydiffDecorator'; @@ -76,7 +76,7 @@ viewsRegistry.registerViewWelcomeContent(VIEW_PANE_ID, { viewsRegistry.registerViews([{ id: VIEW_PANE_ID, - name: localize('source control', "Source Control"), + name: localize2('source control', "Source Control"), ctorDescriptor: new SyncDescriptor(SCMViewPane), canToggleVisibility: true, canMoveView: true, @@ -98,7 +98,7 @@ viewsRegistry.registerViews([{ viewsRegistry.registerViews([{ id: REPOSITORIES_VIEW_PANE_ID, - name: localize('source control repositories', "Source Control Repositories"), + name: localize2('source control repositories', "Source Control Repositories"), ctorDescriptor: new SyncDescriptor(SCMRepositoriesViewPane), canToggleVisibility: true, hideByDefault: true, @@ -112,7 +112,7 @@ viewsRegistry.registerViews([{ viewsRegistry.registerViews([{ id: SYNC_VIEW_PANE_ID, - name: localize('source control sync', "Source Control Sync"), + name: localize2('source control sync', "Source Control Sync"), ctorDescriptor: new SyncDescriptor(SCMSyncViewPane), canToggleVisibility: true, canMoveView: true, diff --git a/code/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/code/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 9e3fbce5719..46151c7881d 100644 --- a/code/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/code/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -227,6 +227,7 @@ class SCMTreeDragAndDrop implements ITreeDragAndDrop { interface InputTemplate { readonly inputWidget: SCMInputWidget; inputWidgetHeight: number; + actionBar: ActionBar; readonly elementDisposables: DisposableStore; readonly templateDisposable: IDisposable; } @@ -246,7 +247,9 @@ class InputRenderer implements ICompressibleTreeRenderer void, + private actionViewItemProvider: IActionViewItemProvider, @IInstantiationService private instantiationService: IInstantiationService, + @ISCMViewService private scmViewService: ISCMViewService ) { } renderTemplate(container: HTMLElement): InputTemplate { @@ -261,7 +264,10 @@ class InputRenderer implements ICompressibleTreeRenderer, index: number, templateData: InputTemplate): void { @@ -314,6 +320,13 @@ class InputRenderer implements ICompressibleTreeRenderer templateData.inputWidget.layout(); templateData.elementDisposables.add(this.outerLayout.onDidChange(layoutEditor)); layoutEditor(); + + // Action bar + templateData.actionBar.clear(); + templateData.actionBar.context = input.repository.provider; + + const menus = this.scmViewService.menus.getRepositoryMenus(input.repository.provider); + templateData.elementDisposables.add(connectPrimaryMenuToInlineActionBar(menus.inputBoxMenu, templateData.actionBar)); } renderCompressedElements(): void { @@ -2348,7 +2361,7 @@ export class SCMViewPane extends ViewPane { this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.providerCountBadge'), this.disposables)(updateProviderCountVisibility)); updateProviderCountVisibility(); - this.inputRenderer = this.instantiationService.createInstance(InputRenderer, this.layoutCache, overflowWidgetsDomNode, (input, height) => this.tree.updateElementHeight(input, height)); + this.inputRenderer = this.instantiationService.createInstance(InputRenderer, this.layoutCache, overflowWidgetsDomNode, (input, height) => this.tree.updateElementHeight(input, height), getActionViewItemProvider(this.instantiationService)); const delegate = new ListDelegate(this.inputRenderer); this.actionButtonRenderer = this.instantiationService.createInstance(ActionButtonRenderer); diff --git a/code/src/vs/workbench/contrib/scm/common/scm.ts b/code/src/vs/workbench/contrib/scm/common/scm.ts index e7d39bfecbb..af7852084c7 100644 --- a/code/src/vs/workbench/contrib/scm/common/scm.ts +++ b/code/src/vs/workbench/contrib/scm/common/scm.ts @@ -172,6 +172,7 @@ export interface ISCMTitleMenu { export interface ISCMRepositoryMenus { readonly titleMenu: ISCMTitleMenu; readonly repositoryMenu: IMenu; + readonly inputBoxMenu: IMenu; getResourceGroupMenu(group: ISCMResourceGroup): IMenu; getResourceMenu(resource: ISCMResource): IMenu; getResourceFolderMenu(group: ISCMResourceGroup): IMenu; diff --git a/code/src/vs/workbench/contrib/search/browser/search.contribution.ts b/code/src/vs/workbench/contrib/search/browser/search.contribution.ts index a2f41a98fac..a341863546a 100644 --- a/code/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/code/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -56,7 +56,7 @@ const SEARCH_MODE_CONFIG = 'search.mode'; const viewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, - title: { value: nls.localize('name', "Search"), original: 'Search' }, + title: nls.localize2('search', "Search"), ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }]), hideIfEmpty: true, icon: searchViewIcon, @@ -66,7 +66,7 @@ const viewContainer = Registry.as(ViewExtensions.ViewCo const viewDescriptor: IViewDescriptor = { id: VIEW_ID, containerIcon: searchViewIcon, - name: nls.localize('search', "Search"), + name: nls.localize2('search', "Search"), ctorDescriptor: new SyncDescriptor(SearchView), canToggleVisibility: false, canMoveView: true, @@ -93,7 +93,7 @@ class RegisterSearchViewContribution implements IWorkbenchContribution { ) { const data = configurationService.inspect('search.location'); if (data.value === 'panel') { - viewDescriptorService.moveViewToLocation(viewDescriptor, ViewContainerLocation.Panel); + viewDescriptorService.moveViewToLocation(viewDescriptor, ViewContainerLocation.Panel, 'search.location'); } Registry.as(Extensions.ConfigurationMigration) .registerConfigurationMigrations([{ key: 'search.location', migrateFn: (value: any) => ({ value: undefined }) }]); @@ -202,7 +202,7 @@ configurationRegistry.registerConfiguration({ }, 'search.useGlobalIgnoreFiles': { type: 'boolean', - markdownDescription: nls.localize('useGlobalIgnoreFiles', "Controls whether to use your global gitignore file (e.g., from `$HOME/.config/git/ignore`) when searching for files. Requires `#search.useIgnoreFiles#` to be enabled."), + markdownDescription: nls.localize('useGlobalIgnoreFiles', "Controls whether to use your global gitignore file (for example, from `$HOME/.config/git/ignore`) when searching for files. Requires `#search.useIgnoreFiles#` to be enabled."), default: false, scope: ConfigurationScope.RESOURCE }, diff --git a/code/src/vs/workbench/contrib/speech/common/speech.contribution.ts b/code/src/vs/workbench/contrib/speech/common/speech.contribution.ts new file mode 100644 index 00000000000..6a093cd32e7 --- /dev/null +++ b/code/src/vs/workbench/contrib/speech/common/speech.contribution.ts @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ISpeechService, SpeechService } from 'vs/workbench/contrib/speech/common/speechService'; + +registerSingleton(ISpeechService, SpeechService, InstantiationType.Delayed); diff --git a/code/src/vs/workbench/contrib/speech/common/speechService.ts b/code/src/vs/workbench/contrib/speech/common/speechService.ts new file mode 100644 index 00000000000..c071ce0746d --- /dev/null +++ b/code/src/vs/workbench/contrib/speech/common/speechService.ts @@ -0,0 +1,94 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { firstOrDefault } from 'vs/base/common/arrays'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Emitter, Event } from 'vs/base/common/event'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService } from 'vs/platform/log/common/log'; + +export const ISpeechService = createDecorator('speechService'); + +export interface ISpeechProviderMetadata { + readonly extension: ExtensionIdentifier; + readonly displayName: string; +} + +export enum SpeechToTextStatus { + Started = 1, + Recognizing = 2, + Recognized = 3, + Stopped = 4 +} + +export interface ISpeechToTextEvent { + readonly status: SpeechToTextStatus; + readonly text?: string; +} + +export interface ISpeechProvider { + readonly metadata: ISpeechProviderMetadata; + + createSpeechToTextSession(token: CancellationToken): ISpeechToTextSession; +} + +export interface ISpeechToTextSession extends IDisposable { + readonly onDidChange: Event; +} + +export interface ISpeechService { + + readonly _serviceBrand: undefined; + + readonly onDidRegisterSpeechProvider: Event; + readonly onDidUnregisterSpeechProvider: Event; + + registerSpeechProvider(identifier: string, provider: ISpeechProvider): IDisposable; + + createSpeechToTextSession(token: CancellationToken): ISpeechToTextSession; +} + +export class SpeechService implements ISpeechService { + + readonly _serviceBrand: undefined; + + private readonly _onDidRegisterSpeechProvider = new Emitter(); + readonly onDidRegisterSpeechProvider = this._onDidRegisterSpeechProvider.event; + + private readonly _onDidUnregisterSpeechProvider = new Emitter(); + readonly onDidUnregisterSpeechProvider = this._onDidUnregisterSpeechProvider.event; + + private readonly providers = new Map(); + + constructor(@ILogService private readonly logService: ILogService) { } + + registerSpeechProvider(identifier: string, provider: ISpeechProvider): IDisposable { + if (this.providers.has(identifier)) { + throw new Error(`Speech provider with identifier ${identifier} is already registered.`); + } + + this.providers.set(identifier, provider); + + this._onDidRegisterSpeechProvider.fire(provider); + + return toDisposable(() => { + this.providers.delete(identifier); + this._onDidUnregisterSpeechProvider.fire(provider); + }); + } + + createSpeechToTextSession(token: CancellationToken): ISpeechToTextSession { + const provider = firstOrDefault(Array.from(this.providers.values())); + if (!provider) { + throw new Error(`No Speech provider is registered.`); + } else if (this.providers.size > 1) { + this.logService.warn(`Multiple speech providers registered. Picking first one: ${provider.metadata.displayName}`); + } + + return provider.createSpeechToTextSession(token); + } +} diff --git a/code/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts b/code/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts index 91e03015dc5..8274fb572f6 100644 --- a/code/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts +++ b/code/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts @@ -293,21 +293,24 @@ const GoModulesToLookFor = [ 'github.com/Azure/azure-sdk-for-go/sdk/storage/azblob', 'github.com/Azure/azure-sdk-for-go/sdk/storage/azfile', 'github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue', + 'github.com/Azure/azure-sdk-for-go/sdk/storage/azdatalake', 'github.com/Azure/azure-sdk-for-go/sdk/tracing/azotel', 'github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azadmin', 'github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates', 'github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys', 'github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets', 'github.com/Azure/azure-sdk-for-go/sdk/monitor/azquery', + 'github.com/Azure/azure-sdk-for-go/sdk/monitor/azingest', 'github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs', 'github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus', 'github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig', 'github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos', 'github.com/Azure/azure-sdk-for-go/sdk/data/aztables', 'github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry', - 'github.com/Azure/azure-sdk-for-go/sdk/cognitiveservices/azopenai', + 'github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai', 'github.com/Azure/azure-sdk-for-go/sdk/azidentity', - 'github.com/Azure/azure-sdk-for-go/sdk/azcore' + 'github.com/Azure/azure-sdk-for-go/sdk/azcore', + 'github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/' ]; @@ -681,21 +684,246 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/storage/azfile" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/storage/azdatalake" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/tracing/azotel" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azadmin" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/monitor/azquery" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/monitor/azingest" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/data/aztables" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/cognitiveservices/azopenai" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/ai/azopenai" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/azidentity" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/azcore" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/azcore" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/iotfirmwaredefense/armiotfirmwaredefense" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/aad/armaad" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/addons/armaddons" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/advisor/armadvisor" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/agrifood/armagrifood" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/alertsmanagement/armalertsmanagement" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/analysisservices/armanalysisservices" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/apimanagement/armapimanagement" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appcomplianceautomation/armappcomplianceautomation" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appconfiguration/armappconfiguration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appplatform/armappplatform" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appservice/armappservice" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/applicationinsights/armapplicationinsights" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/azurearcdata/armazurearcdata" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/attestation/armattestation" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/automanage/armautomanage" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/automation/armautomation" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/azuredata/armazuredata" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/azurestackhci/armazurestackhci" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/avs/armavs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/recoveryservices/armrecoveryservicesbackup" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/baremetalinfrastructure/armbaremetalinfrastructure" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/batch/armbatch" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/billing/armbilling" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/billingbenefits/armbillingbenefits" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/blockchain/armblockchain" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/blueprint/armblueprint" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/botservice/armbotservice" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/changeanalysis/armchangeanalysis" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armchanges" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/chaos/armchaos" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/search/armsearch" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cognitiveservices/armcognitiveservices" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/commerce/armcommerce" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/communication/armcommunication" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/confidentialledger/armconfidentialledger" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/confluent/armconfluent" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/connectedvmware/armconnectedvmware" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/consumption/armconsumption" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/appcontainers/armappcontainers" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerinstance/armcontainerinstance" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerregistry/armcontainerregistry" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservicefleet/armcontainerservicefleet" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cdn/armcdn" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cosmos/armcosmos" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/cosmosforpostgresql/armcosmosforpostgresql" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/costmanagement/armcostmanagement" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/customproviders/armcustomproviders" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/customerinsights/armcustomerinsights" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/customerlockbox/armcustomerlockbox" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/databox/armdatabox" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/databoxedge/armdataboxedge" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/datacatalog/armdatacatalog" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/datafactory/armdatafactory" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/datalake-analytics/armdatalakeanalytics" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/datalake-store/armdatalakestore" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/datamigration/armdatamigration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dataprotection/armdataprotection" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/datashare/armdatashare" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/databricks/armdatabricks" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/datadog/armdatadog" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/delegatednetwork/armdelegatednetwork" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/deploymentmanager/armdeploymentmanager" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armdeploymentscripts" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/desktopvirtualization/armdesktopvirtualization" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/devcenter/armdevcenter" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/devhub/armdevhub" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/deviceprovisioningservices/armdeviceprovisioningservices" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/deviceupdate/armdeviceupdate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/devops/armdevops" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/devtestlabs/armdevtestlabs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/digitaltwins/armdigitaltwins" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dnsresolver/armdnsresolver" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/domainservices/armdomainservices" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dynatrace/armdynatrace" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/edgeorder/armedgeorder" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/edgeorderpartner/armedgeorderpartner" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/education/armeducation" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/elastic/armelastic" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/elasticsan/armelasticsan" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/elasticsans/armelasticsans" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/engagementfabric/armengagementfabric" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/eventgrid/armeventgrid" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/eventhub/armeventhub" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/extendedlocation/armextendedlocation" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armfeatures" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/fluidrelay/armfluidrelay" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/frontdoor/armfrontdoor" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/graphservices/armgraphservices" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/guestconfiguration/armguestconfiguration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/hanaonazure/armhanaonazure" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/hardwaresecuritymodules/armhardwaresecuritymodules" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/hdinsight/armhdinsight" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/healthbot/armhealthbot" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/healthcareapis/armhealthcareapis" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/hybridcompute/armhybridcompute" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/hybridconnectivity/armhybridconnectivity" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/hybridcontainerservice/armhybridcontainerservice" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/hybriddatamanager/armhybriddatamanager" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/hybridkubernetes/armhybridkubernetes" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/hybridnetwork/armhybridnetwork" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/iotcentral/armiotcentral" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/iothub/armiothub" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/iotsecurity/armiotsecurity" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/kubernetesconfiguration/armkubernetesconfiguration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/kusto/armkusto" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/labservices/armlabservices" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armlinks" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/loadtesting/armloadtesting" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armlocks" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/operationalinsights/armoperationalinsights" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/logic/armlogic" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/logz/armlogz" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/m365securityandcompliance/armm365securityandcompliance" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/machinelearning/armmachinelearning" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/machinelearningservices/armmachinelearningservices" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/maintenance/armmaintenance" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armmanagedapplications" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/solutions/armmanagedapplications" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dashboard/armdashboard" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/managednetwork/armmanagednetwork" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/managednetworkfabric/armmanagednetworkfabric" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/msi/armmsi" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/managedservices/armmanagedservices" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/managementgroups/armmanagementgroups" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/managementpartner/armmanagementpartner" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/maps/armmaps" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mariadb/armmariadb" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/marketplace/armmarketplace" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/marketplaceordering/armmarketplaceordering" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mediaservices/armmediaservices" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/migrate/armmigrate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mixedreality/armmixedreality" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mobilenetwork/armmobilenetwork" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/monitor/armmonitor" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysql" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/mysql/armmysqlflexibleservers" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/netapp/armnetapp" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/networkcloud/armnetworkcloud" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/networkfunction/armnetworkfunction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/newrelic/armnewrelicobservability" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/nginx/armnginx" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/notificationhubs/armnotificationhubs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/oep/armoep" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/operationsmanagement/armoperationsmanagement" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/orbital/armorbital" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/paloaltonetworksngfw/armpanngfw" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/peering/armpeering" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armpolicy" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/policyinsights/armpolicyinsights" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/portal/armportal" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresql" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresql/armpostgresqlflexibleservers" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/postgresqlhsc/armpostgresqlhsc" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/powerbiprivatelinks/armpowerbiprivatelinks" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/powerbidedicated/armpowerbidedicated" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/powerbiembedded/armpowerbiembedded" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/powerplatform/armpowerplatform" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/providerhub/armproviderhub" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/purview/armpurview" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/quantum/armquantum" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/liftrqumulo/armqumulo" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/quota/armquota" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/recoveryservices/armrecoveryservices" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redhatopenshift/armredhatopenshift" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redis/armredis" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/redisenterprise/armredisenterprise" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/relay/armrelay" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/reservations/armreservations" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourceconnector/armresourceconnector" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcehealth/armresourcehealth" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcemover/armresourcemover" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/saas/armsaas" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/scheduler/armscheduler" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/scvmm/armscvmm" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/security/armsecurity" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/securitydevops/armsecuritydevops" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/securityinsight/armsecurityinsight" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/securityinsights/armsecurityinsights" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/selfhelp/armselfhelp" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/serialconsole/armserialconsole" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/servicebus/armservicebus" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/servicefabric/armservicefabric" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/servicefabricmesh/armservicefabricmesh" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/servicelinker/armservicelinker" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/servicenetworking/armservicenetworking" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/signalr/armsignalr" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/recoveryservices/armrecoveryservicessiterecovery" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/sphere/armsphere" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/sql/armsql" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/sqlvirtualmachine/armsqlvirtualmachine" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storagecache/armstoragecache" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storageimportexport/armstorageimportexport" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storagemover/armstoragemover" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storagepool/armstoragepool" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storagesync/armstoragesync" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storsimple1200series/armstorsimple1200series" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storsimple8000series/armstorsimple8000series" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/streamanalytics/armstreamanalytics" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armsubscriptions" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/subscription/armsubscription" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/support/armsupport" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/synapse/armsynapse" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armtemplatespecs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/testbase/armtestbase" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/timeseriesinsights/armtimeseriesinsights" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/trafficmanager/armtrafficmanager" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/web/armweb" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/webpubsub/armwebpubsub" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/windowsesu/armwindowsesu" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/windowsiot/armwindowsiot" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/workloadmonitor/armworkloadmonitor" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/workloads/armworkloads" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } } */ private async resolveWorkspaceTags(): Promise { @@ -918,8 +1146,10 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { } if (firstRequireBlockFound && line !== '') { const packageName: string = line.split(' ')[0].trim(); - if (GoModulesToLookFor.indexOf(packageName) > -1) { - tags['workspace.go.mod.' + packageName] = true; + for (const module of GoModulesToLookFor) { + if (packageName.startsWith(module)) { + tags['workspace.go.mod.' + packageName] = true; + } } } } diff --git a/code/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/code/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 263eee32445..dd2d581c170 100644 --- a/code/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/code/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -1045,7 +1045,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { this._viewsService.openView(Markers.MARKERS_VIEW_ID); } else if (task.command.presentation && (task.command.presentation.focus || task.command.presentation.reveal === RevealKind.Always)) { this._terminalService.setActiveInstance(terminal); - this._terminalGroupService.showPanel(task.command.presentation.focus); + await this._terminalService.revealActiveTerminal(); } this._activeTasks[task.getMapKey()].terminal = terminal; this._fireTaskEvent(TaskEvent.changed()); diff --git a/code/src/vs/workbench/contrib/terminal/browser/media/fish_xdg_data/fish/vendor_conf.d/shellIntegration.fish b/code/src/vs/workbench/contrib/terminal/browser/media/fish_xdg_data/fish/vendor_conf.d/shellIntegration.fish index d395be291de..dea46e191da 100644 --- a/code/src/vs/workbench/contrib/terminal/browser/media/fish_xdg_data/fish/vendor_conf.d/shellIntegration.fish +++ b/code/src/vs/workbench/contrib/terminal/browser/media/fish_xdg_data/fish/vendor_conf.d/shellIntegration.fish @@ -28,30 +28,37 @@ if status --is-login; and set -q VSCODE_PATH_PREFIX end set -e VSCODE_PATH_PREFIX -# Apply EnvironmentVariableCollections if needed -if test -n "$VSCODE_ENV_REPLACE" - set ITEMS (string split : $VSCODE_ENV_REPLACE) - for B in $ITEMS - set split (string split = $B) - set -gx "$split[1]" (echo -e "$split[2]") +set -g __vsc_applied_env_vars 0 +function __vsc_apply_env_vars + if test $__vsc_applied_env_vars -eq 1; + return end - set -e VSCODE_ENV_REPLACE -end -if test -n "$VSCODE_ENV_PREPEND" - set ITEMS (string split : $VSCODE_ENV_PREPEND) - for B in $ITEMS - set split (string split = $B) - set -gx "$split[1]" (echo -e "$split[2]")"$$split[1]" # avoid -p as it adds a space + set -l __vsc_applied_env_vars 1 + # Apply EnvironmentVariableCollections if needed + if test -n "$VSCODE_ENV_REPLACE" + set ITEMS (string split : $VSCODE_ENV_REPLACE) + for B in $ITEMS + set split (string split = $B) + set -gx "$split[1]" (echo -e "$split[2]") + end + set -e VSCODE_ENV_REPLACE end - set -e VSCODE_ENV_PREPEND -end -if test -n "$VSCODE_ENV_APPEND" - set ITEMS (string split : $VSCODE_ENV_APPEND) - for B in $ITEMS - set split (string split = $B) - set -gx "$split[1]" "$$split[1]"(echo -e "$split[2]") # avoid -a as it adds a space + if test -n "$VSCODE_ENV_PREPEND" + set ITEMS (string split : $VSCODE_ENV_PREPEND) + for B in $ITEMS + set split (string split = $B) + set -gx "$split[1]" (echo -e "$split[2]")"$$split[1]" # avoid -p as it adds a space + end + set -e VSCODE_ENV_PREPEND + end + if test -n "$VSCODE_ENV_APPEND" + set ITEMS (string split : $VSCODE_ENV_APPEND) + for B in $ITEMS + set split (string split = $B) + set -gx "$split[1]" "$$split[1]"(echo -e "$split[2]") # avoid -a as it adds a space + end + set -e VSCODE_ENV_APPEND end - set -e VSCODE_ENV_APPEND end # Handle the shell integration nonce @@ -140,6 +147,9 @@ end # Sent at the start of the prompt. # Marks the beginning of the prompt (and, implicitly, a new line). function __vsc_fish_prompt_start + # Applying environment variables is deferred to after config.fish has been + # evaluated + __vsc_apply_env_vars __vsc_esc A end diff --git a/code/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/code/src/vs/workbench/contrib/terminal/browser/media/terminal.css index a11d5cc80b7..c222df0deb8 100644 --- a/code/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/code/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -552,34 +552,6 @@ background-color: var(--vscode-scrollbarSlider-activeBackground); } -.monaco-workbench .terminal-accessible-widget { - position: absolute; - left: 10px; - top: 0; - bottom: 0; - right: 0; - opacity: 0; - /* Reset cursor style as monaco controls it here */ - cursor: default; - padding: 0; - overflow: initial; - overflow-x: initial; - pointer-events: none; - z-index: 0; -} - -.monaco-workbench .terminal-accessible-widget div { - white-space: pre-wrap; -} - -.monaco-workbench .terminal-accessible-widget.focus-within, -.monaco-workbench .terminal-accessible-widget.active { - pointer-events: all; - opacity: 1; - z-index: 33; - background-color: var(--vscode-terminal-background, var(--vscode-panel-background)); -} - .monaco-workbench .xterm.terminal.hide { visibility: hidden; } diff --git a/code/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/code/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 5e386e3b26c..ffa5b712d25 100644 --- a/code/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/code/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -124,7 +124,7 @@ Registry.as(DragAndDropExtensions.DragAndDropC // Register views const VIEW_CONTAINER = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: TERMINAL_VIEW_ID, - title: { value: nls.localize('terminal', "Terminal"), original: 'Terminal' }, + title: nls.localize2('terminal', "Terminal"), icon: terminalViewIcon, ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [TERMINAL_VIEW_ID, { mergeViewWithContainerWhenSingleView: true }]), storageId: TERMINAL_VIEW_ID, @@ -133,7 +133,7 @@ const VIEW_CONTAINER = Registry.as(ViewContainerExtensi }, ViewContainerLocation.Panel, { doNotRegisterOpenCommand: true, isDefault: true }); Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([{ id: TERMINAL_VIEW_ID, - name: nls.localize('terminal', "Terminal"), + name: nls.localize2('terminal', "Terminal"), containerIcon: terminalViewIcon, canToggleVisibility: false, canMoveView: true, diff --git a/code/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/code/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 06284075661..39ed8377236 100644 --- a/code/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/code/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -253,7 +253,7 @@ export function registerTerminalActions() { run: async (c, _, args) => { const options = (isObject(args) && 'location' in args) ? args as ICreateTerminalOptions : { location: TerminalLocation.Editor }; const instance = await c.service.createTerminal(options); - instance.focusWhenReady(); + await instance.focusWhenReady(); } }); @@ -268,7 +268,7 @@ export function registerTerminalActions() { const instance = await c.service.createTerminal({ location: { viewColumn: editorGroupsService.activeGroup.index } }); - instance.focusWhenReady(); + await instance.focusWhenReady(); } }); @@ -279,7 +279,7 @@ export function registerTerminalActions() { const instance = await c.service.createTerminal({ location: { viewColumn: SIDE_GROUP } }); - instance.focusWhenReady(); + await instance.focusWhenReady(); } }); diff --git a/code/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts b/code/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts index 23719c3bb76..0318448425a 100644 --- a/code/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts +++ b/code/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts @@ -85,7 +85,9 @@ export class TerminalEditor extends EditorPane { override clearInput(): void { super.clearInput(); - this._editorInput?.terminalInstance?.detachFromElement(); + if (this._overflowGuardElement && this._editorInput?.terminalInstance?.domElement.parentElement === this._overflowGuardElement) { + this._editorInput.terminalInstance?.detachFromElement(); + } this._editorInput = undefined; } @@ -188,7 +190,11 @@ export class TerminalEditor extends EditorPane { } layout(dimension: dom.Dimension): void { - this._editorInput?.terminalInstance?.layout(dimension); + const instance = this._editorInput?.terminalInstance; + if (instance) { + instance.attachToElement(this._overflowGuardElement!); + instance.layout(dimension); + } this._lastDimension = dimension; } diff --git a/code/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/code/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 467a5190c30..a7c08bc63d8 100644 --- a/code/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/code/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -355,7 +355,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { @IOpenerService private readonly _openerService: IOpenerService, @ICommandService private readonly _commandService: ICommandService, @IAudioCueService private readonly _audioCueService: IAudioCueService, - @IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService, + @IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService ) { super(); diff --git a/code/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/code/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 81cdc6320db..3b3d418f5d4 100644 --- a/code/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/code/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -43,7 +43,7 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { debounce } from 'vs/base/common/decorators'; import { MouseWheelClassifier } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { IMouseWheelEvent, StandardWheelEvent } from 'vs/base/browser/mouseEvent'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; const enum RenderConstants { /** @@ -204,7 +204,7 @@ export class XtermTerminal extends Disposable implements IXtermTerminal, IDetach @ITelemetryService private readonly _telemetryService: ITelemetryService, @IClipboardService private readonly _clipboardService: IClipboardService, @IContextKeyService contextKeyService: IContextKeyService, - @IAccessibilityService private readonly _accessibilityService: IAccessibilityService + @IAccessibleNotificationService private readonly _accessibleNotificationService: IAccessibleNotificationService ) { super(); const font = this._configHelper.getFont(undefined, true); @@ -590,7 +590,7 @@ export class XtermTerminal extends Disposable implements IXtermTerminal, IDetach // the prompt being written this._capabilities.get(TerminalCapability.CommandDetection)?.handlePromptStart(); this._capabilities.get(TerminalCapability.CommandDetection)?.handleCommandStart(); - this._accessibilityService.alertCleared(); + this._accessibleNotificationService.notify(AccessibleNotificationEvent.Clear); } hasSelection(): boolean { diff --git a/code/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts b/code/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts index ba012fd2ee0..649ef681da3 100644 --- a/code/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts +++ b/code/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts @@ -37,6 +37,7 @@ class TextAreaSyncContribution extends DisposableStore implements ITerminalContr static get(instance: ITerminalInstance): TextAreaSyncContribution | null { return instance.getContribution(TextAreaSyncContribution.ID); } + private _addon: TextAreaSyncAddon | undefined; constructor( private readonly _instance: ITerminalInstance, processManager: ITerminalProcessManager, @@ -45,10 +46,13 @@ class TextAreaSyncContribution extends DisposableStore implements ITerminalContr ) { super(); } - xtermReady(xterm: IXtermTerminal & { raw: Terminal }): void { - const addon = this._instantiationService.createInstance(TextAreaSyncAddon, this._instance.capabilities); - xterm.raw.loadAddon(addon); - addon.activate(xterm.raw); + layout(xterm: IXtermTerminal & { raw: Terminal }): void { + if (this._addon) { + return; + } + this._addon = this.add(this._instantiationService.createInstance(TextAreaSyncAddon, this._instance.capabilities)); + xterm.raw.loadAddon(this._addon); + this._addon.activate(xterm.raw); } } registerTerminalContribution(TextAreaSyncContribution.ID, TextAreaSyncContribution); @@ -69,7 +73,7 @@ export class TerminalAccessibleViewContribution extends Disposable implements IT @IAccessibleViewService private readonly _accessibleViewService: IAccessibleViewService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @ITerminalService private readonly _terminalService: ITerminalService, - @IConfigurationService configurationService: IConfigurationService, + @IConfigurationService private readonly _configurationService: IConfigurationService, @IContextKeyService private readonly _contextKeyService: IContextKeyService) { super(); this._register(AccessibleViewAction.addImplementation(90, 'terminal', () => { @@ -80,7 +84,7 @@ export class TerminalAccessibleViewContribution extends Disposable implements IT return true; }, TerminalContextKeys.focus)); this._register(_instance.onDidRunText(() => { - const focusAfterRun = configurationService.getValue(TerminalSettingId.FocusAfterRun); + const focusAfterRun = _configurationService.getValue(TerminalSettingId.FocusAfterRun); if (focusAfterRun === 'terminal') { _instance.focus(true); } else if (focusAfterRun === 'accessible-buffer') { @@ -123,10 +127,11 @@ export class TerminalAccessibleViewContribution extends Disposable implements IT return this._register(this._instantiationService.createInstance(TerminalAccessibilityHelpProvider, this._instance, this._xterm!)).provideContent(); })); } - this._accessibleViewService.show(this._bufferProvider); + const position = this._configurationService.getValue(TerminalSettingId.AccessibleViewPreserveCursorPosition) ? this._accessibleViewService.getPosition(AccessibleViewProviderId.Terminal) : undefined; + this._accessibleViewService.show(this._bufferProvider, position); } navigateToCommand(type: NavigationType): void { - const currentLine = this._accessibleViewService.getPosition()?.lineNumber || this._accessibleViewService.getLastPosition()?.lineNumber; + const currentLine = this._accessibleViewService.getPosition(AccessibleViewProviderId.Terminal)?.lineNumber; const commands = this._getCommandsWithEditorLine(); if (!commands?.length || !currentLine) { return; diff --git a/code/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts b/code/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts index 07597055c0c..7815d8776e8 100644 --- a/code/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts +++ b/code/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts @@ -76,6 +76,7 @@ export class TerminalAccessibilityHelpProvider extends Disposable implements IAc provideContent(): string { const content = []; content.push(this._descriptionForCommand(TerminalCommandId.FocusAccessibleBuffer, localize('focusAccessibleBuffer', 'The Focus Accessible Buffer ({0}) command enables screen readers to read terminal contents.'), localize('focusAccessibleBufferNoKb', 'The Focus Accessible Buffer command enables screen readers to read terminal contents and is currently not triggerable by a keybinding.'))); + content.push(localize('preserveCursor', 'Customize the behavior of the cursor when toggling between the terminal and accessible view with `terminal.integrated.accessibleViewPreserveCursorPosition.`')); if (this._instance.shellType === WindowsShellType.CommandPrompt) { content.push(localize('commandPromptMigration', "Consider using powershell instead of command prompt for an improved experience")); } diff --git a/code/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts b/code/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts index 87c566e9620..057c0ab530f 100644 --- a/code/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts +++ b/code/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts @@ -10,6 +10,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { TerminalCapability, ITerminalCommand } from 'vs/platform/terminal/common/capabilities/capabilities'; import { ICurrentPartialCommand } from 'vs/platform/terminal/common/capabilities/commandDetectionCapability'; +import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { AccessibleViewType, IAccessibleContentProvider, IAccessibleViewOptions, IAccessibleViewSymbol } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { ITerminalInstance, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; @@ -17,10 +18,11 @@ import { BufferContentTracker } from 'vs/workbench/contrib/terminalContrib/acces export class TerminalAccessibleBufferProvider extends DisposableStore implements IAccessibleContentProvider { id = AccessibleViewProviderId.Terminal; - options: IAccessibleViewOptions = { type: AccessibleViewType.View, language: 'terminal', positionBottom: true, id: AccessibleViewProviderId.Terminal }; + options: IAccessibleViewOptions = { type: AccessibleViewType.View, language: 'terminal', id: AccessibleViewProviderId.Terminal }; verbositySettingKey = AccessibilityVerbositySettingId.Terminal; private readonly _onDidRequestClearProvider = new Emitter(); readonly onDidRequestClearLastProvider = this._onDidRequestClearProvider.event; + private _focusedInstance: ITerminalInstance | undefined; constructor( private readonly _instance: Pick, private _bufferTracker: BufferContentTracker, @@ -32,12 +34,22 @@ export class TerminalAccessibleBufferProvider extends DisposableStore implements ) { super(); this.options.customHelp = customHelp; - this.add(this._instance.onDidRequestFocus(() => this._onDidRequestClearProvider.fire(AccessibleViewProviderId.Terminal))); + this.options.position = _configurationService.getValue(TerminalSettingId.AccessibleViewPreserveCursorPosition) ? 'initial-bottom' : 'bottom'; this.add(this._instance.onDisposed(() => this._onDidRequestClearProvider.fire(AccessibleViewProviderId.Terminal))); + this.add(_configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(TerminalSettingId.AccessibleViewPreserveCursorPosition)) { + this.options.position = _configurationService.getValue(TerminalSettingId.AccessibleViewPreserveCursorPosition) ? 'initial-bottom' : 'bottom'; + } + })); + this._focusedInstance = _terminalService.activeInstance; + this.add(_terminalService.onDidChangeActiveInstance(() => { + if (_terminalService.activeInstance && this._focusedInstance?.instanceId !== _terminalService.activeInstance?.instanceId) { + this._onDidRequestClearProvider.fire(AccessibleViewProviderId.Terminal); + this._focusedInstance = _terminalService.activeInstance; + } + })); } - - onClose() { this._instance.focus(); } diff --git a/code/src/vs/workbench/contrib/terminalContrib/accessibility/browser/textAreaSyncAddon.ts b/code/src/vs/workbench/contrib/terminalContrib/accessibility/browser/textAreaSyncAddon.ts index 2f9cda97bee..fb5b757d2b8 100644 --- a/code/src/vs/workbench/contrib/terminalContrib/accessibility/browser/textAreaSyncAddon.ts +++ b/code/src/vs/workbench/contrib/terminalContrib/accessibility/browser/textAreaSyncAddon.ts @@ -103,7 +103,8 @@ export class TextAreaSyncAddon extends Disposable implements ITerminalAddon { } if (currentCommand.commandStartX !== undefined) { this._currentCommand = commandLine.substring(currentCommand.commandStartX); - this._cursorX = buffer.cursorX - currentCommand.commandStartX; + const cursorPosition = buffer.cursorX - currentCommand.commandStartX; + this._cursorX = cursorPosition >= 0 ? cursorPosition : 0; } else { this._currentCommand = undefined; this._cursorX = undefined; diff --git a/code/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts b/code/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts index 4a4ba9498e1..cfe91ea6d7d 100644 --- a/code/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts +++ b/code/src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts @@ -7,8 +7,8 @@ import * as assert from 'assert'; import { importAMDNodeModule } from 'vs/amdX'; import { isWindows } from 'vs/base/common/platform'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { TestAccessibilityService } from 'vs/platform/accessibility/test/common/testAccessibilityService'; +import { TestAccessibleNotificationService } from 'vs/platform/accessibility/browser/accessibleNotificationService'; +import { IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -67,7 +67,7 @@ suite('Buffer Content Tracker', () => { instantiationService.stub(IContextMenuService, store.add(instantiationService.createInstance(ContextMenuService))); instantiationService.stub(ILifecycleService, store.add(new TestLifecycleService())); instantiationService.stub(IContextKeyService, store.add(new MockContextKeyService())); - instantiationService.stub(IAccessibilityService, new TestAccessibilityService()); + instantiationService.stub(IAccessibleNotificationService, store.add(new TestAccessibleNotificationService())); configHelper = store.add(instantiationService.createInstance(TerminalConfigHelper)); capabilities = store.add(new TerminalCapabilityStore()); if (!isWindows) { diff --git a/code/src/vs/workbench/contrib/terminalContrib/quickFix/browser/media/terminalQuickFix.css b/code/src/vs/workbench/contrib/terminalContrib/quickFix/browser/media/terminalQuickFix.css index 11a54028fe5..c907928d5fb 100644 --- a/code/src/vs/workbench/contrib/terminalContrib/quickFix/browser/media/terminalQuickFix.css +++ b/code/src/vs/workbench/contrib/terminalContrib/quickFix/browser/media/terminalQuickFix.css @@ -13,7 +13,5 @@ } .monaco-workbench .terminal .terminal-command-decoration.quick-fix.explainOnly { - /* Use success background to blend in with the terminal better as it's lower priority. We will - * probably want to add an explicit color for this eventually. */ - color: var(--vscode-terminalCommandDecoration-successBackground) !important; + color: var(--vscode-editorLightBulbAutoFix-foreground) !important; } diff --git a/code/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts b/code/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts index 697f6db5009..d86a971c1ee 100644 --- a/code/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts +++ b/code/src/vs/workbench/contrib/terminalContrib/quickFix/browser/quickFixAddon.ts @@ -186,6 +186,15 @@ export class TerminalQuickFixAddon extends Disposable implements ITerminalAddon, if (command.command !== '' && this._lastQuickFixId) { this._disposeQuickFix(this._lastQuickFixId, false); } + + + // Wait for the next command to start to ensure the quick fix marker is created on the next + // prompt line + const commandDetection = this._capabilities.get(TerminalCapability.CommandDetection); + if (commandDetection) { + await Event.toPromise(commandDetection.onCommandStarted); + } + const resolver = async (selector: ITerminalQuickFixOptions, lines?: string[]) => { if (lines === undefined) { return undefined; diff --git a/code/src/vs/workbench/contrib/testing/browser/media/testing.css b/code/src/vs/workbench/contrib/testing/browser/media/testing.css index 01d1919eecf..26f1ae8e33d 100644 --- a/code/src/vs/workbench/contrib/testing/browser/media/testing.css +++ b/code/src/vs/workbench/contrib/testing/browser/media/testing.css @@ -115,6 +115,14 @@ margin: 0; } +.test-explorer .monaco-list-row .error a { + color: var(--vscode-textLink-foreground); +} + +.test-explorer .monaco-list-row .error a:hover { + color: var(--vscode-textLink-activeForeground); +} + .test-explorer .computed-state, .test-output-peek-tree .computed-state { margin-right: 0.25em; diff --git a/code/src/vs/workbench/contrib/testing/browser/testing.contribution.ts b/code/src/vs/workbench/contrib/testing/browser/testing.contribution.ts index f628cbf59ec..edd2ca435b4 100644 --- a/code/src/vs/workbench/contrib/testing/browser/testing.contribution.ts +++ b/code/src/vs/workbench/contrib/testing/browser/testing.contribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { registerAction2 } from 'vs/platform/actions/common/actions'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; @@ -74,7 +74,7 @@ const viewContainer = Registry.as(ViewContainerExtensio const testResultsViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: Testing.ResultsPanelId, - title: { value: localize('testResultsPanelName', "Test Results"), original: 'Test Results' }, + title: localize2('testResultsPanelName', "Test Results"), icon: testingResultsIcon, ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [Testing.ResultsPanelId, { mergeViewWithContainerWhenSingleView: true }]), hideIfEmpty: true, @@ -86,7 +86,7 @@ const viewsRegistry = Registry.as(ViewContainerExtensions.ViewsR viewsRegistry.registerViews([{ id: Testing.ResultsViewId, - name: localize('testResultsPanelName', "Test Results"), + name: localize2('testResultsPanelName', "Test Results"), containerIcon: testingResultsIcon, canToggleVisibility: false, canMoveView: true, @@ -105,7 +105,7 @@ viewsRegistry.registerViewWelcomeContent(Testing.ExplorerViewId, { viewsRegistry.registerViews([{ id: Testing.ExplorerViewId, - name: localize('testExplorer', "Test Explorer"), + name: localize2('testExplorer', "Test Explorer"), ctorDescriptor: new SyncDescriptor(TestingExplorerView), canToggleVisibility: true, canMoveView: true, diff --git a/code/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts b/code/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts index f04d0d41651..abac89c073a 100644 --- a/code/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts +++ b/code/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts @@ -20,13 +20,14 @@ import { ExplorerFolderContext } from 'vs/workbench/contrib/files/common/files'; import { ResourceContextKey } from 'vs/workbench/common/contextkeys'; import { Codicon } from 'vs/base/common/codicons'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; +import { ILocalizedString } from 'vs/platform/action/common/action'; const timelineViewIcon = registerIcon('timeline-view-icon', Codicon.history, localize('timelineViewIcon', 'View icon of the timeline view.')); const timelineOpenIcon = registerIcon('timeline-open', Codicon.history, localize('timelineOpenIcon', 'Icon for the open timeline action.')); export class TimelinePaneDescriptor implements IViewDescriptor { readonly id = TimelinePaneId; - readonly name = TimelinePane.TITLE; + readonly name: ILocalizedString = TimelinePane.TITLE; readonly containerIcon = timelineViewIcon; readonly ctorDescriptor = new SyncDescriptor(TimelinePane); readonly order = 2; diff --git a/code/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/code/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index 5b658089901..88f98b07a07 100644 --- a/code/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/code/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/timelinePane'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import * as DOM from 'vs/base/browser/dom'; import { IAction, ActionRunner } from 'vs/base/common/actions'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; @@ -55,6 +55,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { AriaRole } from 'vs/base/browser/ui/aria/aria'; +import { ILocalizedString } from 'vs/platform/action/common/action'; const ItemHeight = 22; @@ -232,7 +233,7 @@ export const TimelineFollowActiveEditorContext = new RawContextKey('tim export const TimelineExcludeSources = new RawContextKey('timelineExcludeSources', '[]', true); export class TimelinePane extends ViewPane { - static readonly TITLE = localize('timeline', "Timeline"); + static readonly TITLE: ILocalizedString = localize2('timeline', "Timeline"); private $container!: HTMLElement; private $message!: HTMLDivElement; diff --git a/code/src/vs/workbench/contrib/update/browser/update.ts b/code/src/vs/workbench/contrib/update/browser/update.ts index bfba80a99df..b0ada2acd81 100644 --- a/code/src/vs/workbench/contrib/update/browser/update.ts +++ b/code/src/vs/workbench/contrib/update/browser/update.ts @@ -250,29 +250,25 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu } let badge: IBadge | undefined = undefined; - let clazz: string | undefined; let priority: number | undefined = undefined; if (state.type === StateType.AvailableForDownload || state.type === StateType.Downloaded || state.type === StateType.Ready) { badge = new NumberBadge(1, () => nls.localize('updateIsReady', "New {0} update available.", this.productService.nameShort)); } else if (state.type === StateType.CheckingForUpdates) { badge = new ProgressBadge(() => nls.localize('checkingForUpdates', "Checking for Updates...")); - clazz = 'progress-badge'; priority = 1; } else if (state.type === StateType.Downloading) { badge = new ProgressBadge(() => nls.localize('downloading', "Downloading...")); - clazz = 'progress-badge'; priority = 1; } else if (state.type === StateType.Updating) { badge = new ProgressBadge(() => nls.localize('updating', "Updating...")); - clazz = 'progress-badge'; priority = 1; } this.badgeDisposable.clear(); if (badge) { - this.badgeDisposable.value = this.activityService.showGlobalActivity({ badge, clazz, priority }); + this.badgeDisposable.value = this.activityService.showGlobalActivity({ badge, priority }); } this.state = state; diff --git a/code/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/code/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 666d26dd16d..5691e410542 100644 --- a/code/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/code/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -42,7 +42,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ViewContainerLocation, IViewContainersRegistry, Extensions, ViewContainer } from 'vs/workbench/common/views'; import { UserDataSyncDataViews } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncViews'; -import { IUserDataSyncWorkbenchService, getSyncAreaLabel, AccountStatus, CONTEXT_SYNC_STATE, CONTEXT_SYNC_ENABLEMENT, CONTEXT_ACCOUNT_STATE, CONFIGURE_SYNC_COMMAND_ID, SHOW_SYNC_LOG_COMMAND_ID, SYNC_VIEW_CONTAINER_ID, SYNC_TITLE, SYNC_ORIGINAL_TITLE, SYNC_VIEW_ICON, CONTEXT_HAS_CONFLICTS, DOWNLOAD_ACTIVITY_ACTION_DESCRIPTOR } from 'vs/workbench/services/userDataSync/common/userDataSync'; +import { IUserDataSyncWorkbenchService, getSyncAreaLabel, AccountStatus, CONTEXT_SYNC_STATE, CONTEXT_SYNC_ENABLEMENT, CONTEXT_ACCOUNT_STATE, CONFIGURE_SYNC_COMMAND_ID, SHOW_SYNC_LOG_COMMAND_ID, SYNC_VIEW_CONTAINER_ID, SYNC_TITLE, SYNC_VIEW_ICON, CONTEXT_HAS_CONFLICTS, DOWNLOAD_ACTIVITY_ACTION_DESCRIPTOR } from 'vs/workbench/services/userDataSync/common/userDataSync'; import { Codicon } from 'vs/base/common/codicons'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; @@ -425,19 +425,17 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo this.globalActivityBadgeDisposable.clear(); let badge: IBadge | undefined = undefined; - let clazz: string | undefined; let priority: number | undefined = undefined; if (this.userDataSyncService.conflicts.length && this.userDataSyncEnablementService.isEnabled()) { - badge = new NumberBadge(this.getConflictsCount(), () => localize('has conflicts', "{0}: Conflicts Detected", SYNC_TITLE)); + badge = new NumberBadge(this.getConflictsCount(), () => localize('has conflicts', "{0}: Conflicts Detected", SYNC_TITLE.value)); } else if (this.turningOnSync) { badge = new ProgressBadge(() => localize('turning on syncing', "Turning on Settings Sync...")); - clazz = 'progress-badge'; priority = 1; } if (badge) { - this.globalActivityBadgeDisposable.value = this.activityService.showGlobalActivity({ badge, clazz, priority }); + this.globalActivityBadgeDisposable.value = this.activityService.showGlobalActivity({ badge, priority }); } } @@ -451,7 +449,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } if (badge) { - this.accountBadgeDisposable.value = this.activityService.showAccountsActivity({ badge, clazz: undefined, priority: undefined }); + this.accountBadgeDisposable.value = this.activityService.showAccountsActivity({ badge, priority: undefined }); } } @@ -520,7 +518,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo const disposables: DisposableStore = new DisposableStore(); const quickPick = this.quickInputService.createQuickPick(); disposables.add(quickPick); - quickPick.title = SYNC_TITLE; + quickPick.title = SYNC_TITLE.value; quickPick.ok = false; quickPick.customButton = true; quickPick.customLabel = localize('sign in and turn on', "Sign in"); @@ -598,7 +596,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo const disposables: DisposableStore = new DisposableStore(); const quickPick = this.quickInputService.createQuickPick(); disposables.add(quickPick); - quickPick.title = localize('configure sync title', "{0}: Configure...", SYNC_TITLE); + quickPick.title = localize('configure sync title', "{0}: Configure...", SYNC_TITLE.value); quickPick.placeholder = localize('configure sync placeholder', "Choose what to sync"); quickPick.canSelectMany = true; quickPick.ignoreFocusOut = true; @@ -654,7 +652,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo return new Promise((c, e) => { const disposables: DisposableStore = new DisposableStore(); const quickPick = disposables.add(this.quickInputService.createQuickPick<{ id: UserDataSyncStoreType; label: string; description?: string }>()); - quickPick.title = localize('switchSyncService.title', "{0}: Select Service", SYNC_TITLE); + quickPick.title = localize('switchSyncService.title', "{0}: Select Service", SYNC_TITLE.value); quickPick.description = localize('switchSyncService.description', "Ensure you are using the same settings sync service when syncing with multiple environments"); quickPick.hideInput = true; quickPick.ignoreFocusOut = true; @@ -725,7 +723,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo super({ id: 'workbench.userDataSync.actions.turnOn', title: { value: localize('global activity turn on sync', "Backup and Sync Settings..."), original: 'Backup and Sync Settings...' }, - category: { value: SYNC_TITLE, original: `Settings Sync` }, + category: SYNC_TITLE, f1: true, precondition: when, menu: [{ @@ -845,7 +843,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo super({ id: showConflictsCommandId, get title() { return that.getShowConflictsTitle(); }, - category: { value: SYNC_TITLE, original: `Settings Sync` }, + category: SYNC_TITLE, f1: true, precondition: CONTEXT_HAS_CONFLICTS, menu: [{ @@ -943,7 +941,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo super({ id: showSyncedDataCommand.id, title: showSyncedDataCommand.title, - category: { value: SYNC_TITLE, original: `Settings Sync` }, + category: SYNC_TITLE, precondition: when, menu: { id: MenuId.CommandPalette, @@ -964,7 +962,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo super({ id: syncNowCommand.id, title: syncNowCommand.title, - category: { value: SYNC_TITLE, original: `Settings Sync` }, + category: SYNC_TITLE, menu: { id: MenuId.CommandPalette, when: ContextKeyExpr.and(CONTEXT_SYNC_ENABLEMENT, CONTEXT_ACCOUNT_STATE.isEqualTo(AccountStatus.Available), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)) @@ -984,7 +982,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo super({ id: turnOffSyncCommand.id, title: turnOffSyncCommand.title, - category: { value: SYNC_TITLE, original: `Settings Sync` }, + category: SYNC_TITLE, menu: { id: MenuId.CommandPalette, when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT), @@ -1011,7 +1009,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo super({ id: configureSyncCommand.id, title: configureSyncCommand.title, - category: { value: SYNC_TITLE, original: `Settings Sync` }, + category: SYNC_TITLE, icon: Codicon.settingsGear, tooltip: localize('configure', "Configure..."), menu: [{ @@ -1035,7 +1033,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo constructor() { super({ id: SHOW_SYNC_LOG_COMMAND_ID, - title: localize('show sync log title', "{0}: Show Log", SYNC_TITLE), + title: localize('show sync log title', "{0}: Show Log", SYNC_TITLE.value), tooltip: localize('show sync log toolrip', "Show Log"), icon: Codicon.output, menu: [{ @@ -1059,7 +1057,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo super({ id: showSyncSettingsCommand.id, title: showSyncSettingsCommand.title, - category: { value: SYNC_TITLE, original: `Settings Sync` }, + category: SYNC_TITLE, menu: { id: MenuId.CommandPalette, when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)), @@ -1078,7 +1076,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo constructor() { super({ id: 'workbench.userDataSync.actions.help', - title: { value: SYNC_TITLE, original: 'Settings Sync' }, + title: SYNC_TITLE, category: Categories.Help, menu: [{ id: MenuId.CommandPalette, @@ -1155,7 +1153,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo return Registry.as(Extensions.ViewContainersRegistry).registerViewContainer( { id: SYNC_VIEW_CONTAINER_ID, - title: { value: SYNC_TITLE, original: SYNC_ORIGINAL_TITLE }, + title: SYNC_TITLE, ctorDescriptor: new SyncDescriptor( ViewPaneContainer, [SYNC_VIEW_CONTAINER_ID, { mergeViewWithContainerWhenSingleView: true }] diff --git a/code/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts b/code/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts index 59f69f53d85..4362953931d 100644 --- a/code/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts +++ b/code/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts @@ -5,7 +5,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IViewsRegistry, Extensions, ITreeViewDescriptor, ITreeViewDataProvider, ITreeItem, TreeItemCollapsibleState, TreeViewItemHandleArg, ViewContainer } from 'vs/workbench/common/views'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { TreeView, TreeViewPane } from 'vs/workbench/browser/parts/views/treeView'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -60,7 +60,7 @@ export class UserDataSyncDataViews extends Disposable { private registerConflictsView(container: ViewContainer): void { const viewsRegistry = Registry.as(Extensions.ViewsRegistry); - const viewName = localize('conflicts', "Conflicts"); + const viewName = localize2('conflicts', "Conflicts"); viewsRegistry.registerViews([{ id: SYNC_CONFLICTS_VIEW_ID, name: viewName, @@ -68,7 +68,7 @@ export class UserDataSyncDataViews extends Disposable { when: ContextKeyExpr.and(CONTEXT_ENABLE_SYNC_CONFLICTS_VIEW, CONTEXT_HAS_CONFLICTS), canToggleVisibility: false, canMoveView: false, - treeView: this.instantiationService.createInstance(TreeView, SYNC_CONFLICTS_VIEW_ID, viewName), + treeView: this.instantiationService.createInstance(TreeView, SYNC_CONFLICTS_VIEW_ID, viewName.value), collapsed: false, order: 100, }], container); @@ -76,8 +76,8 @@ export class UserDataSyncDataViews extends Disposable { private registerMachinesView(container: ViewContainer): void { const id = `workbench.views.sync.machines`; - const name = localize('synced machines', "Synced Machines"); - const treeView = this.instantiationService.createInstance(TreeView, id, name); + const name = localize2('synced machines', "Synced Machines"); + const treeView = this.instantiationService.createInstance(TreeView, id, name.value); const dataProvider = this.instantiationService.createInstance(UserDataSyncMachinesViewDataProvider, treeView); treeView.showRefreshAction = true; treeView.canSelectMany = true; @@ -140,8 +140,8 @@ export class UserDataSyncDataViews extends Disposable { private registerActivityView(container: ViewContainer, remote: boolean): void { const id = `workbench.views.sync.${remote ? 'remote' : 'local'}Activity`; - const name = remote ? localize('remote sync activity title', "Sync Activity (Remote)") : localize('local sync activity title', "Sync Activity (Local)"); - const treeView = this.instantiationService.createInstance(TreeView, id, name); + const name = remote ? localize2('remote sync activity title', "Sync Activity (Remote)") : localize2('local sync activity title', "Sync Activity (Local)"); + const treeView = this.instantiationService.createInstance(TreeView, id, name.value); treeView.showCollapseAllAction = true; treeView.showRefreshAction = true; treeView.dataProvider = remote ? this.instantiationService.createInstance(RemoteUserDataSyncActivityViewDataProvider) @@ -170,9 +170,9 @@ export class UserDataSyncDataViews extends Disposable { private registerExternalActivityView(container: ViewContainer): void { const id = `workbench.views.sync.externalActivity`; - const name = localize('downloaded sync activity title', "Sync Activity (Developer)"); + const name = localize2('downloaded sync activity title', "Sync Activity (Developer)"); const dataProvider = this.instantiationService.createInstance(ExtractedUserDataSyncActivityViewDataProvider, undefined); - const treeView = this.instantiationService.createInstance(TreeView, id, name); + const treeView = this.instantiationService.createInstance(TreeView, id, name.value); treeView.showCollapseAllAction = false; treeView.showRefreshAction = false; treeView.dataProvider = dataProvider; @@ -284,7 +284,7 @@ export class UserDataSyncDataViews extends Disposable { const result = await dialogService.confirm({ message: localize({ key: 'confirm replace', comment: ['A confirmation message to replace current user data (settings, extensions, keybindings, snippets) with selected version'] }, "Would you like to replace your current {0} with selected?", getSyncAreaLabel(syncResource)), type: 'info', - title: SYNC_TITLE + title: SYNC_TITLE.value }); if (result.confirmed) { return userDataSyncService.replace({ created: syncResourceHandle.created, uri: URI.revive(syncResourceHandle.uri) }); @@ -296,8 +296,8 @@ export class UserDataSyncDataViews extends Disposable { private registerTroubleShootView(container: ViewContainer): void { const id = `workbench.views.sync.troubleshoot`; - const name = localize('troubleshoot', "Troubleshoot"); - const treeView = this.instantiationService.createInstance(TreeView, id, name); + const name = localize2('troubleshoot', "Troubleshoot"); + const treeView = this.instantiationService.createInstance(TreeView, id, name.value); const dataProvider = this.instantiationService.createInstance(UserDataSyncTroubleshootViewDataProvider); treeView.showRefreshAction = true; treeView.dataProvider = dataProvider; diff --git a/code/src/vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution.ts b/code/src/vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution.ts index 7d757dc69b2..4f3ae606740 100644 --- a/code/src/vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution.ts +++ b/code/src/vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution.ts @@ -10,7 +10,7 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { UserDataSycnUtilServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions'; -import { localize } from 'vs/nls'; +import { localize, localize2 } from 'vs/nls'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; @@ -36,8 +36,8 @@ registerAction2(class OpenSyncBackupsFolder extends Action2 { constructor() { super({ id: 'workbench.userData.actions.openSyncBackupsFolder', - title: { value: localize('Open Backup folder', "Open Local Backups Folder"), original: 'Open Local Backups Folder' }, - category: { value: SYNC_TITLE, original: `Settings Sync` }, + title: localize2('Open Backup folder', "Open Local Backups Folder"), + category: SYNC_TITLE, menu: { id: MenuId.CommandPalette, when: CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), diff --git a/code/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInput.ts b/code/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInput.ts index 42e9345171b..4d5ae275604 100644 --- a/code/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInput.ts +++ b/code/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInput.ts @@ -30,7 +30,7 @@ export class WebviewInput extends EditorInput { } public override get capabilities(): EditorInputCapabilities { - return EditorInputCapabilities.Readonly | EditorInputCapabilities.Singleton | EditorInputCapabilities.CanDropIntoEditor; + return EditorInputCapabilities.Readonly | EditorInputCapabilities.Singleton | EditorInputCapabilities.CanDropIntoEditor | EditorInputCapabilities.AuxWindowUnsupported; } private readonly _resourceId = generateUuid(); diff --git a/code/src/vs/workbench/electron-sandbox/actions/windowActions.ts b/code/src/vs/workbench/electron-sandbox/actions/windowActions.ts index 352f575bc2d..95c4100ff13 100644 --- a/code/src/vs/workbench/electron-sandbox/actions/windowActions.ts +++ b/code/src/vs/workbench/electron-sandbox/actions/windowActions.ts @@ -237,7 +237,7 @@ abstract class BaseSwitchWindow extends Action2 { }); if (pick) { - nativeHostService.focusWindow({ windowId: pick.payload }); + nativeHostService.focusWindow({ targetWindowId: pick.payload }); } } } diff --git a/code/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/code/src/vs/workbench/electron-sandbox/desktop.contribution.ts index b2dad41cbe3..f904efaafae 100644 --- a/code/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/code/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -361,6 +361,10 @@ import { applicationConfigurationNodeBase, securityConfigurationNodeBase } from 'disable-chromium-sandbox': { type: 'boolean', description: localize('argv.disableChromiumSandbox', "Disables the Chromium sandbox. This is useful when running VS Code as elevated on Linux and running under Applocker on Windows.") + }, + 'use-inmemory-secretstorage': { + type: 'boolean', + description: localize('argv.useInMemorySecretStorage', "Ensures that an in-memory store will be used for secret storage instead of using the OS's credential store. This is often used when running VS Code extension tests or when you're experiencing difficulties with the credential store.") } } }; @@ -369,6 +373,10 @@ import { applicationConfigurationNodeBase, securityConfigurationNodeBase } from type: 'boolean', description: localize('argv.force-renderer-accessibility', 'Forces the renderer to be accessible. ONLY change this if you are using a screen reader on Linux. On other platforms the renderer will automatically be accessible. This flag is automatically set if you have editor.accessibilitySupport: on.'), }; + schema.properties!['password-store'] = { + type: 'string', + description: localize('argv.passwordStore', "Configures the backend used to store secrets on Linux. This argument is ignored on Windows & macOS.") + }; } jsonRegistry.registerSchema(argvDefinitionFileSchemaId, schema); diff --git a/code/src/vs/workbench/electron-sandbox/desktop.main.ts b/code/src/vs/workbench/electron-sandbox/desktop.main.ts index d6522c913f6..e202c0de9c1 100644 --- a/code/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/code/src/vs/workbench/electron-sandbox/desktop.main.ts @@ -29,7 +29,7 @@ import { IRemoteAuthorityResolverService, RemoteConnectionType } from 'vs/platfo import { RemoteAgentService } from 'vs/workbench/services/remote/electron-sandbox/remoteAgentService'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { FileService } from 'vs/platform/files/common/fileService'; -import { IWorkbenchFileService } from 'vs/workbench/services/files/common/files'; +import { IFileService } from 'vs/platform/files/common/files'; import { RemoteFileSystemProviderClient } from 'vs/workbench/services/remote/common/remoteFileSystemProviderClient'; import { ConfigurationCache } from 'vs/workbench/services/configuration/common/configurationCache'; import { ISignService } from 'vs/platform/sign/common/sign'; @@ -227,7 +227,7 @@ export class DesktopMain extends Disposable { // Files const fileService = this._register(new FileService(logService)); - serviceCollection.set(IWorkbenchFileService, fileService); + serviceCollection.set(IFileService, fileService); // Remote const remoteAuthorityResolverService = new RemoteAuthorityResolverService(productService, new ElectronRemoteResourceLoader(environmentService.window.id, mainProcessService, fileService)); diff --git a/code/src/vs/workbench/electron-sandbox/window.ts b/code/src/vs/workbench/electron-sandbox/window.ts index f7188e6f8e6..9503b73d7c2 100644 --- a/code/src/vs/workbench/electron-sandbox/window.ts +++ b/code/src/vs/workbench/electron-sandbox/window.ts @@ -62,7 +62,6 @@ import { whenEditorClosed } from 'vs/workbench/browser/editor'; import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { toErrorMessage } from 'vs/base/common/errorMessage'; -import { registerWindowDriver } from 'vs/platform/driver/electron-sandbox/driver'; import { ILabelService } from 'vs/platform/label/common/label'; import { dirname } from 'vs/base/common/resources'; import { IBannerService } from 'vs/workbench/services/banner/browser/bannerService'; @@ -70,6 +69,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { IUtilityProcessWorkerWorkbenchService } from 'vs/workbench/services/utilityProcess/electron-sandbox/utilityProcessWorkerWorkbenchService'; +import { registerWindowDriver } from 'vs/workbench/services/driver/electron-sandbox/driver'; export class NativeWindow extends Disposable { @@ -726,18 +726,6 @@ export class NativeWindow extends Disposable { // Windows 32-bit warning if (isWindows && this.environmentService.os.arch === 'ia32') { const message = localize('windows32eolmessage', "You are running {0} 32-bit, which will soon stop receiving updates on Windows. Consider upgrading to the 64-bit build.", this.productService.nameLong); - const actions = [{ - label: localize('windowseolBannerLearnMore', "Learn More"), - href: 'https://aka.ms/vscode-faq-old-windows' - }]; - - this.bannerService.show({ - id: 'windows32eol.banner', - message, - ariaLabel: localize('windowseolarialabel', "{0}. Use navigation keys to access banner actions.", message), - actions, - icon: Codicon.warning - }); this.notificationService.prompt( Severity.Warning, @@ -764,18 +752,6 @@ export class NativeWindow extends Disposable { if (eolReleases.has(majorVersion)) { const message = localize('macoseolmessage', "{0} on {1} will soon stop receiving updates. Consider upgrading your macOS version.", this.productService.nameLong, eolReleases.get(majorVersion)); - const actions = [{ - label: localize('macoseolBannerLearnMore', "Learn More"), - href: 'https://aka.ms/vscode-faq-old-macOS' - }]; - - this.bannerService.show({ - id: 'macoseol.banner', - message, - ariaLabel: localize('macoseolarialabel', "{0}. Use navigation keys to access banner actions.", message), - actions, - icon: Codicon.warning - }); this.notificationService.prompt( Severity.Warning, diff --git a/code/src/vs/workbench/services/actions/common/menusExtensionPoint.ts b/code/src/vs/workbench/services/actions/common/menusExtensionPoint.ts index bb1993e9d95..56609d8331f 100644 --- a/code/src/vs/workbench/services/actions/common/menusExtensionPoint.ts +++ b/code/src/vs/workbench/services/actions/common/menusExtensionPoint.ts @@ -124,6 +124,12 @@ const apiMenus: IAPIMenu[] = [ id: MenuId.SCMSourceControl, description: localize('menus.scmSourceControl', "The Source Control menu") }, + { + key: 'scm/inputBox', + id: MenuId.SCMInputBox, + description: localize('menus.scmInputBox', "The Source Control input box menu"), + proposed: 'contribSourceControlInputBoxMenu' + }, { key: 'scm/resourceState/context', id: MenuId.SCMResourceContext, diff --git a/code/src/vs/workbench/services/activity/browser/activityService.ts b/code/src/vs/workbench/services/activity/browser/activityService.ts index 77a8841dcb5..8295fe57470 100644 --- a/code/src/vs/workbench/services/activity/browser/activityService.ts +++ b/code/src/vs/workbench/services/activity/browser/activityService.ts @@ -6,11 +6,10 @@ import { IActivityService, IActivity } from 'vs/workbench/services/activity/common/activity'; import { IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IViewDescriptorService, ViewContainer } from 'vs/workbench/common/views'; import { GLOBAL_ACTIVITY_ID, ACCOUNTS_ACTIVITY_ID } from 'vs/workbench/common/activity'; -import { Event } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; class ViewContainerActivityByView extends Disposable { @@ -55,29 +54,62 @@ interface IViewActivity { readonly activity: ViewContainerActivityByView; } -export class ActivityService implements IActivityService { +export class ActivityService extends Disposable implements IActivityService { public _serviceBrand: undefined; - private viewActivities = new Map(); + private readonly viewActivities = new Map(); + + private readonly _onDidChangeActivity = this._register(new Emitter()); + readonly onDidChangeActivity = this._onDidChangeActivity.event; + + private readonly viewContainerActivities = new Map(); + private readonly globalActivities = new Map(); constructor( - @IPaneCompositePartService private readonly paneCompositeService: IPaneCompositePartService, @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, @IInstantiationService private readonly instantiationService: IInstantiationService - ) { } + ) { + super(); + } - showViewContainerActivity(viewContainerId: string, { badge, clazz, priority }: IActivity): IDisposable { + showViewContainerActivity(viewContainerId: string, activity: IActivity): IDisposable { const viewContainer = this.viewDescriptorService.getViewContainerById(viewContainerId); if (viewContainer) { - const location = this.viewDescriptorService.getViewContainerLocation(viewContainer); - if (location !== null) { - return this.paneCompositeService.showActivity(viewContainer.id, location, badge, clazz, priority); + let activities = this.viewContainerActivities.get(viewContainerId); + if (!activities) { + activities = []; + this.viewContainerActivities.set(viewContainerId, activities); } + for (let i = 0; i <= activities.length; i++) { + if (i === activities.length) { + activities.push(activity); + break; + } else if (activities[i].priority ?? 0 <= (activity.priority ?? 0)) { + activities.splice(i, 0, activity); + break; + } + } + this._onDidChangeActivity.fire(viewContainer); + return toDisposable(() => { + activities!.splice(activities!.indexOf(activity), 1); + if (activities!.length === 0) { + this.viewContainerActivities.delete(viewContainerId); + } + this._onDidChangeActivity.fire(viewContainer); + }); } return Disposable.None; } + getViewContainerActivities(viewContainerId: string): IActivity[] { + const viewContainer = this.viewDescriptorService.getViewContainerById(viewContainerId); + if (viewContainer) { + return this.viewContainerActivities.get(viewContainerId) ?? []; + } + return []; + } + showViewActivity(viewId: string, activity: IActivity): IDisposable { let maybeItem = this.viewActivities.get(viewId); @@ -104,12 +136,33 @@ export class ActivityService implements IActivityService { }); } - showAccountsActivity({ badge, clazz, priority }: IActivity): IDisposable { - return this.paneCompositeService.showActivity(ACCOUNTS_ACTIVITY_ID, ViewContainerLocation.Sidebar, badge, clazz, priority); + showAccountsActivity(activity: IActivity): IDisposable { + return this.showActivity(ACCOUNTS_ACTIVITY_ID, activity); + } + + showGlobalActivity(activity: IActivity): IDisposable { + return this.showActivity(GLOBAL_ACTIVITY_ID, activity); + } + + getActivity(id: string): IActivity[] { + return this.globalActivities.get(id) ?? []; } - showGlobalActivity({ badge, clazz, priority }: IActivity): IDisposable { - return this.paneCompositeService.showActivity(GLOBAL_ACTIVITY_ID, ViewContainerLocation.Sidebar, badge, clazz, priority); + private showActivity(id: string, activity: IActivity): IDisposable { + let activities = this.globalActivities.get(id); + if (!activities) { + activities = []; + this.globalActivities.set(id, activities); + } + activities.push(activity); + this._onDidChangeActivity.fire(id); + return toDisposable(() => { + activities!.splice(activities!.indexOf(activity), 1); + if (activities!.length === 0) { + this.globalActivities.delete(id); + } + this._onDidChangeActivity.fire(id); + }); } } diff --git a/code/src/vs/workbench/services/activity/common/activity.ts b/code/src/vs/workbench/services/activity/common/activity.ts index 1465fca02a3..62f5150097b 100644 --- a/code/src/vs/workbench/services/activity/common/activity.ts +++ b/code/src/vs/workbench/services/activity/common/activity.ts @@ -6,10 +6,11 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ThemeIcon } from 'vs/base/common/themables'; +import { Event } from 'vs/base/common/event'; +import { ViewContainer } from 'vs/workbench/common/views'; export interface IActivity { readonly badge: IBadge; - readonly clazz?: string; readonly priority?: number; } @@ -19,11 +20,21 @@ export interface IActivityService { readonly _serviceBrand: undefined; + /** + * Emitted when activity changes for a view container or when the activity of the global actions change. + */ + readonly onDidChangeActivity: Event; + /** * Show activity for the given view container */ showViewContainerActivity(viewContainerId: string, badge: IActivity): IDisposable; + /** + * Returns the activity for the given view container + */ + getViewContainerActivities(viewContainerId: string): IActivity[]; + /** * Show activity for the given view */ @@ -38,6 +49,11 @@ export interface IActivityService { * Show global activity */ showGlobalActivity(activity: IActivity): IDisposable; + + /** + * Return the activity for the given action + */ + getActivity(id: string): IActivity[]; } export interface IBadge { diff --git a/code/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts b/code/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts index af390792fd5..c6daa32b996 100644 --- a/code/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts +++ b/code/src/vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService.ts @@ -12,8 +12,6 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { onUnexpectedError } from 'vs/base/common/errors'; import { isWeb } from 'vs/base/common/platform'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; export const IAuxiliaryWindowService = createDecorator('auxiliaryWindowService'); @@ -26,22 +24,22 @@ export interface IAuxiliaryWindowService { export interface IAuxiliaryWindow extends IDisposable { - readonly onDidResize: Event; + readonly onWillLayout: Event; readonly onDidClose: Event; readonly container: HTMLElement; + + layout(): void; } -type AuxiliaryWindow = Window & typeof globalThis; +export type AuxiliaryWindow = Window & typeof globalThis; export class BrowserAuxiliaryWindowService implements IAuxiliaryWindowService { declare readonly _serviceBrand: undefined; constructor( - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @ILifecycleService private readonly lifecycleService: ILifecycleService + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService ) { } open(): IAuxiliaryWindow { @@ -51,6 +49,18 @@ export class BrowserAuxiliaryWindowService implements IAuxiliaryWindowService { disposables.add(registerWindow(auxiliaryWindow)); disposables.add(toDisposable(() => auxiliaryWindow.close())); + const { container, onWillLayout, onDidClose } = this.create(auxiliaryWindow, disposables); + + return { + container, + onWillLayout: onWillLayout.event, + onDidClose: onDidClose.event, + layout: () => onWillLayout.fire(getClientArea(container)), + dispose: () => disposables.dispose() + }; + } + + protected create(auxiliaryWindow: AuxiliaryWindow, disposables: DisposableStore) { this.patchMethods(auxiliaryWindow); this.applyMeta(auxiliaryWindow); @@ -58,17 +68,9 @@ export class BrowserAuxiliaryWindowService implements IAuxiliaryWindowService { const container = this.applyHTML(auxiliaryWindow, disposables); - const { onDidResize, onDidClose } = this.registerListeners(auxiliaryWindow, container, disposables); + const { onWillLayout, onDidClose } = this.registerListeners(auxiliaryWindow, container, disposables); - disposables.add(Event.once(this.lifecycleService.onDidShutdown)(() => disposables.dispose())); - disposables.add(Event.once(onDidClose.event)(() => disposables.dispose())); - - return { - container, - onDidResize: onDidResize.event, - onDidClose: onDidClose.event, - dispose: () => disposables.dispose() - }; + return { container, onWillLayout, onDidClose }; } private applyMeta(auxiliaryWindow: AuxiliaryWindow): void { @@ -87,31 +89,60 @@ export class BrowserAuxiliaryWindowService implements IAuxiliaryWindowService { } } - private applyCSS(auxiliaryWindow: AuxiliaryWindow, disposables: DisposableStore): void { + protected applyCSS(auxiliaryWindow: AuxiliaryWindow, disposables: DisposableStore): void { + const mapOriginalToClone = new Map(); + + function cloneNode(originalNode: Node): void { + const clonedNode = auxiliaryWindow.document.head.appendChild(originalNode.cloneNode(true)); + mapOriginalToClone.set(originalNode, clonedNode); + } // Clone all style elements and stylesheet links from the window to the child window - for (const element of document.head.querySelectorAll('link[rel="stylesheet"], style')) { - auxiliaryWindow.document.head.appendChild(element.cloneNode(true)); + for (const originalNode of document.head.querySelectorAll('link[rel="stylesheet"], style')) { + cloneNode(originalNode); } - // Running out of sources: listen to new stylesheets as they - // are being added to the main window and apply to child window - if (!this.environmentService.isBuilt) { - const observer = new MutationObserver(mutations => { - for (const mutation of mutations) { - if (mutation.type === 'childList') { - for (const node of mutation.addedNodes) { - if (node instanceof HTMLElement && node.tagName.toLowerCase() === 'style') { - auxiliaryWindow.document.head.appendChild(node.cloneNode(true)); - } + // Listen to new stylesheets as they are being added or removed in the main window + // and apply to child window (including changes to existing stylesheets elements) + const observer = new MutationObserver(mutations => { + for (const mutation of mutations) { + if ( + mutation.type !== 'childList' || // only interested in added/removed nodes + mutation.target.nodeName.toLowerCase() === 'title' || // skip over title changes that happen frequently + mutation.target.nodeName.toLowerCase() === 'script' || // block