diff --git a/build/azure-pipelines/alpine/product-build-alpine.yml b/build/azure-pipelines/alpine/product-build-alpine.yml index 4e318644d08..77f97561426 100644 --- a/build/azure-pipelines/alpine/product-build-alpine.yml +++ b/build/azure-pipelines/alpine/product-build-alpine.yml @@ -138,6 +138,19 @@ steps: condition: and(succeededOrFailed(), notIn(variables['Agent.JobStatus'], 'Succeeded', 'SucceededWithIssues')) displayName: Generate artifact prefix + - script: mkdir $(agent.builddirectory)/vscode-alpine-$(VSCODE_ARCH) + displayName: Make folder for SBOM + + - task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 + displayName: Generate SBOM + inputs: + BuildDropPath: $(agent.builddirectory)/vscode-alpine-$(VSCODE_ARCH) + PackageName: Visual Studio Code Server + + - publish: $(agent.builddirectory)/vscode-alpine-$(VSCODE_ARCH)/_manifest + displayName: Publish SBOM + artifact: $(ARTIFACT_PREFIX)sbom_vscode_alpine_$(VSCODE_ARCH) + - publish: $(SERVER_PATH) artifact: $(ARTIFACT_PREFIX)vscode_server_alpine_$(VSCODE_ARCH)_archive-unsigned displayName: Publish server archive diff --git a/build/azure-pipelines/cli/cli-compile-and-publish.yml b/build/azure-pipelines/cli/cli-compile-and-publish.yml index af9960d7f5b..05ad5538d87 100644 --- a/build/azure-pipelines/cli/cli-compile-and-publish.yml +++ b/build/azure-pipelines/cli/cli-compile-and-publish.yml @@ -106,3 +106,26 @@ steps: - publish: $(Build.ArtifactStagingDirectory)/${{ parameters.VSCODE_CLI_ARTIFACT }}.tar.gz artifact: ${{ parameters.VSCODE_CLI_ARTIFACT }} displayName: Publish ${{ parameters.VSCODE_CLI_ARTIFACT }} artifact + + # Make a folder for the SBOM for the specific artifact + - ${{ if contains(parameters.VSCODE_CLI_TARGET, '-windows-') }}: + - powershell: mkdir $(Build.ArtifactStagingDirectory)/sbom_${{ parameters.VSCODE_CLI_ARTIFACT }} + displayName: Make folder for SBOM (Windows) + + - ${{ else }}: + - script: mkdir $(Build.ArtifactStagingDirectory)/sbom_${{ parameters.VSCODE_CLI_ARTIFACT }} + displayName: Make folder for SBOM (non-Windows) + + # The if cases above are for different OSes, + # but we're still in the branch where the cli is being published in general. + # Generate and publish an SBOM. + - task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 + displayName: Generate SBOM + inputs: + BuildComponentPath: $(Build.SourcesDirectory)/cli + BuildDropPath: $(Build.ArtifactStagingDirectory)/sbom_${{ parameters.VSCODE_CLI_ARTIFACT }} + PackageName: Visual Studio Code CLI + + - publish: $(Build.ArtifactStagingDirectory)/sbom_${{ parameters.VSCODE_CLI_ARTIFACT }}/_manifest + displayName: Publish SBOM + artifact: sbom_${{ parameters.VSCODE_CLI_ARTIFACT }} diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index 35b207cb7f5..4a4c587c7aa 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -219,16 +219,17 @@ steps: - task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 displayName: Generate SBOM (server) inputs: + BuildComponentPath: $(Build.SourcesDirectory)/remote BuildDropPath: $(agent.builddirectory)/vscode-server-darwin-$(VSCODE_ARCH) PackageName: Visual Studio Code Server - publish: $(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH)/_manifest displayName: Publish SBOM (client) - artifact: $(ARTIFACT_PREFIX)sbom_client_darwin_$(VSCODE_ARCH)_sbom + artifact: $(ARTIFACT_PREFIX)sbom_vscode_client_darwin_$(VSCODE_ARCH) - publish: $(agent.builddirectory)/vscode-server-darwin-$(VSCODE_ARCH)/_manifest displayName: Publish SBOM (server) - artifact: $(ARTIFACT_PREFIX)sbom_server_darwin_$(VSCODE_ARCH)_sbom + artifact: $(ARTIFACT_PREFIX)sbom_vscode_server_darwin_$(VSCODE_ARCH) - publish: $(CLIENT_PATH) artifact: $(ARTIFACT_PREFIX)unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 3923d7d105f..b2c993b4a14 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -331,6 +331,7 @@ steps: - task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 displayName: Generate SBOM (server) inputs: + BuildComponentPath: $(Build.SourcesDirectory)/remote BuildDropPath: $(agent.builddirectory)/vscode-server-linux-$(VSCODE_ARCH) PackageName: Visual Studio Code Server diff --git a/build/azure-pipelines/linux/snap-build-linux.yml b/build/azure-pipelines/linux/snap-build-linux.yml index c74783da146..7f00653a4bd 100644 --- a/build/azure-pipelines/linux/snap-build-linux.yml +++ b/build/azure-pipelines/linux/snap-build-linux.yml @@ -50,6 +50,19 @@ steps: echo "##vso[task.setvariable variable=SNAP_PATH]$SNAP_PATH" displayName: Prepare for publish + - script: mkdir -p $(agent.builddirectory)/vscode-snap-linux-$(VSCODE_ARCH) + displayName: Make folder for SBOM + + - task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 + displayName: Generate SBOM + inputs: + BuildDropPath: $(agent.builddirectory)/vscode-snap-linux-$(VSCODE_ARCH) + PackageName: Visual Studio Code Snap + + - publish: $(agent.builddirectory)/vscode-snap-linux-$(VSCODE_ARCH)/_manifest + displayName: Publish SBOM + artifact: $(ARTIFACT_PREFIX)sbom_vscode_client_linux_snap_$(VSCODE_ARCH) + - publish: $(SNAP_PATH) artifact: vscode_client_linux_$(VSCODE_ARCH)_snap displayName: Publish snap package diff --git a/build/azure-pipelines/web/product-build-web.yml b/build/azure-pipelines/web/product-build-web.yml index ecdfe91ed0b..74ebc3df8f5 100644 --- a/build/azure-pipelines/web/product-build-web.yml +++ b/build/azure-pipelines/web/product-build-web.yml @@ -107,6 +107,7 @@ steps: displayName: Build - task: AzureCLI@2 + displayName: Fetch secrets from Azure inputs: azureSubscription: "vscode-builds-subscription" scriptType: pscore @@ -151,6 +152,16 @@ steps: condition: and(succeededOrFailed(), notIn(variables['Agent.JobStatus'], 'Succeeded', 'SucceededWithIssues')) displayName: Generate artifact prefix + - task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 + displayName: Generate SBOM + inputs: + BuildDropPath: $(agent.builddirectory)/vscode-web + PackageName: Visual Studio Code Web + + - publish: $(agent.builddirectory)/vscode-web/_manifest + displayName: Publish SBOM (client) + artifact: $(ARTIFACT_PREFIX)sbom_vscode_web + - publish: $(WEB_PATH) artifact: $(ARTIFACT_PREFIX)vscode_web_linux_standalone_archive-unsigned condition: and(succeededOrFailed(), ne(variables['WEB_PATH'], '')) diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index ebfbc701146..ab0813b9f76 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -322,17 +322,18 @@ steps: - task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 displayName: Generate SBOM (server) inputs: + BuildComponentPath: $(Build.SourcesDirectory)/remote BuildDropPath: $(agent.builddirectory)/vscode-server-win32-$(VSCODE_ARCH) PackageName: Visual Studio Code Server condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'arm64')) - publish: $(agent.builddirectory)/VSCode-win32-$(VSCODE_ARCH)/_manifest displayName: Publish SBOM (client) - artifact: $(ARTIFACT_PREFIX)sbom_client_win32_$(VSCODE_ARCH) + artifact: $(ARTIFACT_PREFIX)sbom_vscode_client_win32_$(VSCODE_ARCH) - publish: $(agent.builddirectory)/vscode-server-win32-$(VSCODE_ARCH)/_manifest displayName: Publish SBOM (server) - artifact: $(ARTIFACT_PREFIX)sbom_server_win32_$(VSCODE_ARCH) + artifact: $(ARTIFACT_PREFIX)sbom_vscode_server_win32_$(VSCODE_ARCH) condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'arm64')) - publish: $(CLIENT_PATH) diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 2d2451c28e8..4ae98a95577 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -73,7 +73,6 @@ const vscodeResources = [ 'out-build/vs/workbench/contrib/terminal/browser/media/*.sh', 'out-build/vs/workbench/contrib/terminal/browser/media/*.zsh', 'out-build/vs/workbench/contrib/webview/browser/pre/*.js', - 'out-build/vs/workbench/services/voiceRecognition/electron-sandbox/voiceTranscriptionWorklet.js', 'out-build/vs/**/markdown.css', 'out-build/vs/workbench/contrib/tasks/**/*.json', '!**/test/**' diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 2d03fbd5712..65f37d5e283 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -82,10 +82,6 @@ "name": "vs/workbench/services/assignment", "project": "vscode-workbench" }, - { - "name": "vs/workbench/services/voiceRecognition", - "project": "vscode-workbench" - }, { "name": "vs/workbench/contrib/extensions", "project": "vscode-workbench" diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index 6a99e81b5a4..70475768de0 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -681,6 +681,7 @@ "--vscode-textCodeBlock-background", "--vscode-textLink-activeForeground", "--vscode-textLink-foreground", + "--vscode-textPreformat-background", "--vscode-textPreformat-foreground", "--vscode-textSeparator-foreground", "--vscode-titleBar-activeBackground", @@ -695,6 +696,8 @@ "--vscode-tree-indentGuidesStroke", "--vscode-tree-tableColumnsBorder", "--vscode-tree-tableOddRowsBackground", + "--vscode-voiceRecording-background", + "--vscode-voiceRecording-dimmedBackground", "--vscode-walkThrough-embeddedEditorBackground", "--vscode-walkthrough-stepTitle-foreground", "--vscode-welcomePage-background", @@ -706,9 +709,7 @@ "--vscode-widget-border", "--vscode-widget-shadow", "--vscode-window-activeBorder", - "--vscode-window-inactiveBorder", - "--vscode-voiceRecording-background", - "--vscode-voiceRecording-dimmedBackground" + "--vscode-window-inactiveBorder" ], "others": [ "--background-dark", @@ -782,4 +783,4 @@ "--z-index-notebook-sticky-scroll", "--zoom-factor" ] -} +} \ No newline at end of file diff --git a/cli/src/tunnels/code_server.rs b/cli/src/tunnels/code_server.rs index e17e32f8840..06e42681799 100644 --- a/cli/src/tunnels/code_server.rs +++ b/cli/src/tunnels/code_server.rs @@ -510,7 +510,7 @@ impl<'a> ServerBuilder<'a> { let (mut origin, listen_rx) = monitor_server::(child, Some(log_file), plog, false); - let socket = match timeout(Duration::from_secs(8), listen_rx).await { + let socket = match timeout(Duration::from_secs(30), listen_rx).await { Err(e) => { origin.kill().await; Err(wrap(e, "timed out looking for socket")) diff --git a/extensions/theme-defaults/themes/dark_modern.json b/extensions/theme-defaults/themes/dark_modern.json index e1055e8a4e2..cdb5c1f5f8f 100644 --- a/extensions/theme-defaults/themes/dark_modern.json +++ b/extensions/theme-defaults/themes/dark_modern.json @@ -49,7 +49,7 @@ "input.background": "#313131", "input.border": "#3C3C3C", "input.foreground": "#CCCCCC", - "input.placeholderForeground": "#9D9D9D", + "input.placeholderForeground": "#818181", "inputOption.activeBackground": "#2489DB82", "inputOption.activeBorder": "#2488DB", "keybindingLabel.foreground": "#CCCCCC", @@ -111,8 +111,10 @@ "textBlockQuote.background": "#2B2B2B", "textBlockQuote.border": "#616161", "textCodeBlock.background": "#2B2B2B", - "textLink.activeForeground": "#40A6FF", - "textLink.foreground": "#40A6FF", + "textLink.activeForeground": "#4daafc", + "textLink.foreground": "#4daafc", + "textPreformat.foreground": "#D0D0D0", + "textPreformat.background": "#3C3C3C", "textSeparator.foreground": "#21262D", "titleBar.activeBackground": "#181818", "titleBar.activeForeground": "#CCCCCC", diff --git a/extensions/theme-defaults/themes/light_modern.json b/extensions/theme-defaults/themes/light_modern.json index d5bf68ba831..a89defee385 100644 --- a/extensions/theme-defaults/themes/light_modern.json +++ b/extensions/theme-defaults/themes/light_modern.json @@ -131,6 +131,8 @@ "textCodeBlock.background": "#F8F8F8", "textLink.activeForeground": "#005FB8", "textLink.foreground": "#005FB8", + "textPreformat.foreground": "#3B3B3B", + "textPreformat.background": "#0000001F", "textSeparator.foreground": "#21262D", "titleBar.activeBackground": "#F8F8F8", "titleBar.activeForeground": "#1E1E1E", diff --git a/extensions/typescript-language-features/src/languageFeatures/tsconfig.ts b/extensions/typescript-language-features/src/languageFeatures/tsconfig.ts index 34ed6828145..398c8c10ee2 100644 --- a/extensions/typescript-language-features/src/languageFeatures/tsconfig.ts +++ b/extensions/typescript-language-features/src/languageFeatures/tsconfig.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as jsonc from 'jsonc-parser'; -import { posix } from 'path'; +import { isAbsolute, posix } from 'path'; import * as vscode from 'vscode'; import { Utils } from 'vscode-uri'; import { coalesce } from '../utils/arrays'; @@ -95,6 +95,10 @@ class TsconfigLinkProvider implements vscode.DocumentLinkProvider { } private getFileTarget(document: vscode.TextDocument, node: jsonc.Node): vscode.Uri { + if (isAbsolute(node.value)) { + return vscode.Uri.file(node.value); + } + return vscode.Uri.joinPath(Utils.dirname(document.uri), node.value); } diff --git a/package.json b/package.json index 6301522d2f2..0fdbab3c9e3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.84.0", - "distro": "0f218422a902175f8b82cbf0f13fa4feb278f22a", + "distro": "b30d9687a6941b0d17b73334fc5a0f12590bff90", "author": { "name": "Microsoft Corporation" }, @@ -96,14 +96,14 @@ "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "9.0.0", - "xterm": "5.4.0-beta.31", - "xterm-addon-canvas": "0.6.0-beta.31", + "xterm": "5.4.0-beta.32", + "xterm-addon-canvas": "0.6.0-beta.32", "xterm-addon-image": "0.6.0-beta.21", - "xterm-addon-search": "0.14.0-beta.30", - "xterm-addon-serialize": "0.12.0-beta.30", - "xterm-addon-unicode11": "0.7.0-beta.30", - "xterm-addon-webgl": "0.17.0-beta.30", - "xterm-headless": "5.4.0-beta.31", + "xterm-addon-search": "0.14.0-beta.31", + "xterm-addon-serialize": "0.12.0-beta.31", + "xterm-addon-unicode11": "0.7.0-beta.31", + "xterm-addon-webgl": "0.17.0-beta.31", + "xterm-headless": "5.4.0-beta.32", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, diff --git a/remote/package.json b/remote/package.json index a144d3f85b2..dbb57afa152 100644 --- a/remote/package.json +++ b/remote/package.json @@ -26,14 +26,14 @@ "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "9.0.0", - "xterm": "5.4.0-beta.31", - "xterm-addon-canvas": "0.6.0-beta.31", + "xterm": "5.4.0-beta.32", + "xterm-addon-canvas": "0.6.0-beta.32", "xterm-addon-image": "0.6.0-beta.21", - "xterm-addon-search": "0.14.0-beta.30", - "xterm-addon-serialize": "0.12.0-beta.30", - "xterm-addon-unicode11": "0.7.0-beta.30", - "xterm-addon-webgl": "0.17.0-beta.30", - "xterm-headless": "5.4.0-beta.31", + "xterm-addon-search": "0.14.0-beta.31", + "xterm-addon-serialize": "0.12.0-beta.31", + "xterm-addon-unicode11": "0.7.0-beta.31", + "xterm-addon-webgl": "0.17.0-beta.31", + "xterm-headless": "5.4.0-beta.32", "yauzl": "^2.9.2", "yazl": "^2.4.3" } diff --git a/remote/web/package.json b/remote/web/package.json index 0fb5ee2bcfc..2634c6ff3b8 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -11,11 +11,11 @@ "tas-client-umd": "0.1.8", "vscode-oniguruma": "1.7.0", "vscode-textmate": "9.0.0", - "xterm": "5.4.0-beta.31", - "xterm-addon-canvas": "0.6.0-beta.31", + "xterm": "5.4.0-beta.32", + "xterm-addon-canvas": "0.6.0-beta.32", "xterm-addon-image": "0.6.0-beta.21", - "xterm-addon-search": "0.14.0-beta.30", - "xterm-addon-unicode11": "0.7.0-beta.30", - "xterm-addon-webgl": "0.17.0-beta.30" + "xterm-addon-search": "0.14.0-beta.31", + "xterm-addon-unicode11": "0.7.0-beta.31", + "xterm-addon-webgl": "0.17.0-beta.31" } } diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index bd6e5adf267..6eb7304ba80 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -68,32 +68,32 @@ vscode-textmate@9.0.0: resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-9.0.0.tgz#313c6c8792b0507aef35aeb81b6b370b37c44d6c" integrity sha512-Cl65diFGxz7gpwbav10HqiY/eVYTO1sjQpmRmV991Bj7wAoOAjGQ97PpQcXorDE2Uc4hnGWLY17xme+5t6MlSg== -xterm-addon-canvas@0.6.0-beta.31: - version "0.6.0-beta.31" - resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.6.0-beta.31.tgz#04ebde31c5e01b2595b966a2861deaec9927e1cb" - integrity sha512-/Dz90IF5FQqzAitKi3k/JEyyRMhSuQG8PVtB2NwOlWUcE3Ukp6gJMFdkyfOOt0Lx/8oyWR7xoDgKY3bxbzpkGQ== +xterm-addon-canvas@0.6.0-beta.32: + version "0.6.0-beta.32" + resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.6.0-beta.32.tgz#c9e74dd72fcc981a2e0cbd0b82827676bc5c74b9" + integrity sha512-Xw7oE4dbS+x+pu6cGW1bDSXcVviuorLz1OLaYw46jjmDezIqQIIEMhSMOprExFEWgeRQ9AEN4lPqw6aH87V74w== xterm-addon-image@0.6.0-beta.21: version "0.6.0-beta.21" resolved "https://registry.yarnpkg.com/xterm-addon-image/-/xterm-addon-image-0.6.0-beta.21.tgz#e3708bc504c56a23ff31f12a2eeb335331a92aac" integrity sha512-8/PTaXVPa4kQ0xzVeuZZk10OpbZBj2cgfwhM2B0ChSPvwrk0lX+ksnXdtDKH3tg+JYvo7fIhNXtkr4NwWt7VJQ== -xterm-addon-search@0.14.0-beta.30: - version "0.14.0-beta.30" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.14.0-beta.30.tgz#a84321ded127ab13a0bdbf901d2524900330f6ea" - integrity sha512-e5qb68lmpxQ1cG4oJKq9NC61oV2xGynRyruB2luerGeXPhqkGj9RSDeOqgCWbnQNTfBmkROzrn02MeJAsoqvGQ== +xterm-addon-search@0.14.0-beta.31: + version "0.14.0-beta.31" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.14.0-beta.31.tgz#933ca5d2d642dacad29f2cfbd50830cff83bc274" + integrity sha512-JRY1ukhoh32D0AMz78xpumQkLgkcP9d3GXj6gzVHZZsjLAMDaJYEubYq1bUhM7IGHUyg+x0sdRJyx7d6fJpiQg== -xterm-addon-unicode11@0.7.0-beta.30: - version "0.7.0-beta.30" - resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.7.0-beta.30.tgz#2de2c412d41823f31b66f68c7d8d0fb9e1a66cd3" - integrity sha512-pLSSBxwCOD5aShGnk6VveLHpjDwEDrIci2WnVcuWIbPaqHkB16d6l17jJ50843TaW66k1Np3ZCpDteOoC0Z6Kw== +xterm-addon-unicode11@0.7.0-beta.31: + version "0.7.0-beta.31" + resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.7.0-beta.31.tgz#abcba752172323f31312bd8a3f9b6a049dbca6e3" + integrity sha512-vvBKJbBoLbeIf2++6D16VnOOwevZE3nyO/PDZ7cyTJK1eYR73rr8ZbjUrH92YoTu4Z8MpZFepGQOgK/vlAQMwQ== -xterm-addon-webgl@0.17.0-beta.30: - version "0.17.0-beta.30" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.17.0-beta.30.tgz#e4d7b18eb8f2b0be6ee8bf35185e91b33570e67f" - integrity sha512-SjdfIOmx9xunom2Bk//iQ2DoqYlvAsunEWD3nxdED0oYYf1SPlKxt3I47YHWVshacw6QPZEJHVXJ6K+kHlel/Q== +xterm-addon-webgl@0.17.0-beta.31: + version "0.17.0-beta.31" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.17.0-beta.31.tgz#3cd29b4858e3f4f6dd5a8dd969454e85e1f43baa" + integrity sha512-vYHj+HlTcqUlFFVuoCTjlgh89/lIoSkZ7Nc87cwSFTrJsl07qoKutmpupqFXyjhbEA1fQY2SuQLx08Gmf2jWkQ== -xterm@5.4.0-beta.31: - version "5.4.0-beta.31" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.4.0-beta.31.tgz#598f66cfa49609d4e4935fbaf00aadff8e23d174" - integrity sha512-lAuiiWxxU8s0UaDwuJZupoBOtb9bY5ouBkOufnfpLK05ACm0046TPxs3bg05jPUI8y5y/qLgKqK0L5TxAiZ8WA== +xterm@5.4.0-beta.32: + version "5.4.0-beta.32" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.4.0-beta.32.tgz#1b4242cf1c0c1a5a1070da58d3f11956b537130a" + integrity sha512-mWTwEiNBFMF89oqVfi6qTM2Py5gC1Mwvslx1KxmI2Ukgh9v3CrqKDhj29eY1ZeAo0uuYknFWKyuexqp+3SHJCA== diff --git a/remote/yarn.lock b/remote/yarn.lock index 4b7185b4ffa..b14b646d31c 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -591,45 +591,45 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -xterm-addon-canvas@0.6.0-beta.31: - version "0.6.0-beta.31" - resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.6.0-beta.31.tgz#04ebde31c5e01b2595b966a2861deaec9927e1cb" - integrity sha512-/Dz90IF5FQqzAitKi3k/JEyyRMhSuQG8PVtB2NwOlWUcE3Ukp6gJMFdkyfOOt0Lx/8oyWR7xoDgKY3bxbzpkGQ== +xterm-addon-canvas@0.6.0-beta.32: + version "0.6.0-beta.32" + resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.6.0-beta.32.tgz#c9e74dd72fcc981a2e0cbd0b82827676bc5c74b9" + integrity sha512-Xw7oE4dbS+x+pu6cGW1bDSXcVviuorLz1OLaYw46jjmDezIqQIIEMhSMOprExFEWgeRQ9AEN4lPqw6aH87V74w== xterm-addon-image@0.6.0-beta.21: version "0.6.0-beta.21" resolved "https://registry.yarnpkg.com/xterm-addon-image/-/xterm-addon-image-0.6.0-beta.21.tgz#e3708bc504c56a23ff31f12a2eeb335331a92aac" integrity sha512-8/PTaXVPa4kQ0xzVeuZZk10OpbZBj2cgfwhM2B0ChSPvwrk0lX+ksnXdtDKH3tg+JYvo7fIhNXtkr4NwWt7VJQ== -xterm-addon-search@0.14.0-beta.30: - version "0.14.0-beta.30" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.14.0-beta.30.tgz#a84321ded127ab13a0bdbf901d2524900330f6ea" - integrity sha512-e5qb68lmpxQ1cG4oJKq9NC61oV2xGynRyruB2luerGeXPhqkGj9RSDeOqgCWbnQNTfBmkROzrn02MeJAsoqvGQ== +xterm-addon-search@0.14.0-beta.31: + version "0.14.0-beta.31" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.14.0-beta.31.tgz#933ca5d2d642dacad29f2cfbd50830cff83bc274" + integrity sha512-JRY1ukhoh32D0AMz78xpumQkLgkcP9d3GXj6gzVHZZsjLAMDaJYEubYq1bUhM7IGHUyg+x0sdRJyx7d6fJpiQg== -xterm-addon-serialize@0.12.0-beta.30: - version "0.12.0-beta.30" - resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.12.0-beta.30.tgz#80c4101f45a463ef139705bcd3dcaf0811f51ea4" - integrity sha512-nZP0ip5bd9LBoCTN9vCnn4iLatF4RRwzLupQf9r2N9x1bULzTZ1kAXAQe5gghsXjSEDDtyY2LzGigqTd2KVAqQ== +xterm-addon-serialize@0.12.0-beta.31: + version "0.12.0-beta.31" + resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.12.0-beta.31.tgz#2a95dc1e12f4097e2894b04c9cb8fff0bc0b858c" + integrity sha512-h2rWR+Lfi1Iv4VkLUlrBMYh5Mdq8vux2BKyCJe6a1ZnEu5Dzb0VuiNxfTKXTCT5M83nMn7TCB9TX0E8z6bs7xw== -xterm-addon-unicode11@0.7.0-beta.30: - version "0.7.0-beta.30" - resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.7.0-beta.30.tgz#2de2c412d41823f31b66f68c7d8d0fb9e1a66cd3" - integrity sha512-pLSSBxwCOD5aShGnk6VveLHpjDwEDrIci2WnVcuWIbPaqHkB16d6l17jJ50843TaW66k1Np3ZCpDteOoC0Z6Kw== +xterm-addon-unicode11@0.7.0-beta.31: + version "0.7.0-beta.31" + resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.7.0-beta.31.tgz#abcba752172323f31312bd8a3f9b6a049dbca6e3" + integrity sha512-vvBKJbBoLbeIf2++6D16VnOOwevZE3nyO/PDZ7cyTJK1eYR73rr8ZbjUrH92YoTu4Z8MpZFepGQOgK/vlAQMwQ== -xterm-addon-webgl@0.17.0-beta.30: - version "0.17.0-beta.30" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.17.0-beta.30.tgz#e4d7b18eb8f2b0be6ee8bf35185e91b33570e67f" - integrity sha512-SjdfIOmx9xunom2Bk//iQ2DoqYlvAsunEWD3nxdED0oYYf1SPlKxt3I47YHWVshacw6QPZEJHVXJ6K+kHlel/Q== +xterm-addon-webgl@0.17.0-beta.31: + version "0.17.0-beta.31" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.17.0-beta.31.tgz#3cd29b4858e3f4f6dd5a8dd969454e85e1f43baa" + integrity sha512-vYHj+HlTcqUlFFVuoCTjlgh89/lIoSkZ7Nc87cwSFTrJsl07qoKutmpupqFXyjhbEA1fQY2SuQLx08Gmf2jWkQ== -xterm-headless@5.4.0-beta.31: - version "5.4.0-beta.31" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.4.0-beta.31.tgz#9538553c7426222f94d7da7ed467e699ebaeeedd" - integrity sha512-EE/ZlsZcBE5VOkjQU/KdRL4gvSkfrC2P7VxrmK1+PLc6+QMjPxs60A4Pun3mIIS0MFfN23p6hmN22GAXVckCXA== +xterm-headless@5.4.0-beta.32: + version "5.4.0-beta.32" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.4.0-beta.32.tgz#0d5cd35e1a0372888055ff0b06dfe17457979a6c" + integrity sha512-DQduq8KSoQZyRrQAFB+FkcY2UMxCW39P1/duOpksebc6PT9pbGkyPe5s+AdUQGiYzriEpzVtKUzDcquoVmpPhA== -xterm@5.4.0-beta.31: - version "5.4.0-beta.31" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.4.0-beta.31.tgz#598f66cfa49609d4e4935fbaf00aadff8e23d174" - integrity sha512-lAuiiWxxU8s0UaDwuJZupoBOtb9bY5ouBkOufnfpLK05ACm0046TPxs3bg05jPUI8y5y/qLgKqK0L5TxAiZ8WA== +xterm@5.4.0-beta.32: + version "5.4.0-beta.32" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.4.0-beta.32.tgz#1b4242cf1c0c1a5a1070da58d3f11956b537130a" + integrity sha512-mWTwEiNBFMF89oqVfi6qTM2Py5gC1Mwvslx1KxmI2Ukgh9v3CrqKDhj29eY1ZeAo0uuYknFWKyuexqp+3SHJCA== yallist@^4.0.0: version "4.0.0" diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index d8302103599..024e9988544 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -602,14 +602,7 @@ export function getLargestChildWidth(parent: HTMLElement, children: HTMLElement[ // ---------------------------------------------------------------------------------------- export function isAncestor(testChild: Node | null, testAncestor: Node | null): boolean { - while (testChild) { - if (testChild === testAncestor) { - return true; - } - testChild = testChild.parentNode; - } - - return false; + return Boolean(testAncestor?.contains(testChild)); } const parentFlowToDataKey = 'parentFlowToElementId'; diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index fb758311a1a..147ea68d2ab 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -165,10 +165,6 @@ export class CodeApplication extends Disposable { const isUrlFromWebview = (requestingUrl: string | undefined) => requestingUrl?.startsWith(`${Schemas.vscodeWebview}://`); - const allowedPermissionsInMainFrame = new Set( - this.productService.quality === 'stable' ? [] : ['media'] - ); - const allowedPermissionsInWebview = new Set([ 'clipboard-read', 'clipboard-sanitized-write', @@ -179,10 +175,6 @@ export class CodeApplication extends Disposable { return callback(allowedPermissionsInWebview.has(permission)); } - if (details.isMainFrame && details.securityOrigin === `${Schemas.vscodeFileResource}://${VSCODE_AUTHORITY}/`) { - return callback(allowedPermissionsInMainFrame.has(permission)); - } - return callback(false); }); @@ -191,10 +183,6 @@ export class CodeApplication extends Disposable { return allowedPermissionsInWebview.has(permission); } - if (details.isMainFrame && details.securityOrigin === `${Schemas.vscodeFileResource}://${VSCODE_AUTHORITY}/`) { - return allowedPermissionsInMainFrame.has(permission); - } - return false; }); diff --git a/src/vs/code/node/sharedProcess/contrib/voiceTranscriber.ts b/src/vs/code/node/sharedProcess/contrib/voiceTranscriber.ts deleted file mode 100644 index 6f29b8d3a23..00000000000 --- a/src/vs/code/node/sharedProcess/contrib/voiceTranscriber.ts +++ /dev/null @@ -1,212 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Event } from 'vs/base/common/event'; -import { MessagePortMain, MessageEvent } from 'vs/base/parts/sandbox/node/electronTypes'; -import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IVoiceRecognitionService } from 'vs/platform/voiceRecognition/node/voiceRecognitionService'; -import { ILogService } from 'vs/platform/log/common/log'; -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { LimitedQueue, Queue } from 'vs/base/common/async'; - -export class VoiceTranscriptionManager extends Disposable { - - private static USE_SLIDING_WINDOW = !!process.env.VSCODE_VOICE_USE_SLIDING_WINDOW; - - constructor( - private readonly onDidWindowConnectRaw: Event, - @IVoiceRecognitionService private readonly voiceRecognitionService: IVoiceRecognitionService, - @ILogService private readonly logService: ILogService - ) { - super(); - - this.registerListeners(); - } - - private registerListeners(): void { - this._register(this.onDidWindowConnectRaw(port => { - this.logService.info(`[voice] transcriber: new connection (sliding window: ${VoiceTranscriptionManager.USE_SLIDING_WINDOW})`); - - if (VoiceTranscriptionManager.USE_SLIDING_WINDOW) { - this._register(new SlidingWindowVoiceTranscriber(port, this.voiceRecognitionService, this.logService)); - } else { - this._register(new FullWindowVoiceTranscriber(port, this.voiceRecognitionService, this.logService)); - } - })); - } -} - -abstract class VoiceTranscriber extends Disposable { - - protected static MAX_DATA_LENGTH = 30 /* seconds */ * 16000 /* sampling rate */ * 16 /* bith depth */ * 1 /* channels */ / 8; - - constructor( - protected readonly port: MessagePortMain, - protected readonly voiceRecognitionService: IVoiceRecognitionService, - protected readonly logService: ILogService - ) { - super(); - - this.registerListeners(); - } - - private registerListeners(): void { - const cts = new CancellationTokenSource(); - this._register(toDisposable(() => cts.dispose(true))); - - const requestHandler = (e: MessageEvent) => { - if (!(e.data instanceof Float32Array)) { - return; - } - - this.handleRequest(e.data, cts.token); - }; - this.port.on('message', requestHandler); - this._register(toDisposable(() => this.port.off('message', requestHandler))); - - this.port.start(); - - let closed = false; - this.port.on('close', () => { - this.logService.info(`[voice] transcriber: closed connection`); - - closed = true; - this.dispose(); - }); - - this._register(toDisposable(() => { - if (!closed) { - this.port.close(); - } - })); - } - - protected abstract handleRequest(data: Float32Array, cancellation: CancellationToken): Promise; - - protected joinFloat32Arrays(float32Arrays: Float32Array[]): Float32Array { - const result = new Float32Array(float32Arrays.reduce((prev, curr) => prev + curr.length, 0)); - - let offset = 0; - for (const float32Array of float32Arrays) { - result.set(float32Array, offset); - offset += float32Array.length; - } - - return result; - } -} - -class SlidingWindowVoiceTranscriber extends VoiceTranscriber { - - private readonly transcriptionQueue = this._register(new Queue()); - - private transcribedResults: string[] = []; - private data: Float32Array = new Float32Array(0); - - protected async handleRequest(data: Float32Array, cancellation: CancellationToken): Promise { - if (data.length > 0) { - this.logService.info(`[voice] transcriber: voice detected, storing in buffer`); - - this.data = this.data ? this.joinFloat32Arrays([this.data, data]) : data; - } else { - this.logService.info(`[voice] transcriber: silence detected, transcribing window...`); - - const data = this.data.slice(0); - this.data = new Float32Array(0); - - this.transcriptionQueue.queue(() => this.transcribe(data, cancellation)); - } - } - - private async transcribe(data: Float32Array, cancellation: CancellationToken): Promise { - if (cancellation.isCancellationRequested) { - return; - } - - if (data.length > VoiceTranscriber.MAX_DATA_LENGTH) { - this.logService.warn(`[voice] transcriber: refusing to accept more than 30s of audio data`); - return; - } - - if (data.length !== 0) { - const result = await this.voiceRecognitionService.transcribe(data, cancellation); - if (result) { - this.transcribedResults.push(result); - } - } - - if (cancellation.isCancellationRequested) { - return; - } - - this.port.postMessage(this.transcribedResults.join(' ')); - } - - override dispose(): void { - super.dispose(); - - this.data = new Float32Array(0); - } -} - -class FullWindowVoiceTranscriber extends VoiceTranscriber { - - private readonly transcriptionQueue = new LimitedQueue(); - - private data: Float32Array | undefined = undefined; - - private transcribedDataLength = 0; - private transcribedResult = ''; - - protected async handleRequest(data: Float32Array, cancellation: CancellationToken): Promise { - const dataCandidate = this.data ? this.joinFloat32Arrays([this.data, data]) : data; - if (dataCandidate.length > VoiceTranscriber.MAX_DATA_LENGTH) { - this.logService.warn(`[voice] transcriber: refusing to accept more than 30s of audio data`); - return; - } - - this.data = dataCandidate; - - this.transcriptionQueue.queue(() => this.transcribe(cancellation)); - } - - private async transcribe(cancellation: CancellationToken): Promise { - if (cancellation.isCancellationRequested) { - return; - } - - const data = this.data?.slice(0); - if (!data) { - return; - } - - let result: string; - if (data.length === this.transcribedDataLength) { - // Optimization: if the data is the same as the last time - // we transcribed, don't transcribe again, just return the - // same result as we had last time. - this.logService.info(`[voice] transcriber: silence detected, reusing previous transcription result`); - result = this.transcribedResult; - } else { - this.logService.info(`[voice] transcriber: voice detected, transcribing everything...`); - result = await this.voiceRecognitionService.transcribe(data, cancellation); - } - - this.transcribedResult = result; - this.transcribedDataLength = data.length; - - if (cancellation.isCancellationRequested) { - return; - } - - this.port.postMessage(result); - } - - override dispose(): void { - super.dispose(); - - this.data = undefined; - } -} diff --git a/src/vs/code/node/sharedProcess/sharedProcessMain.ts b/src/vs/code/node/sharedProcess/sharedProcessMain.ts index 557f8b6a6fe..84d080486a3 100644 --- a/src/vs/code/node/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/node/sharedProcess/sharedProcessMain.ts @@ -114,8 +114,6 @@ import { IRemoteSocketFactoryService, RemoteSocketFactoryService } from 'vs/plat import { RemoteConnectionType } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { nodeSocketFactory } from 'vs/platform/remote/node/nodeSocketFactory'; import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; -import { IVoiceRecognitionService, VoiceRecognitionService } from 'vs/platform/voiceRecognition/node/voiceRecognitionService'; -import { VoiceTranscriptionManager } from 'vs/code/node/sharedProcess/contrib/voiceTranscriber'; import { SharedProcessRawConnection, SharedProcessLifecycle } from 'vs/platform/sharedProcess/common/sharedProcess'; class SharedProcessMain extends Disposable implements IClientConnectionFilter { @@ -183,8 +181,7 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter { instantiationService.createInstance(LogsDataCleaner), instantiationService.createInstance(LocalizationsUpdater), instantiationService.createInstance(ExtensionsContributions), - instantiationService.createInstance(UserDataProfilesCleaner), - instantiationService.createInstance(VoiceTranscriptionManager, this.onDidWindowConnectRaw.event) + instantiationService.createInstance(UserDataProfilesCleaner) )); } @@ -367,9 +364,6 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter { // Remote Tunnel services.set(IRemoteTunnelService, new SyncDescriptor(RemoteTunnelService)); - // Voice Recognition - services.set(IVoiceRecognitionService, new SyncDescriptor(VoiceRecognitionService)); - return new InstantiationService(services); } diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 165a2ed3636..2dafc74b618 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -69,6 +69,7 @@ export class MenuId { static readonly EmptyEditorGroup = new MenuId('EmptyEditorGroup'); static readonly EmptyEditorGroupContext = new MenuId('EmptyEditorGroupContext'); static readonly EditorTabsBarContext = new MenuId('EditorTabsBarContext'); + static readonly EditorTabsBarShowTabsSubmenu = new MenuId('EditorTabsBarShowTabsSubmenu'); static readonly ExplorerContext = new MenuId('ExplorerContext'); static readonly ExplorerContextShare = new MenuId('ExplorerContextShare'); static readonly ExtensionContext = new MenuId('ExtensionContext'); diff --git a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts index 7372f366459..66defd005d1 100644 --- a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts +++ b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts @@ -19,7 +19,6 @@ import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtil import { parseSharedProcessDebugPort } from 'vs/platform/environment/node/environmentService'; import { assertIsDefined } from 'vs/base/common/types'; import { SharedProcessChannelConnection, SharedProcessRawConnection, SharedProcessLifecycle } from 'vs/platform/sharedProcess/common/sharedProcess'; -import { IProductService } from 'vs/platform/product/common/productService'; export class SharedProcess extends Disposable { @@ -35,8 +34,7 @@ export class SharedProcess extends Disposable { @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @ILogService private readonly logService: ILogService, @ILoggerMainService private readonly loggerMainService: ILoggerMainService, - @IPolicyService private readonly policyService: IPolicyService, - @IProductService private readonly productService: IProductService + @IPolicyService private readonly policyService: IPolicyService ) { super(); @@ -165,8 +163,7 @@ export class SharedProcess extends Disposable { type: 'shared-process', entryPoint: 'vs/code/node/sharedProcess/sharedProcessMain', payload: this.createSharedProcessConfiguration(), - execArgv, - allowLoadingUnsignedLibraries: !!process.env.VSCODE_VOICE_MODULE_PATH && this.productService.quality !== 'stable' // TODO@bpasero package + execArgv }); } diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 9f66d2cd1a5..6f83635a148 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -227,8 +227,9 @@ export const selectionBackground = registerColor('selection.background', { light export const textSeparatorForeground = registerColor('textSeparator.foreground', { light: '#0000002e', dark: '#ffffff2e', hcDark: Color.black, hcLight: '#292929' }, nls.localize('textSeparatorForeground', "Color for text separators.")); export const textLinkForeground = registerColor('textLink.foreground', { light: '#006AB1', dark: '#3794FF', hcDark: '#3794FF', hcLight: '#0F4A85' }, nls.localize('textLinkForeground', "Foreground color for links in text.")); export const textLinkActiveForeground = registerColor('textLink.activeForeground', { light: '#006AB1', dark: '#3794FF', hcDark: '#3794FF', hcLight: '#0F4A85' }, nls.localize('textLinkActiveForeground', "Foreground color for links in text when clicked on and on mouse hover.")); -export const textPreformatForeground = registerColor('textPreformat.foreground', { light: '#A31515', dark: '#D7BA7D', hcDark: '#D7BA7D', hcLight: '#292929' }, nls.localize('textPreformatForeground', "Foreground color for preformatted text segments.")); -export const textBlockQuoteBackground = registerColor('textBlockQuote.background', { light: '#7f7f7f1a', dark: '#7f7f7f1a', hcDark: null, hcLight: '#F2F2F2' }, nls.localize('textBlockQuoteBackground', "Background color for block quotes in text.")); +export const textPreformatForeground = registerColor('textPreformat.foreground', { light: '#A31515', dark: '#D7BA7D', hcDark: '#000000', hcLight: '#FFFFFF' }, nls.localize('textPreformatForeground', "Foreground color for preformatted text segments.")); +export const textPreformatBackground = registerColor('textPreformat.background', { light: '#0000001A', dark: '#FFFFFF1A', hcDark: '#FFFFFF', hcLight: '#09345f' }, nls.localize('textPreformatBackground', "Background color for preformatted text segments.")); +export const textBlockQuoteBackground = registerColor('textBlockQuote.background', { light: '#f2f2f2', dark: '#222222', hcDark: null, hcLight: '#F2F2F2' }, nls.localize('textBlockQuoteBackground', "Background color for block quotes in text.")); export const textBlockQuoteBorder = registerColor('textBlockQuote.border', { light: '#007acc80', dark: '#007acc80', hcDark: Color.white, hcLight: '#292929' }, nls.localize('textBlockQuoteBorder', "Border color for block quotes in text.")); export const textCodeBlockBackground = registerColor('textCodeBlock.background', { light: '#dcdcdc66', dark: '#0a0a0a66', hcDark: Color.black, hcLight: '#F2F2F2' }, nls.localize('textCodeBlockBackground', "Background color for code blocks in text.")); diff --git a/src/vs/platform/voiceRecognition/node/voiceRecognitionService.ts b/src/vs/platform/voiceRecognition/node/voiceRecognitionService.ts deleted file mode 100644 index 03f7a16ca95..00000000000 --- a/src/vs/platform/voiceRecognition/node/voiceRecognitionService.ts +++ /dev/null @@ -1,81 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { CancellationToken } from 'vs/base/common/cancellation'; -import { ILogService } from 'vs/platform/log/common/log'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IProductService } from 'vs/platform/product/common/productService'; - -export const IVoiceRecognitionService = createDecorator('voiceRecognitionService'); - -export interface IVoiceRecognitionService { - - readonly _serviceBrand: undefined; - - /** - * Given a buffer of audio data, attempts to - * transcribe the spoken words into text. - * - * @param channelData the raw audio data obtained - * from the microphone as uncompressed PCM data: - * - 1 channel (mono) - * - 16khz sampling rate - * - 16bit sample size - */ - transcribe(channelData: Float32Array, cancellation: CancellationToken): Promise; -} - -export class VoiceRecognitionService implements IVoiceRecognitionService { - - declare readonly _serviceBrand: undefined; - - constructor( - @ILogService private readonly logService: ILogService, - @IProductService private readonly productService: IProductService - ) { } - - async transcribe(channelData: Float32Array, cancellation: CancellationToken): Promise { - const modulePath = process.env.VSCODE_VOICE_MODULE_PATH; // TODO@bpasero package - if (!modulePath || this.productService.quality === 'stable') { - this.logService.error(`[voice] transcribe(${channelData.length}): Voice recognition not yet supported`); - throw new Error('Voice recognition not yet supported!'); - } - - const now = Date.now(); - - try { - const voiceModule: { - transcribe: ( - audioBuffer: { channelCount: 1; samplingRate: 16000; bitDepth: 16; channelData: Float32Array }, - options: { - language: string | 'auto'; - signal: AbortSignal; - } - ) => Promise; - } = require.__$__nodeRequire(modulePath); - - const abortController = new AbortController(); - cancellation.onCancellationRequested(() => abortController.abort()); - - const text = await voiceModule.transcribe({ - samplingRate: 16000, - bitDepth: 16, - channelCount: 1, - channelData - }, { - language: 'en', - signal: abortController.signal - }); - - this.logService.info(`[voice] transcribe(${channelData.length}): Text "${text}", took ${Date.now() - now}ms)`); - - return text; - } catch (error) { - this.logService.error(`[voice] transcribe(${channelData.length}): Failed width "${error}", took ${Date.now() - now}ms)`); - - throw error; - } - } -} diff --git a/src/vs/platform/window/electron-main/window.ts b/src/vs/platform/window/electron-main/window.ts index 8330d6f39a5..379bdd161f6 100644 --- a/src/vs/platform/window/electron-main/window.ts +++ b/src/vs/platform/window/electron-main/window.ts @@ -13,7 +13,11 @@ import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataPro import { INativeWindowConfiguration } from 'vs/platform/window/common/window'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; -export interface ICodeWindow extends IDisposable { +export interface IBaseWindow extends IDisposable { + focus(options?: { force: boolean }): void; +} + +export interface ICodeWindow extends IBaseWindow { readonly onWillLoad: Event; readonly onDidSignalReady: Event; @@ -49,7 +53,6 @@ export interface ICodeWindow extends IDisposable { load(config: INativeWindowConfiguration, options?: { isReload?: boolean }): void; reload(cli?: NativeParsedArgs): void; - focus(options?: { force: boolean }): void; close(): void; getBounds(): Rectangle; diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts index da1c92d03b6..32168a31b7e 100644 --- a/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/src/vs/platform/windows/electron-main/windowImpl.ts @@ -35,7 +35,7 @@ import { getMenuBarVisibility, getTitleBarStyle, IFolderToOpen, INativeWindowCon import { defaultBrowserWindowOptions, IWindowsMainService, OpenContext } from 'vs/platform/windows/electron-main/windows'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, toWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; -import { IWindowState, ICodeWindow, ILoadEvent, WindowMode, WindowError, LoadReason, defaultWindowState } from 'vs/platform/window/electron-main/window'; +import { IWindowState, ICodeWindow, ILoadEvent, WindowMode, WindowError, LoadReason, defaultWindowState, IBaseWindow } from 'vs/platform/window/electron-main/window'; import { Color } from 'vs/base/common/color'; import { IPolicyService } from 'vs/platform/policy/common/policy'; import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; @@ -82,7 +82,7 @@ const enum ReadyState { READY } -export abstract class BaseWindow extends Disposable { +export abstract class BaseWindow extends Disposable implements IBaseWindow { protected abstract getWin(): BrowserWindow | null; diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index a80992df95d..07a10a92e9c 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -8,7 +8,6 @@ import { IMarkdownString } from 'vs/base/common/htmlContent'; import { Disposable, DisposableMap } from 'vs/base/common/lifecycle'; import { revive } from 'vs/base/common/marshalling'; import { UriComponents } from 'vs/base/common/uri'; -import { generateUuid } from 'vs/base/common/uuid'; import { ExtHostChatAgentsShape2, ExtHostContext, IChatResponseProgressDto, IChatResponseProgressFileTreeData, IExtensionChatAgentMetadata, ILocationDto, MainContext, MainThreadChatAgentsShape2 } from 'vs/workbench/api/common/extHost.protocol'; import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { isCompleteInteractiveProgressTreeData } from 'vs/workbench/contrib/chat/common/chatModel'; @@ -44,13 +43,13 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA this._proxy.$releaseSession(e.sessionId); })); this._register(this._chatService.onDidPerformUserAction(e => { - if (e.agentId) { + if (typeof e.agentId === 'string') { 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); + this._proxy.$acceptFeedback(handle, e.sessionId, e.requestId, e.action.direction); } else { - this._proxy.$acceptAction(handle, e.sessionId, e); + this._proxy.$acceptAction(handle, e.sessionId, e.requestId, e); } break; } @@ -68,12 +67,11 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA id: name, metadata: revive(metadata), invoke: async (request, progress, history, token) => { - const requestId = generateUuid(); - this._pendingProgress.set(requestId, progress); + this._pendingProgress.set(request.requestId, progress); try { - return await this._proxy.$invokeAgent(handle, request.sessionId, requestId, request, { history }, token) ?? {}; + return await this._proxy.$invokeAgent(handle, request.sessionId, request.requestId, request, { history }, token) ?? {}; } finally { - this._pendingProgress.delete(requestId); + this._pendingProgress.delete(request.requestId); } }, provideFollowups: async (sessionId, token): Promise => { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 969de0075e2..674cb4bb2a4 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1185,8 +1185,8 @@ export interface ExtHostChatAgentsShape2 { $invokeAgent(handle: number, sessionId: string, requestId: string, 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; + $acceptFeedback(handle: number, sessionId: string, requestId: string, vote: InteractiveSessionVoteDirection): void; + $acceptAction(handle: number, sessionId: string, requestId: string, action: IChatUserActionEvent): void; $releaseSession(sessionId: string): void; } diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index c202d7b1031..4044bb79786 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -30,6 +30,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { private readonly _proxy: MainThreadChatAgentsShape2; private readonly _previousResultMap: Map = new Map(); + private readonly _resultsBySessionAndRequestId: Map> = new Map(); constructor( mainContext: IMainContext, @@ -49,6 +50,10 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { } async $invokeAgent(handle: number, sessionId: string, requestId: string, request: IChatAgentRequest, context: { history: IChatMessage[] }, token: CancellationToken): Promise { + // Clear the previous result so that $acceptFeedback or $acceptAction during a request will be ignored. + // We may want to support sending those during a request. + this._previousResultMap.delete(sessionId); + const agent = this._agents.get(handle); if (!agent) { throw new Error(`[CHAT](${handle}) CANNOT invoke agent because the agent is not registered`); @@ -100,6 +105,13 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { return await raceCancellation(Promise.resolve(task).then((result) => { if (result) { this._previousResultMap.set(sessionId, result); + let sessionResults = this._resultsBySessionAndRequestId.get(sessionId); + if (!sessionResults) { + sessionResults = new Map(); + this._resultsBySessionAndRequestId.set(sessionId, sessionResults); + } + sessionResults.set(requestId, result); + return { errorDetails: result.errorDetails }; // TODO timings here } else { this._previousResultMap.delete(sessionId); @@ -124,6 +136,7 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { $releaseSession(sessionId: string): void { this._previousResultMap.delete(sessionId); + this._resultsBySessionAndRequestId.delete(sessionId); } async $provideSlashCommands(handle: number, token: CancellationToken): Promise { @@ -149,12 +162,12 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { return agent.provideFollowups(result, token); } - $acceptFeedback(handle: number, sessionId: string, vote: InteractiveSessionVoteDirection): void { + $acceptFeedback(handle: number, sessionId: string, requestId: string, vote: InteractiveSessionVoteDirection): void { const agent = this._agents.get(handle); if (!agent) { return; } - const result = this._previousResultMap.get(sessionId); + const result = this._resultsBySessionAndRequestId.get(sessionId)?.get(requestId); if (!result) { return; } @@ -171,12 +184,12 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 { agent.acceptFeedback(Object.freeze({ result, kind })); } - $acceptAction(handle: number, sessionId: string, action: IChatUserActionEvent): void { + $acceptAction(handle: number, sessionId: string, requestId: string, action: IChatUserActionEvent): void { const agent = this._agents.get(handle); if (!agent) { return; } - const result = this._previousResultMap.get(sessionId); + const result = this._resultsBySessionAndRequestId.get(sessionId)?.get(requestId); if (!result) { return; } @@ -302,7 +315,6 @@ class ExtHostChatAgent { that._iconPath = v; updateMetadataSoon(); }, - // onDidPerformAction get slashCommandProvider() { return that._slashCommandProvider; }, diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index c39144529d6..853891af351 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -470,49 +470,87 @@ export class ToggleStatusbarVisibilityAction extends Action2 { registerAction2(ToggleStatusbarVisibilityAction); -// --- Bse Class Toggle Boolean Setting Action +// --- Hide Editor Tabs -abstract class BaseToggleBooleanSettingAction extends Action2 { +export class HideEditorTabsAction extends Action2 { - protected abstract get settingId(): string; - - override run(accessor: ServicesAccessor): Promise { - const configurationService = accessor.get(IConfigurationService); - - const oldettingValue = configurationService.getValue(this.settingId); - const newSettingValue = !oldettingValue; - - return configurationService.updateValue(this.settingId, newSettingValue); - } -} - -// --- Toggle Tabs Visibility - -export class ToggleTabsVisibilityAction extends BaseToggleBooleanSettingAction { - - static readonly ID = 'workbench.action.toggleTabsVisibility'; + static readonly ID = 'workbench.action.hideEditorTabs'; constructor() { super({ - id: ToggleTabsVisibilityAction.ID, + id: HideEditorTabsAction.ID, title: { - value: localize('toggleTabs', "Toggle Editor Tab Visibility"), - original: 'Toggle Editor Tab Visibility' + value: localize('hideEditorTabs', "Hide Editor Tabs"), + original: 'Hide Editor Tabs' }, category: Categories.View, + precondition: ContextKeyExpr.equals('config.workbench.editor.showTabs', 'none').negate(), f1: true }); } - protected override get settingId(): string { - return 'workbench.editor.showTabs'; + override run(accessor: ServicesAccessor): Promise { + const configurationService = accessor.get(IConfigurationService); + return configurationService.updateValue('workbench.editor.showTabs', 'none'); } } -registerAction2(ToggleTabsVisibilityAction); +registerAction2(HideEditorTabsAction); + +// --- Show Multiple Editor Tabs + +export class ShowMultipleEditorTabsAction extends Action2 { + + static readonly ID = 'workbench.action.showMultipleEditorTabs'; + + constructor() { + super({ + id: ShowMultipleEditorTabsAction.ID, + title: { + value: localize('showMultipleEditorTabs', "Show Multiple Editor Tabs"), + original: 'Show Multiple Editor Tabs' + }, + category: Categories.View, + precondition: ContextKeyExpr.equals('config.workbench.editor.showTabs', 'multiple').negate(), + f1: true + }); + } + + override run(accessor: ServicesAccessor): Promise { + const configurationService = accessor.get(IConfigurationService); + return configurationService.updateValue('workbench.editor.showTabs', 'multiple'); + } +} +registerAction2(ShowMultipleEditorTabsAction); + +// --- Show Single Editor Tab + +export class ShowSingleEditorTabAction extends Action2 { + + static readonly ID = 'workbench.action.showEditorTab'; + + constructor() { + super({ + id: ShowSingleEditorTabAction.ID, + title: { + value: localize('showSingleEditorTab', "Show Single Editor Tab"), + original: 'Show Single Editor Tab' + }, + category: Categories.View, + precondition: ContextKeyExpr.equals('config.workbench.editor.showTabs', 'single').negate(), + f1: true + }); + } + + override run(accessor: ServicesAccessor): Promise { + const configurationService = accessor.get(IConfigurationService); + return configurationService.updateValue('workbench.editor.showTabs', 'single'); + } +} +registerAction2(ShowSingleEditorTabAction); // --- Toggle Pinned Tabs On Separate Row -export class ToggleSeparatePinnedTabsAction extends BaseToggleBooleanSettingAction { +export class ToggleSeparatePinnedTabsAction extends Action2 { static readonly ID = 'workbench.action.toggleSeparatePinnedEditorTabs'; @@ -524,13 +562,18 @@ export class ToggleSeparatePinnedTabsAction extends BaseToggleBooleanSettingActi original: 'Separate Pinned Editor Tabs' }, category: Categories.View, - precondition: ContextKeyExpr.has('config.workbench.editor.showTabs'), + precondition: ContextKeyExpr.equals('config.workbench.editor.showTabs', 'multiple'), f1: true }); } - protected override get settingId(): string { - return 'workbench.editor.pinnedTabsOnSeparateRow'; + override run(accessor: ServicesAccessor): Promise { + const configurationService = accessor.get(IConfigurationService); + + const oldettingValue = configurationService.getValue('workbench.editor.pinnedTabsOnSeparateRow'); + const newSettingValue = !oldettingValue; + + return configurationService.updateValue('workbench.editor.pinnedTabsOnSeparateRow', newSettingValue); } } registerAction2(ToggleSeparatePinnedTabsAction); diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index e3acfe3755a..fcddfd142d8 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -271,7 +271,7 @@ export class WorkbenchContextKeysHandler extends Disposable { } private updateEditorAreaContextKeys(): void { - this.editorTabsVisibleContext.set(!!this.editorGroupService.partOptions.showTabs); + this.editorTabsVisibleContext.set(this.editorGroupService.partOptions.showTabs === 'multiple'); } private updateEditorContextKeys(): void { diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 2708caa9350..18c41696437 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -5,7 +5,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; -import { EventType, addDisposableListener, getClientArea, Dimension, position, size, IDimension, isAncestorUsingFlowTo, computeScreenAwareSize, getActiveDocument, getWindows } from 'vs/base/browser/dom'; +import { EventType, addDisposableListener, getClientArea, Dimension, position, size, IDimension, isAncestorUsingFlowTo, computeScreenAwareSize, getActiveDocument, getWindows, getActiveWindow } from 'vs/base/browser/dom'; import { onDidChangeFullscreen, isFullscreen, isWCOEnabled } from 'vs/base/browser/browser'; import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; import { isWindows, isLinux, isMacintosh, isWeb, isNative, isIOS } from 'vs/base/common/platform'; @@ -1238,8 +1238,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.state.runtime.zenMode.transitionDisposables.add(this.editorService.onDidVisibleEditorsChange(() => setLineNumbers('off'))); } - if (config.hideTabs && this.editorGroupService.partOptions.showTabs) { - this.state.runtime.zenMode.transitionDisposables.add(this.editorGroupService.enforcePartOptions({ showTabs: false })); + if (config.showTabs !== this.editorGroupService.partOptions.showTabs) { + this.state.runtime.zenMode.transitionDisposables.add(this.editorGroupService.enforcePartOptions({ showTabs: config.showTabs })); } if (config.silentNotifications && zenModeExitInfo.handleNotificationsDoNotDisturbMode) { @@ -1463,8 +1463,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } resizePart(part: Parts, sizeChangeWidth: number, sizeChangeHeight: number): void { - const sizeChangePxWidth = Math.sign(sizeChangeWidth) * computeScreenAwareSize(window, Math.abs(sizeChangeWidth)); - const sizeChangePxHeight = Math.sign(sizeChangeHeight) * computeScreenAwareSize(window, Math.abs(sizeChangeHeight)); + const sizeChangePxWidth = Math.sign(sizeChangeWidth) * computeScreenAwareSize(getActiveWindow(), Math.abs(sizeChangeWidth)); + const sizeChangePxHeight = Math.sign(sizeChangeHeight) * computeScreenAwareSize(getActiveWindow(), Math.abs(sizeChangeHeight)); let viewSize: IViewSize; @@ -2311,7 +2311,7 @@ type ZenModeConfiguration = { hideActivityBar: boolean; hideLineNumbers: boolean; hideStatusBar: boolean; - hideTabs: boolean; + showTabs: 'multiple' | 'single' | 'none'; restore: boolean; silentNotifications: boolean; }; diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index d86ebb34475..1d76000dfcf 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -64,7 +64,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { UntitledTextEditorInputSerializer, UntitledTextEditorWorkingCopyEditorHandler } from 'vs/workbench/services/untitled/common/untitledTextEditorHandler'; import { DynamicEditorConfigurations } from 'vs/workbench/browser/parts/editor/editorConfiguration'; -import { ToggleSeparatePinnedTabsAction, ToggleTabsVisibilityAction } from 'vs/workbench/browser/actions/layoutActions'; +import { HideEditorTabsAction, ShowMultipleEditorTabsAction, ShowSingleEditorTabAction, ToggleSeparatePinnedTabsAction } from 'vs/workbench/browser/actions/layoutActions'; import product from 'vs/platform/product/common/product'; import { ICommandAction } from 'vs/platform/action/common/action'; @@ -354,7 +354,10 @@ MenuRegistry.appendMenuItem(MenuId.EditorTabsBarContext, { command: { id: SPLIT_ MenuRegistry.appendMenuItem(MenuId.EditorTabsBarContext, { command: { id: SPLIT_EDITOR_DOWN, title: localize('splitDown', "Split Down") }, group: '2_split', order: 20 }); MenuRegistry.appendMenuItem(MenuId.EditorTabsBarContext, { command: { id: SPLIT_EDITOR_LEFT, title: localize('splitLeft', "Split Left") }, group: '2_split', order: 30 }); MenuRegistry.appendMenuItem(MenuId.EditorTabsBarContext, { command: { id: SPLIT_EDITOR_RIGHT, title: localize('splitRight', "Split Right") }, group: '2_split', order: 40 }); -MenuRegistry.appendMenuItem(MenuId.EditorTabsBarContext, { command: { id: ToggleTabsVisibilityAction.ID, title: localize('toggleTabs', "Editor Tabs"), toggled: ContextKeyExpr.has('config.workbench.editor.showTabs') }, group: '3_config', order: 10 }); +MenuRegistry.appendMenuItem(MenuId.EditorTabsBarContext, { submenu: MenuId.EditorTabsBarShowTabsSubmenu, title: localize('showTabs', "Show Tabs"), group: '3_config', order: 10 }); +MenuRegistry.appendMenuItem(MenuId.EditorTabsBarShowTabsSubmenu, { command: { id: ShowMultipleEditorTabsAction.ID, title: localize('multipleTabs', "Multiple Tabs"), toggled: ContextKeyExpr.equals('config.workbench.editor.showTabs', 'multiple') }, group: '1_config', order: 10 }); +MenuRegistry.appendMenuItem(MenuId.EditorTabsBarShowTabsSubmenu, { command: { id: ShowSingleEditorTabAction.ID, title: localize('singleTab', "Single Tab"), toggled: ContextKeyExpr.equals('config.workbench.editor.showTabs', 'single') }, group: '1_config', order: 20 }); +MenuRegistry.appendMenuItem(MenuId.EditorTabsBarShowTabsSubmenu, { command: { id: HideEditorTabsAction.ID, title: localize('hideTabBar', "Hide Tab Bar"), toggled: ContextKeyExpr.equals('config.workbench.editor.showTabs', 'none') }, group: '1_config', order: 30 }); MenuRegistry.appendMenuItem(MenuId.EditorTabsBarContext, { command: { id: ToggleSeparatePinnedTabsAction.ID, title: localize('toggleSeparatePinnedEditorTabs', "Separate Pinned Editor Tabs"), toggled: ContextKeyExpr.has('config.workbench.editor.pinnedTabsOnSeparateRow') }, when: EditorPinnedAndUnpinnedTabsContext, group: '3_config', order: 20 }); // Editor Title Context Menu diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index 4573e19ba9d..26cd22c89ad 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -25,7 +25,7 @@ export const DEFAULT_EDITOR_MIN_DIMENSIONS = new Dimension(220, 70); export const DEFAULT_EDITOR_MAX_DIMENSIONS = new Dimension(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY); export const DEFAULT_EDITOR_PART_OPTIONS: IEditorPartOptions = { - showTabs: true, + showTabs: 'multiple', highlightModifiedTabs: false, tabCloseButton: 'right', tabSizing: 'fit', @@ -85,9 +85,21 @@ export function getEditorPartOptions(configurationService: IConfigurationService options.tabHeight = windowConfig.window.density.editorTabHeight; } + validateEditorPartOptions(options); + return options; } +function validateEditorPartOptions(options: IEditorPartOptions) { + // showTabs ensure correct enum value + if (typeof options.showTabs === 'boolean') { + // Migration service kicks in very late and can cause a flicker otherwise + options.showTabs = options.showTabs ? 'multiple' : 'single'; + } else if (options.showTabs !== 'multiple' && options.showTabs !== 'single' && options.showTabs !== 'none') { + options.showTabs = 'multiple'; + } +} + /** * A helper to access editor groups across all opened editor parts. */ diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index fa1f412e1b0..64221b9382d 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -530,7 +530,7 @@ class DropOverlay extends Themable { private getOverlayOffsetHeight(): number { // With tabs and opened editors: use the area below tabs as drop target - if (!this.groupView.isEmpty && this.editorGroupService.partOptions.showTabs) { + if (!this.groupView.isEmpty && this.editorGroupService.partOptions.showTabs === 'multiple') { return this.groupView.titleHeight.offset; } diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index bda45dfa508..78807a73dc4 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -464,7 +464,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } private updateTitleContainer(): void { - this.titleContainer.classList.toggle('tabs', this.groupsView.partOptions.showTabs); + this.titleContainer.classList.toggle('tabs', this.groupsView.partOptions.showTabs === 'multiple'); this.titleContainer.classList.toggle('show-file-icons', this.groupsView.partOptions.showIcons); } @@ -700,7 +700,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Title control switch between singleEditorTabs, multiEditorTabs and multiRowEditorTabs if ( event.oldPartOptions.showTabs !== event.newPartOptions.showTabs || - (event.oldPartOptions.showTabs && event.oldPartOptions.pinnedTabsOnSeparateRow !== event.newPartOptions.pinnedTabsOnSeparateRow) + (event.oldPartOptions.showTabs === 'multiple' && event.oldPartOptions.pinnedTabsOnSeparateRow !== event.newPartOptions.pinnedTabsOnSeparateRow) ) { // Re-layout @@ -1892,7 +1892,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } const { showTabs } = this.groupsView.partOptions; - this.titleContainer.style.backgroundColor = this.getColor(showTabs ? EDITOR_GROUP_HEADER_TABS_BACKGROUND : EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND) || ''; + this.titleContainer.style.backgroundColor = this.getColor(showTabs === 'multiple' ? EDITOR_GROUP_HEADER_TABS_BACKGROUND : EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND) || ''; // Editor container this.editorContainer.style.backgroundColor = this.getColor(editorBackground) || ''; diff --git a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts index d2ee98e46a0..9bcff2aec43 100644 --- a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts @@ -290,7 +290,7 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC // Drag all tabs of the group if tabs are enabled let hasDataTransfer = false; - if (this.groupsView.partOptions.showTabs) { + if (this.groupsView.partOptions.showTabs === 'multiple') { hasDataTransfer = this.doFillResourceDataTransfers(this.groupView.getEditors(EditorsOrder.SEQUENTIAL), e); } @@ -309,7 +309,7 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC // Drag Image if (this.groupView.activeEditor) { let label = this.groupView.activeEditor.getName(); - if (this.groupsView.partOptions.showTabs && this.groupView.count > 1) { + if (this.groupsView.partOptions.showTabs === 'multiple' && this.groupView.count > 1) { label = localize('draggedEditorGroup', "{0} (+{1})", label, this.groupView.count - 1); } diff --git a/src/vs/workbench/browser/parts/editor/editorTitleControl.ts b/src/vs/workbench/browser/parts/editor/editorTitleControl.ts index 69aa10b14b7..6330c3e814a 100644 --- a/src/vs/workbench/browser/parts/editor/editorTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorTitleControl.ts @@ -17,6 +17,7 @@ import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { MultiRowEditorControl } from 'vs/workbench/browser/parts/editor/multiRowEditorTabsControl'; import { IReadonlyEditorGroupModel } from 'vs/workbench/common/editor/editorGroupModel'; +import { NoEditorTabsControl } from 'vs/workbench/browser/parts/editor/noEditorTabsControl'; export interface IEditorTitleControlDimensions { @@ -57,23 +58,27 @@ export class EditorTitleControl extends Themable { } private createEditorTabsControl(): IEditorTabsControl { - let control: IEditorTabsControl; - if (this.groupsView.partOptions.showTabs) { - if (this.groupsView.partOptions.pinnedTabsOnSeparateRow) { - control = this.instantiationService.createInstance(MultiRowEditorControl, this.parent, this.editorPartsView, this.groupsView, this.groupView, this.model); - } else { - control = this.instantiationService.createInstance(MultiEditorTabsControl, this.parent, this.editorPartsView, this.groupsView, this.groupView, this.model); - } - } else { - control = this.instantiationService.createInstance(SingleEditorTabsControl, this.parent, this.editorPartsView, this.groupsView, this.groupView, this.model); + let tabsControlType; + switch (this.groupsView.partOptions.showTabs) { + case 'none': + tabsControlType = NoEditorTabsControl; + break; + case 'single': + tabsControlType = SingleEditorTabsControl; + break; + case 'multiple': + default: + tabsControlType = this.groupsView.partOptions.pinnedTabsOnSeparateRow ? MultiRowEditorControl : MultiEditorTabsControl; + break; } + const control = this.instantiationService.createInstance(tabsControlType, this.parent, this.editorPartsView, this.groupsView, this.groupView, this.model); return this.editorTabsControlDisposable.add(control); } private createBreadcrumbsControl(): BreadcrumbsControlFactory | undefined { - if (!this.groupsView.partOptions.showTabs) { - return undefined; // single tabs have breadcrumbs inlined + if (this.groupsView.partOptions.showTabs !== 'multiple') { + return undefined; // Single tabs have breadcrumbs inlined. No tabs have no breadcrumbs. } // Breadcrumbs container @@ -170,7 +175,7 @@ export class EditorTitleControl extends Themable { // Update editor tabs control if options changed if ( oldOptions.showTabs !== newOptions.showTabs || - (newOptions.showTabs && oldOptions.pinnedTabsOnSeparateRow !== newOptions.pinnedTabsOnSeparateRow) + (newOptions.showTabs === 'multiple' && oldOptions.pinnedTabsOnSeparateRow !== newOptions.pinnedTabsOnSeparateRow) ) { // Clear old this.editorTabsControlDisposable.clear(); diff --git a/src/vs/workbench/browser/parts/editor/media/singleeditortabscontrol.css b/src/vs/workbench/browser/parts/editor/media/singleeditortabscontrol.css index 893920c71c5..f3a7d7b2484 100644 --- a/src/vs/workbench/browser/parts/editor/media/singleeditortabscontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/singleeditortabscontrol.css @@ -28,7 +28,7 @@ /* Breadcrumbs (inline next to single editor tab) */ -.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .no-tabs.title-label { +.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .single-tab.title-label { flex: none; } diff --git a/src/vs/workbench/browser/parts/editor/noEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/noEditorTabsControl.ts new file mode 100644 index 00000000000..f265b196212 --- /dev/null +++ b/src/vs/workbench/browser/parts/editor/noEditorTabsControl.ts @@ -0,0 +1,56 @@ +/*--------------------------------------------------------------------------------------------- + * 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/singleeditortabscontrol'; +import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { EditorTabsControl, IToolbarActions } from 'vs/workbench/browser/parts/editor/editorTabsControl'; +import { Dimension } from 'vs/base/browser/dom'; +import { IEditorTitleControlDimensions } from 'vs/workbench/browser/parts/editor/editorTitleControl'; + +export class NoEditorTabsControl extends EditorTabsControl { + + protected prepareEditorActions(editorActions: IToolbarActions): IToolbarActions { + return { + primary: [], + secondary: [] + }; + } + + openEditor(editor: EditorInput): boolean { + return false; + } + + openEditors(editors: EditorInput[]): boolean { + return false; + } + + beforeCloseEditor(editor: EditorInput): void { } + + closeEditor(editor: EditorInput): void { } + + closeEditors(editors: EditorInput[]): void { } + + moveEditor(editor: EditorInput, fromIndex: number, targetIndex: number): void { } + + pinEditor(editor: EditorInput): void { } + + stickEditor(editor: EditorInput): void { } + + unstickEditor(editor: EditorInput): void { } + + setActive(isActive: boolean): void { } + + updateEditorLabel(editor: EditorInput): void { } + + updateEditorDirty(editor: EditorInput): void { } + + getHeight(): number { + return 0; + } + + layout(dimensions: IEditorTitleControlDimensions): Dimension { + return new Dimension(dimensions.container.width, this.getHeight()); + } +} diff --git a/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts index 8efda41b36d..0ea58b717d2 100644 --- a/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts @@ -317,7 +317,7 @@ export class SingleEditorTabsControl extends EditorTabsControl { { title, italic: !isEditorPinned, - extraClasses: ['no-tabs', 'title-label'].concat(editor.getLabelExtraClasses()), + extraClasses: ['single-tab', 'title-label'].concat(editor.getLabelExtraClasses()), fileDecorations: { colors: Boolean(options.decorations?.colors), badges: Boolean(options.decorations?.badges) diff --git a/src/vs/workbench/browser/parts/media/paneCompositePart.css b/src/vs/workbench/browser/parts/media/paneCompositePart.css index 0006a43986d..b30eba09a97 100644 --- a/src/vs/workbench/browser/parts/media/paneCompositePart.css +++ b/src/vs/workbench/browser/parts/media/paneCompositePart.css @@ -172,12 +172,12 @@ .monaco-workbench .pane-composite-part > .title > .composite-bar-container > .composite-bar > .monaco-action-bar .action-item.icon .badge.compact .badge-content { position: absolute; - top: 11px; + top: 12px; right: 0px; font-size: 9px; font-weight: 600; min-width: 12px; - height: 12px; + height: 11px; padding: 0 2px; border-radius: 16px; text-align: center; diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts index 3d7b73c376a..51115350f94 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts @@ -14,7 +14,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { INotificationsCenterController, NotificationActionRunner } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; import { NotificationsList } from 'vs/workbench/browser/parts/notifications/notificationsList'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { isAncestor, Dimension } from 'vs/base/browser/dom'; +import { Dimension, isAncestorOfActiveElement } from 'vs/base/browser/dom'; import { widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { localize } from 'vs/nls'; @@ -25,6 +25,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { assertAllDefined, assertIsDefined } from 'vs/base/common/types'; import { NotificationsCenterVisibleContext } from 'vs/workbench/common/contextkeys'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; export class NotificationsCenter extends Themable implements INotificationsCenterController { @@ -53,6 +54,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IKeybindingService private readonly keybindingService: IKeybindingService, @INotificationService private readonly notificationService: INotificationService, + @IAccessibleNotificationService private readonly accessibleNotificationService: IAccessibleNotificationService ) { super(themeService); @@ -220,7 +222,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente notificationsList.updateNotificationsList(e.index, 1, [e.item]); break; case NotificationChangeType.REMOVE: - focusEditor = isAncestor(document.activeElement, notificationsCenterContainer); + focusEditor = isAncestorOfActiveElement(notificationsCenterContainer); notificationsList.updateNotificationsList(e.index, 1); e.item.updateVisibility(false); break; @@ -245,7 +247,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente return; // already hidden } - const focusEditor = isAncestor(document.activeElement, this.notificationsCenterContainer); + const focusEditor = isAncestorOfActiveElement(this.notificationsCenterContainer); // Hide this._isVisible = false; @@ -329,6 +331,7 @@ export class NotificationsCenter extends Themable implements INotificationsCente if (!notification.hasProgress) { notification.close(); } + this.accessibleNotificationService.notify(AccessibleNotificationEvent.Clear); } } } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts index 21a7891569b..d341f22ee65 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts @@ -19,6 +19,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ActionRunner, IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; import { hash } from 'vs/base/common/hash'; import { firstOrDefault } from 'vs/base/common/arrays'; +import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; // Center export const SHOW_NOTIFICATIONS_CENTER = 'notifications.showList'; @@ -138,9 +139,11 @@ export function registerNotificationCommands(center: INotificationsCenterControl primary: KeyMod.CtrlCmd | KeyCode.Backspace }, handler: (accessor, args?) => { + const accessibleNotificationService = accessor.get(IAccessibleNotificationService); const notification = getNotificationFromContext(accessor.get(IListService), args); if (notification && !notification.hasProgress) { notification.close(); + accessibleNotificationService.notify(AccessibleNotificationEvent.Clear); } } }); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsList.ts b/src/vs/workbench/browser/parts/notifications/notificationsList.ts index 727e2e866a3..5ac8c173685 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsList.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsList.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/notificationsList'; import { localize } from 'vs/nls'; -import { isAncestor, trackFocus } from 'vs/base/browser/dom'; +import { isAncestorOfActiveElement, trackFocus } from 'vs/base/browser/dom'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IListAccessibilityProvider, IListOptions } from 'vs/base/browser/ui/list/listWidget'; @@ -133,7 +133,7 @@ export class NotificationsList extends Disposable { updateNotificationsList(start: number, deleteCount: number, items: INotificationViewItem[] = []) { const [list, listContainer] = assertAllDefined(this.list, this.listContainer); - const listHasDOMFocus = isAncestor(document.activeElement, listContainer); + const listHasDOMFocus = isAncestorOfActiveElement(listContainer); // Remember focus and relative top of that item const focusedIndex = list.getFocus()[0]; @@ -223,7 +223,7 @@ export class NotificationsList extends Disposable { return false; // not created yet } - return isAncestor(document.activeElement, this.listContainer); + return isAncestorOfActiveElement(this.listContainer); } layout(width: number, maxHeight?: number): void { diff --git a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts index 35785314672..f6fa63e49f2 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/notificationsToasts'; import { localize } from 'vs/nls'; import { INotificationsModel, NotificationChangeType, INotificationChangeEvent, INotificationViewItem, NotificationViewItemContentChangeKind } from 'vs/workbench/common/notifications'; import { IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { isAncestor, addDisposableListener, EventType, Dimension, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; +import { addDisposableListener, EventType, Dimension, scheduleAtNextAnimationFrame, isAncestorOfActiveElement } from 'vs/base/browser/dom'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { NotificationsList } from 'vs/workbench/browser/parts/notifications/notificationsList'; import { Event, Emitter } from 'vs/base/common/event'; @@ -322,7 +322,7 @@ export class NotificationsToasts extends Themable implements INotificationsToast // UI const notificationToast = this.mapNotificationToToast.get(item); if (notificationToast) { - const toastHasDOMFocus = isAncestor(document.activeElement, notificationToast.container); + const toastHasDOMFocus = isAncestorOfActiveElement(notificationToast.container); if (toastHasDOMFocus) { focusEditor = !(this.focusNext() || this.focusPrevious()); // focus next if any, otherwise focus editor } @@ -380,7 +380,7 @@ export class NotificationsToasts extends Themable implements INotificationsToast } hide(): void { - const focusEditor = this.notificationsToastsContainer ? isAncestor(document.activeElement, this.notificationsToastsContainer) : false; + const focusEditor = this.notificationsToastsContainer ? isAncestorOfActiveElement(this.notificationsToastsContainer) : false; this.removeToasts(); diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 5109fdeaf92..a9f73b33351 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -444,12 +444,12 @@ .monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .global-actions-container .monaco-action-bar .action-item.icon .badge.compact .badge-content { position: absolute; - top: 10px; + top: 11px; right: 0px; font-size: 9px; font-weight: 600; min-width: 12px; - height: 12px; + height: 11px; padding: 0 2px; border-radius: 16px; text-align: center; diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 5e867ccc662..c6c287adb4d 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -39,23 +39,29 @@ const registry = Registry.as(ConfigurationExtensions.Con default: 'default', }, 'workbench.editor.showTabs': { - 'type': 'boolean', - 'description': localize('showEditorTabs', "Controls whether opened editors should show in tabs or not."), - 'default': true + 'type': 'string', + 'enum': ['multiple', 'single', 'none'], + 'enumDescriptions': [ + localize('workbench.editor.showTabs.multiple', "Each editor is displayed as a tab in the editor title area."), + localize('workbench.editor.showTabs.single', "The active editor is displayed as a single large tab in the editor title area."), + localize('workbench.editor.showTabs.none', "The editor title area is not displayed."), + ], + 'description': localize('showEditorTabs', "Controls whether opened editors should show as individual tabs, one single large tab or if the title area should not be shown."), + 'default': 'multiple' }, 'workbench.editor.wrapTabs': { 'type': 'boolean', - 'markdownDescription': localize('wrapTabs', "Controls whether tabs should be wrapped over multiple lines when exceeding available space or whether a scrollbar should appear instead. This value is ignored when `#workbench.editor.showTabs#` is disabled."), + 'markdownDescription': localize('wrapTabs', "Controls whether tabs should be wrapped over multiple lines when exceeding available space or whether a scrollbar should appear instead. This value is ignored when `#workbench.editor.showTabs#` is not set to `multiple`."), 'default': false }, 'workbench.editor.scrollToSwitchTabs': { 'type': 'boolean', - 'markdownDescription': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'scrollToSwitchTabs' }, "Controls whether scrolling over tabs will open them or not. By default tabs will only reveal upon scrolling, but not open. You can press and hold the Shift-key while scrolling to change this behavior for that duration. This value is ignored when `#workbench.editor.showTabs#` is disabled."), + 'markdownDescription': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'scrollToSwitchTabs' }, "Controls whether scrolling over tabs will open them or not. By default tabs will only reveal upon scrolling, but not open. You can press and hold the Shift-key while scrolling to change this behavior for that duration. This value is ignored when `#workbench.editor.showTabs#` is not set to `multiple`."), 'default': false }, 'workbench.editor.highlightModifiedTabs': { 'type': 'boolean', - 'markdownDescription': localize('highlightModifiedTabs', "Controls whether a top border is drawn on tabs for editors that have unsaved changes. This value is ignored when `#workbench.editor.showTabs#` is disabled."), + 'markdownDescription': localize('highlightModifiedTabs', "Controls whether a top border is drawn on tabs for editors that have unsaved changes. This value is ignored when `#workbench.editor.showTabs#` is not set to `multiple`."), 'default': false }, 'workbench.editor.decorations.badges': { @@ -141,7 +147,7 @@ const registry = Registry.as(ConfigurationExtensions.Con 'type': 'string', 'enum': ['left', 'right', 'off'], 'default': 'right', - 'markdownDescription': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'editorTabCloseButton' }, "Controls the position of the editor's tabs close buttons, or disables them when set to 'off'. This value is ignored when `#workbench.editor.showTabs#` is disabled.") + 'markdownDescription': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'editorTabCloseButton' }, "Controls the position of the editor's tabs close buttons, or disables them when set to 'off'. This value is ignored when `#workbench.editor.showTabs#` is not set to `multiple`.") }, 'workbench.editor.tabSizing': { 'type': 'string', @@ -152,7 +158,7 @@ const registry = Registry.as(ConfigurationExtensions.Con localize('workbench.editor.tabSizing.shrink', "Allow tabs to get smaller when the available space is not enough to show all tabs at once."), localize('workbench.editor.tabSizing.fixed', "Make all tabs the same size, while allowing them to get smaller when the available space is not enough to show all tabs at once.") ], - 'markdownDescription': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'tabSizing' }, "Controls the size of editor tabs. This value is ignored when `#workbench.editor.showTabs#` is disabled.") + 'markdownDescription': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'tabSizing' }, "Controls the size of editor tabs. This value is ignored when `#workbench.editor.showTabs#` is not set to `multiple`.") }, 'workbench.editor.tabSizingFixedMinWidth': { 'type': 'number', @@ -170,7 +176,7 @@ const registry = Registry.as(ConfigurationExtensions.Con 'type': 'string', 'enum': ['default', 'compact'], 'default': 'default', - 'markdownDescription': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.editor.tabHeight' }, "Controls the height of editor tabs. Also applies to the title control bar when `#workbench.editor.showTabs#` is disabled.") + 'markdownDescription': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.editor.tabHeight' }, "Controls the height of editor tabs. Also applies to the title control bar when `#workbench.editor.showTabs#` is not set to `multiple`.") }, 'workbench.editor.pinnedTabSizing': { 'type': 'string', @@ -181,12 +187,12 @@ const registry = Registry.as(ConfigurationExtensions.Con localize('workbench.editor.pinnedTabSizing.compact', "A pinned tab will show in a compact form with only icon or first letter of the editor name."), localize('workbench.editor.pinnedTabSizing.shrink', "A pinned tab shrinks to a compact fixed size showing parts of the editor name.") ], - 'markdownDescription': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'pinnedTabSizing' }, "Controls the size of pinned editor tabs. Pinned tabs are sorted to the beginning of all opened tabs and typically do not close until unpinned. This value is ignored when `#workbench.editor.showTabs#` is disabled.") + 'markdownDescription': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'pinnedTabSizing' }, "Controls the size of pinned editor tabs. Pinned tabs are sorted to the beginning of all opened tabs and typically do not close until unpinned. This value is ignored when `#workbench.editor.showTabs#` is not set to `multiple`.") }, 'workbench.editor.pinnedTabsOnSeparateRow': { 'type': 'boolean', 'default': false, - 'markdownDescription': localize('workbench.editor.pinnedTabsOnSeparateRow', "When enabled, displays pinned tabs in a separate row above all other tabs. This value is ignored when `#workbench.editor.showTabs#` is disabled."), + 'markdownDescription': localize('workbench.editor.pinnedTabsOnSeparateRow', "When enabled, displays pinned tabs in a separate row above all other tabs. This value is ignored when `#workbench.editor.showTabs#` is not set to `multiple`."), }, 'workbench.editor.preventPinnedEditorClose': { 'type': 'string', @@ -318,7 +324,7 @@ const registry = Registry.as(ConfigurationExtensions.Con 'workbench.editor.doubleClickTabToToggleEditorGroupSizes': { 'type': 'boolean', 'default': true, - 'markdownDescription': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'doubleClickTabToToggleEditorGroupSizes' }, "Controls whether to maximize/restore the editor group when double clicking on a tab. This value is ignored when `#workbench.editor.showTabs#` is disabled.") + 'markdownDescription': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'doubleClickTabToToggleEditorGroupSizes' }, "Controls whether to maximize/restore the editor group when double clicking on a tab. This value is ignored when `#workbench.editor.showTabs#` is not set to `multiple`.") }, 'workbench.editor.limit.enabled': { 'type': 'boolean', @@ -721,10 +727,16 @@ const registry = Registry.as(ConfigurationExtensions.Con 'default': true, 'description': localize('zenMode.centerLayout', "Controls whether turning on Zen Mode also centers the layout.") }, - 'zenMode.hideTabs': { - 'type': 'boolean', - 'default': true, - 'description': localize('zenMode.hideTabs', "Controls whether turning on Zen Mode also hides workbench tabs.") + 'zenMode.showTabs': { + 'type': 'string', + 'enum': ['multiple', 'single', 'none'], + 'description': localize('zenMode.showTabs', "Controls whether turning on Zen Mode should show multiple editor tabs, a single editor tab or hide the editor title area completely."), + 'enumDescriptions': [ + localize('zenMode.showTabs.multiple', "Each editor is displayed as a tab in the editor title area."), + localize('zenMode.showTabs.single', "The active editor is displayed as a single large tab in the editor title area."), + localize('zenMode.showTabs.none', "The editor title area is not displayed."), + ], + 'default': 'multiple' }, 'zenMode.hideStatusBar': { 'type': 'boolean', @@ -765,3 +777,24 @@ Registry.as(Extensions.ConfigurationMigration) return result; } }]); + +Registry.as(Extensions.ConfigurationMigration) + .registerConfigurationMigrations([{ + key: 'workbench.editor.showTabs', migrateFn: (value: any) => { + if (typeof value === 'boolean') { + value = value ? 'multiple' : 'single'; + } + return [['workbench.editor.showTabs', { value: value }]]; + } + }]); + +Registry.as(Extensions.ConfigurationMigration) + .registerConfigurationMigrations([{ + key: 'zenMode.hideTabs', migrateFn: (value: any) => { + const result: ConfigurationKeyValuePairs = [['zenMode.hideTabs', { value: undefined }]]; + if (value === true) { + result.push(['zenMode.showTabs', { value: 'single' }]); + } + return result; + } + }]); diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 5cef907363e..36c62f1a362 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -1096,7 +1096,7 @@ export interface IWorkbenchEditorConfiguration { } interface IEditorPartConfiguration { - showTabs?: boolean; + showTabs?: 'multiple' | 'single' | 'none'; wrapTabs?: boolean; scrollToSwitchTabs?: boolean; highlightModifiedTabs?: boolean; diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index 9d31beaefc2..c09ca6b8053 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -256,7 +256,7 @@ export const EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND = registerColor('editorGroup light: editorBackground, hcDark: editorBackground, hcLight: editorBackground -}, localize('editorGroupHeaderBackground', "Background color of the editor group title header when tabs are disabled (`\"workbench.editor.showTabs\": false`). Editor groups are the containers of editors.")); +}, localize('editorGroupHeaderBackground', "Background color of the editor group title header when (`\"workbench.editor.showTabs\": \"single\"`). Editor groups are the containers of editors.")); export const EDITOR_GROUP_HEADER_BORDER = registerColor('editorGroupHeader.border', { dark: null, @@ -911,7 +911,7 @@ export const COMMAND_CENTER_ACTIVEBACKGROUND = registerColor( ); // border: active and inactive. defaults to active background export const COMMAND_CENTER_BORDER = registerColor( - 'commandCenter.border', { dark: transparent(TITLE_BAR_ACTIVE_FOREGROUND, .20), hcDark: transparent(TITLE_BAR_ACTIVE_FOREGROUND, .60), light: transparent(TITLE_BAR_ACTIVE_FOREGROUND, .20), hcLight: transparent(TITLE_BAR_ACTIVE_FOREGROUND, .60) }, + 'commandCenter.border', { dark: transparent(TITLE_BAR_ACTIVE_FOREGROUND, .20), hcDark: contrastBorder, light: transparent(TITLE_BAR_ACTIVE_FOREGROUND, .20), hcLight: contrastBorder }, localize('commandCenter-border', "Border color of the command center"), false ); diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 2e2eb2e188a..86b8003c0dd 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -125,10 +125,10 @@ const configuration: IConfigurationNode = { ...baseProperty }, [AccessibilityAlertSettingId.Save]: { - 'markdownDescription': localize('alert.save', "When in screen reader mode, alerts when a file is saved. Also see {0}", '`#audioCues.save#`'), + 'markdownDescription': localize('alert.save', "When in screen reader mode, alerts when a file is saved. Note that this will be ignored when {0} is enabled.", '`#audioCues.save#`'), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], - 'default': 'never', + 'default': 'always', '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."), @@ -137,10 +137,10 @@ const configuration: IConfigurationNode = { tags: ['accessibility'] }, [AccessibilityAlertSettingId.Format]: { - 'markdownDescription': localize('alert.format', "When in screen reader mode, alerts when a file or notebook cell is formatted. Also see {0}", '`#audioCues.format#`'), + 'markdownDescription': localize('alert.format', "When in screen reader mode, alerts when a file or notebook cell is formatted. Note that this will be ignored when {0} is enabled.", '`#audioCues.format#`'), 'type': 'string', 'enum': ['userGesture', 'always', 'never'], - 'default': 'never', + 'default': 'always', 'enumDescriptions': [ localize('alert.format.userGesture', "Alerts when a file is formatted via user gesture."), localize('alert.format.always', "Alerts whenever is a file is formatted, including auto save, on cell execution, and more."), diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleNotificationService.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleNotificationService.ts index 62d1c14ca23..7e8503931d9 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleNotificationService.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleNotificationService.ts @@ -50,16 +50,19 @@ export class AccessibleNotificationService extends Disposable implements IAccess if (!alertSetting) { return; } - const alertSettingValue: NotificationSetting = this._configurationService.getValue(alertSetting); - if (this._shouldNotify(alertSettingValue, userGesture)) { - this._logService.debug('AccessibleNotificationService alerting: ', alertMessage); - this._accessibilityService.alert(alertMessage); - } const audioCueSetting: NotificationSetting = this._configurationService.getValue(audioCue.settingsKey); if (this._shouldNotify(audioCueSetting, userGesture)) { this._logService.debug('AccessibleNotificationService playing sound: ', audioCue.name); + console.log('AccessibleNotificationService playing sound: ', audioCue.name); // Play sound bypasses the usual audio cue checks IE screen reader optimized, auto, etc. this._audioCueService.playSound(audioCue.sound.getSound(), true); + return; + } + const alertSettingValue: NotificationSetting = this._configurationService.getValue(alertSetting); + if (this._shouldNotify(alertSettingValue, userGesture)) { + this._logService.debug('AccessibleNotificationService alerting: ', alertMessage); + console.log('AccessibleNotificationService alerting: ', alertMessage); + this._accessibilityService.alert(alertMessage); } } diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 21cd85094e7..c73385b4246 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { EventType, addDisposableListener, isActiveElement } from 'vs/base/browser/dom'; +import { EventType, addDisposableListener, getActiveWindow, isActiveElement } from 'vs/base/browser/dom'; import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { alert } from 'vs/base/browser/ui/aria/aria'; @@ -57,7 +57,7 @@ export interface IAccessibleContentProvider { actions?: IAction[]; provideContent(): string; onClose(): void; - onKeyUp?(e: IKeyboardEvent): void; + onKeyDown?(e: IKeyboardEvent): void; previous?(): void; next?(): void; /** @@ -264,7 +264,7 @@ export class AccessibleView extends Disposable { return; } const delegate: IContextViewDelegate = { - getAnchor: () => { return { x: (window.innerWidth / 2) - ((Math.min(this._layoutService.dimension.width * 0.62 /* golden cut */, DIMENSIONS.MAX_WIDTH)) / 2), y: this._layoutService.offset.quickPickTop }; }, + getAnchor: () => { return { x: (getActiveWindow().innerWidth / 2) - ((Math.min(this._layoutService.dimension.width * 0.62 /* golden cut */, DIMENSIONS.MAX_WIDTH)) / 2), y: this._layoutService.offset.quickPickTop }; }, render: (container) => { container.classList.add('accessible-view-container'); return this._render(provider!, container, showAccessibleViewHelp); @@ -507,7 +507,6 @@ export class AccessibleView extends Disposable { setTimeout(() => provider.onClose(), 100); }; const disposableStore = new DisposableStore(); - disposableStore.add(this._editorWidget.onKeyUp((e) => provider.onKeyUp?.(e))); disposableStore.add(this._editorWidget.onKeyDown((e) => { if (e.keyCode === KeyCode.Escape) { handleEscape(e); @@ -518,6 +517,7 @@ export class AccessibleView extends Disposable { e.preventDefault(); e.stopPropagation(); } + provider.onKeyDown?.(e); })); disposableStore.add(addDisposableListener(this._toolbar.getElement(), EventType.KEY_DOWN, (e: KeyboardEvent) => { const keyboardEvent = new StandardKeyboardEvent(e); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index c29335d7607..f43739183dd 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -28,7 +28,7 @@ import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatAct import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { ICodeBlockActionContext } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; import { CONTEXT_IN_CHAT_SESSION, CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; -import { IChatCopyAction, IChatService, IChatUserActionEvent, IDocumentContext, InteractiveSessionCopyKind } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatCopyAction, IChatService, IDocumentContext, InteractiveSessionCopyKind } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatResponseViewModel, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { CTX_INLINE_CHAT_VISIBLE } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; @@ -106,10 +106,11 @@ export function registerChatCodeBlockActions() { if (isResponseVM(context.element)) { const chatService = accessor.get(IChatService); - chatService.notifyUserAction({ + chatService.notifyUserAction({ providerId: context.element.providerId, agentId: context.element.agent?.id, sessionId: context.element.sessionId, + requestId: context.element.requestId, action: { kind: 'copy', responseId: context.element.providerResponseId, @@ -154,6 +155,7 @@ export function registerChatCodeBlockActions() { providerId: context.element.providerId, agentId: context.element.agent?.id, sessionId: context.element.sessionId, + requestId: context.element.requestId, action: { kind: 'copy', codeBlockIndex: context.codeBlockIndex, @@ -328,13 +330,14 @@ export function registerChatCodeBlockActions() { private notifyUserAction(accessor: ServicesAccessor, context: ICodeBlockActionContext) { if (isResponseVM(context.element)) { const chatService = accessor.get(IChatService); - chatService.notifyUserAction({ + chatService.notifyUserAction({ providerId: context.element.providerId, agentId: context.element.agent?.id, sessionId: context.element.sessionId, + requestId: context.element.requestId, action: { kind: 'insert', - responseId: context.element.providerResponseId, + responseId: context.element.providerResponseId!, codeBlockIndex: context.codeBlockIndex, totalCharacters: context.code.length, } @@ -376,13 +379,14 @@ export function registerChatCodeBlockActions() { editorService.openEditor({ contents: context.code, languageId: context.languageId, resource: undefined }); if (isResponseVM(context.element)) { - chatService.notifyUserAction({ + chatService.notifyUserAction({ providerId: context.element.providerId, agentId: context.element.agent?.id, sessionId: context.element.sessionId, + requestId: context.element.requestId, action: { kind: 'insert', - responseId: context.element.providerResponseId, + responseId: context.element.providerResponseId!, codeBlockIndex: context.codeBlockIndex, totalCharacters: context.code.length, newFile: true @@ -461,13 +465,14 @@ export function registerChatCodeBlockActions() { terminal.sendText(context.code, false, true); if (isResponseVM(context.element)) { - chatService.notifyUserAction({ + chatService.notifyUserAction({ providerId: context.element.providerId, agentId: context.element.agent?.id, sessionId: context.element.sessionId, + requestId: context.element.requestId, action: { kind: 'runInTerminal', - responseId: context.element.providerResponseId, + responseId: context.element.providerResponseId!, codeBlockIndex: context.codeBlockIndex, languageId: context.languageId, } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts index b2c5828ece2..4d9bf1fe75b 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts @@ -16,7 +16,7 @@ import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/ import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { CONTEXT_IN_CHAT_INPUT, CONTEXT_IN_CHAT_SESSION, CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; -import { IChatService, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatService, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { isRequestVM, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellEditType, CellKind, NOTEBOOK_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -52,14 +52,15 @@ export function registerChatTitleActions() { } const chatService = accessor.get(IChatService); - chatService.notifyUserAction({ + chatService.notifyUserAction({ providerId: item.providerId, agentId: item.agent?.id, sessionId: item.sessionId, + requestId: item.requestId, action: { kind: 'vote', direction: InteractiveSessionVoteDirection.Up, - responseId: item.providerResponseId, + responseId: item.providerResponseId!, } }); item.setVote(InteractiveSessionVoteDirection.Up); @@ -94,14 +95,15 @@ export function registerChatTitleActions() { } const chatService = accessor.get(IChatService); - chatService.notifyUserAction({ + chatService.notifyUserAction({ providerId: item.providerId, agentId: item.agent?.id, sessionId: item.sessionId, + requestId: item.requestId, action: { kind: 'vote', direction: InteractiveSessionVoteDirection.Down, - responseId: item.providerResponseId, + responseId: item.providerResponseId!, } }); item.setVote(InteractiveSessionVoteDirection.Down); diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index 7576aadb922..e81c2705d7a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -79,6 +79,9 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge private inputEditorHasText: IContextKey; private providerId: string | undefined; + private cachedDimensions: dom.Dimension | undefined; + private cachedToolbarWidth: number | undefined; + readonly inputUri = URI.parse(`${ChatInputPart.INPUT_SCHEME}:input-${ChatInputPart._counter++}`); constructor( @@ -251,6 +254,11 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge })); this.toolbar.getElement().classList.add('interactive-execute-toolbar'); this.toolbar.context = { widget }; + this._register(this.toolbar.onDidChangeMenuItems(() => { + if (this.cachedDimensions && typeof this.cachedToolbarWidth === 'number' && this.cachedToolbarWidth !== this.toolbar.getItemsWidth()) { + this.layout(this.cachedDimensions.height, this.cachedDimensions.width); + } + })); if (this.options.renderStyle === 'compact') { const toolbarSide = this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, inputAndSideToolbar, MenuId.ChatInputSide, { @@ -285,6 +293,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge } layout(height: number, width: number): number { + this.cachedDimensions = new dom.Dimension(width, height); + return this._layout(height, width); } @@ -301,7 +311,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge const editorBorder = 2; const editorPadding = 8; - const executeToolbarWidth = this.toolbar.getItemsWidth(); + const executeToolbarWidth = this.cachedToolbarWidth = this.toolbar.getItemsWidth(); const sideToolbarWidth = this.options.renderStyle === 'compact' ? 20 : 0; const initialEditorScrollWidth = this._inputEditor.getScrollWidth(); diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 9427302ee8d..af6ba1145eb 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -353,6 +353,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { diff --git a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts index ba56b069fcc..9be6f027aba 100644 --- a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts @@ -82,6 +82,10 @@ export class ChatViewPane extends ViewPane implements IChatViewPane { this.viewState.sessionId = model.sessionId; } + override shouldShowWelcome(): boolean { + return !this.chatService.hasProviders(); + } + protected override renderBody(parent: HTMLElement): void { try { super.renderBody(parent); diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 30ca4ca5734..65e2434e130 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -412,20 +412,28 @@ export class ChatWidget extends Disposable implements IChatWidget { this._register(this.inputPart.onDidFocus(() => this._onDidFocus.fire())); this._register(this.inputPart.onDidAcceptFollowup(e => { - // this.chatService.notifyUserAction if (!this.viewModel) { return; } + + this.acceptInput(e.followup.message); + + if (!e.response) { + // Followups can be shown by the welcome message, then there is no response associated. + // At some point we probably want telemetry for these too. + return; + } + this.chatService.notifyUserAction({ providerId: this.viewModel.providerId, sessionId: this.viewModel.sessionId, + requestId: e.response.requestId, agentId: e.response?.agent?.id, action: { kind: 'followUp', followup: e.followup }, }); - this.acceptInput(e.followup.message); })); this._register(this.inputPart.onDidChangeHeight(() => this.bodyDimension && this.layout(this.bodyDimension.height, this.bodyDimension.width))); } diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index d5d5a10dce4..04abd853552 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -17,7 +17,7 @@ padding: 16px 20px 16px 20px; display: flex; flex-direction: column; - gap: 6px; + gap: 8px; color: var(--vscode-interactive-session-foreground); cursor: default; @@ -169,7 +169,11 @@ .interactive-item-container .monaco-tokenized-source, .interactive-item-container code { font-family: var(--monaco-monospace-font); + font-size: 12px; color: var(--vscode-textPreformat-foreground); + background-color: var(--vscode-textPreformat-background); + padding: 1px 3px; + border-radius: 4px; } .interactive-item-container.interactive-item-compact { @@ -260,7 +264,7 @@ .interactive-response .interactive-result-code-block .interactive-result-editor .monaco-editor, .interactive-response .interactive-result-code-block .interactive-result-editor .monaco-editor .margin, .interactive-response .interactive-result-code-block .interactive-result-editor .monaco-editor .monaco-editor-background { - background-color: var(--vscode-interactive-result-editor-background-color); + background-color: var(--vscode-interactive-result-editor-background-color) !important; } .interactive-item-compact .interactive-result-code-block { @@ -332,10 +336,12 @@ .interactive-session .interactive-input-part .interactive-input-followups .interactive-session-followups .monaco-button { display: block; color: var(--vscode-textLink-foreground); + font-size: 12px; } .interactive-session .interactive-input-part .interactive-input-followups .interactive-session-followups code { font-family: var(--monaco-monospace-font); + font-size: 11px; } .interactive-session .interactive-input-part .interactive-input-followups .interactive-session-followups .monaco-button .codicon-sparkle { @@ -356,7 +362,7 @@ color: var(--vscode-icon-foreground) !important; } -.interactive-item-container.filtered-response .value .rendered-markdown { +.interactive-item-container.filtered-response .value > .rendered-markdown { -webkit-mask-image: linear-gradient(rgba(0, 0, 0, 0.85), rgba(0, 0, 0, 0.05)); mask-image: linear-gradient(rgba(0, 0, 0, 0.85), rgba(0, 0, 0, 0.05)); } @@ -421,19 +427,26 @@ .interactive-item-container .chat-resource-widget { background-color: var(--vscode-chat-slashCommandBackground); color: var(--vscode-chat-slashCommandForeground); - border-radius: 3px; + border-radius: 4px; white-space: nowrap; - padding: 1px; + padding: 1px 3px; } .interactive-session .chat-used-context.chat-used-context-collapsed .chat-used-context-list { display: none; } +.interactive-session .chat-used-context { + display: flex; + flex-direction: column; + gap: 6px; +} + .interactive-session .chat-used-context-list { border: 1px solid var(--vscode-chat-requestBorder); border-radius: 4px; padding: 4px; + margin-bottom: 8px; } .interactive-session .chat-used-context-list .monaco-list .monaco-list-row { @@ -458,13 +471,16 @@ padding: 0; text-align: initial; justify-content: initial; - margin-bottom: 6px; } -.interactive-session .chat-used-context-label .monaco-text-button { - outline-offset: unset !important; +.interactive-session .chat-used-context-label .monaco-text-button:focus { + outline: none; +} + +.interactive-session .chat-used-context-label .monaco-text-button:focus-visible { + outline: 1px solid var(--vscode-focusBorder); } .interactive-session .chat-used-context .chat-used-context-label .monaco-button .codicon { - margin: 0 2px 0 0; + margin: 0 0 0 4px; } diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 43f64cd6bd7..8b928207f83 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -53,6 +53,7 @@ export interface IChatResponseModel { readonly id: string; readonly providerId: string; readonly providerResponseId: string | undefined; + readonly requestId: string; readonly username: string; readonly avatarIconUri?: URI; readonly session: IChatModel; @@ -294,6 +295,7 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel _response: IMarkdownString | ReadonlyArray, public readonly session: ChatModel, public readonly agent: IChatAgent | undefined, + public readonly requestId: string, private _isComplete: boolean = false, private _isCanceled = false, private _vote?: InteractiveSessionVoteDirection, @@ -565,7 +567,7 @@ export class ChatModel extends Disposable implements IChatModel { const request = new ChatRequestModel(this, parsedRequest, raw.providerRequestId); if (raw.response || raw.responseErrorDetails) { const agent = raw.agent && this.chatAgentService.getAgents().find(a => a.id === raw.agent!.id); // TODO do something reasonable if this agent has disappeared since the last session - request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, agent, true, raw.isCanceled, raw.vote, raw.providerRequestId, raw.responseErrorDetails, raw.followups); + request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, agent, request.id, true, raw.isCanceled, raw.vote, raw.providerRequestId, raw.responseErrorDetails, raw.followups); if (raw.usedContext) { // @ulugbekna: if this's a new vscode sessions, doc versions are incorrect anyway? request.response.updateContent(raw.usedContext); } @@ -652,7 +654,7 @@ export class ChatModel extends Disposable implements IChatModel { } const request = new ChatRequestModel(this, message); - request.response = new ChatResponseModel([], this, chatAgent); + request.response = new ChatResponseModel([], this, chatAgent, request.id); this._requests.push(request); this._onDidChange.fire({ kind: 'addRequest', request }); @@ -665,7 +667,7 @@ export class ChatModel extends Disposable implements IChatModel { } if (!request.response) { - request.response = new ChatResponseModel([], this, undefined); + request.response = new ChatResponseModel([], this, undefined, request.id); } if (request.response.isComplete) { @@ -710,7 +712,7 @@ export class ChatModel extends Disposable implements IChatModel { } if (!request.response) { - request.response = new ChatResponseModel([], this, undefined); + request.response = new ChatResponseModel([], this, undefined, request.id); } request.response.setErrorDetails(rawResponse.errorDetails); diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index bb52eeaceef..d3e13df3fe3 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -212,7 +212,7 @@ export interface IChatCommandAction { export interface IChatFollowupAction { kind: 'followUp'; - followup: IChatFollowup; + followup: IChatReplyFollowup; } export type ChatUserAction = IChatVoteAction | IChatCopyAction | IChatInsertAction | IChatTerminalAction | IChatCommandAction | IChatFollowupAction; @@ -222,6 +222,7 @@ export interface IChatUserActionEvent { providerId: string; agentId: string | undefined; sessionId: string; + requestId: string; } export interface IChatDynamicRequest { @@ -265,6 +266,7 @@ export interface IChatService { onDidSubmitSlashCommand: Event<{ slashCommand: string; sessionId: string }>; registerProvider(provider: IChatProvider): IDisposable; + hasProviders(): boolean; getProviderInfos(): IChatProviderInfo[]; startSession(providerId: string, token: CancellationToken): ChatModel | undefined; getSession(sessionId: string): IChatModel | undefined; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 0e7382192dd..2b09ccbe24b 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -12,7 +12,6 @@ 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'; @@ -518,7 +517,7 @@ export class ChatService extends Disposable implements IChatService { request = model.addRequest(parsedRequest, agent); const requestProps: IChatAgentRequest = { sessionId, - requestId: generateUuid(), + requestId: request.id, message, variables: {}, command: agentSlashCommandPart?.command.name ?? '', @@ -760,6 +759,10 @@ export class ChatService extends Disposable implements IChatService { }); } + hasProviders(): boolean { + return this._providers.size > 0; + } + getProviderInfos(): IChatProviderInfo[] { return Array.from(this._providers.values()).map(provider => { return { diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index 365303eaf7b..dbf12f34f08 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -88,6 +88,8 @@ export interface IChatResponseViewModel { readonly dataId: string; readonly providerId: string; readonly providerResponseId: string | undefined; + /** The ID of the associated IChatRequestViewModel */ + readonly requestId: string; readonly username: string; readonly avatarIconUri?: URI; readonly agent?: IChatAgent; @@ -326,6 +328,10 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi return this._model.vote; } + get requestId() { + return this._model.requestId; + } + renderData: IChatResponseRenderData | undefined = undefined; currentRenderedHeight: number | undefined; diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts b/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts index 409022e8651..b382598b7bb 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/chat.contribution.ts @@ -32,9 +32,13 @@ class VoiceChatActionsContributor extends Disposable implements IWorkbenchContri constructor(@ISpeechService speechService: ISpeechService) { super(); - this._register(Event.once(speechService.onDidRegisterSpeechProvider)(() => { + if (speechService.hasSpeechProvider) { registerVoiceChatActions(); - })); + } else { + this._register(Event.once(speechService.onDidRegisterSpeechProvider)(() => { + registerVoiceChatActions(); + })); + } } } diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts index 85be3c855a2..d3849333ddc 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts @@ -21,6 +21,8 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { Event } from 'vs/base/common/event'; +import { addDisposableListener, onDidRegisterWindow } from 'vs/base/browser/dom'; const transientWordWrapState = 'transientWordWrapState'; const isWordWrapMinifiedKey = 'isWordWrapMinified'; @@ -255,7 +257,7 @@ function canToggleWordWrap(codeEditorService: ICodeEditorService, editor: ICodeE return true; } -class EditorWordWrapContextKeyTracker implements IWorkbenchContribution { +class EditorWordWrapContextKeyTracker extends Disposable implements IWorkbenchContribution { private readonly _canToggleWordWrap: IContextKey; private readonly _editorWordWrap: IContextKey; @@ -267,8 +269,11 @@ class EditorWordWrapContextKeyTracker implements IWorkbenchContribution { @ICodeEditorService private readonly _codeEditorService: ICodeEditorService, @IContextKeyService private readonly _contextService: IContextKeyService, ) { - window.addEventListener('focus', () => this._update(), true); - window.addEventListener('blur', () => this._update(), true); + super(); + this._register(Event.runAndSubscribe(onDidRegisterWindow, ({ window, disposableStore }) => { + disposableStore.add(addDisposableListener(window, 'focus', () => this._update(), true)); + disposableStore.add(addDisposableListener(window, 'blur', () => this._update(), true)); + }, { window, disposableStore: this._store })); this._editorService.onDidActiveEditorChange(() => this._update()); this._canToggleWordWrap = CAN_TOGGLE_WORD_WRAP.bindTo(this._contextService); this._editorWordWrap = EDITOR_WORD_WRAP.bindTo(this._contextService); diff --git a/src/vs/workbench/contrib/debug/browser/linkDetector.ts b/src/vs/workbench/contrib/debug/browser/linkDetector.ts index abfca65cb23..484f3286dba 100644 --- a/src/vs/workbench/contrib/debug/browser/linkDetector.ts +++ b/src/vs/workbench/contrib/debug/browser/linkDetector.ts @@ -18,6 +18,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { localize } from 'vs/nls'; import { ITunnelService } from 'vs/platform/tunnel/common/tunnel'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { getWindow } from 'vs/base/browser/dom'; const CONTROL_CODES = '\\u0000-\\u0020\\u007f-\\u009f'; const WEB_LINK_REGEX = new RegExp('(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\\/\\/|data:|www\\.)[^\\s' + CONTROL_CODES + '"]{2,}[^\\s' + CONTROL_CODES + '"\')}\\],:;.!?]', 'ug'); @@ -201,7 +202,7 @@ export class LinkDetector { link.onmousemove = (event) => { link.classList.toggle('pointer', platform.isMacintosh ? event.metaKey : event.ctrlKey); }; link.onmouseleave = () => link.classList.remove('pointer'); link.onclick = (event) => { - const selection = window.getSelection(); + const selection = getWindow(link).getSelection(); if (!selection || selection.type === 'Range') { return; // do not navigate when user is selecting } diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 7be4e8eb0bd..9805ab089a4 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -570,16 +570,18 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { this._register(registerNavigableContainer({ focusNotifiers: [this, this.filterWidget], focusNextWidget: () => { + const element = this.tree?.getHTMLElement(); if (this.filterWidget.hasFocus()) { this.tree?.domFocus(); - } else if (this.tree?.getHTMLElement() === document.activeElement) { + } else if (element && dom.isActiveElement(element)) { this.focus(); } }, focusPreviousWidget: () => { + const element = this.tree?.getHTMLElement(); if (this.replInput.hasTextFocus()) { this.tree?.domFocus(); - } else if (this.tree?.getHTMLElement() === document.activeElement) { + } else if (element && dom.isActiveElement(element)) { this.focusFilter(); } } @@ -648,7 +650,7 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { this._register(tree.onContextMenu(e => this.onContextMenu(e))); let lastSelectedString: string; this._register(tree.onMouseClick(() => { - const selection = window.getSelection(); + const selection = dom.getWindow(this.treeContainer).getSelection(); if (!selection || selection.type !== 'Range' || lastSelectedString === selection.toString()) { // only focus the input if the user is not currently selecting. this.replInput.focus(); @@ -1070,7 +1072,7 @@ registerAction2(class extends Action2 { async run(accessor: ServicesAccessor, element: IReplElement): Promise { const clipboardService = accessor.get(IClipboardService); const debugService = accessor.get(IDebugService); - const nativeSelection = window.getSelection(); + const nativeSelection = dom.getActiveWindow().getSelection(); const selectedText = nativeSelection?.toString(); if (selectedText && selectedText.length > 0) { return clipboardService.writeText(selectedText); diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index d3d8d5c7e59..ca2b5d06823 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -325,7 +325,7 @@ export class ExplorerView extends ViewPane implements IExplorerView { } hasFocus(): boolean { - return DOM.isAncestor(document.activeElement, this.container); + return DOM.isAncestorOfActiveElement(this.container); } getContext(respectMultiSelection: boolean): ExplorerItem[] { diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index bfebab5f73d..d567ec80916 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -621,11 +621,12 @@ export class FilesRenderer implements ICompressibleTreeRenderer { - ctrl.cancelSession(); + await ctrl.cancelSession(); } } @@ -357,7 +357,7 @@ export class DiscardToClipboardAction extends AbstractInlineChatAction { override async runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController): Promise { const clipboardService = accessor.get(IClipboardService); - const changedText = ctrl.cancelSession(); + const changedText = await ctrl.cancelSession(); if (changedText !== undefined) { clipboardService.writeText(changedText); } @@ -381,7 +381,7 @@ export class DiscardUndoToNewFileAction extends AbstractInlineChatAction { override async runInlineChatCommand(accessor: ServicesAccessor, ctrl: InlineChatController, editor: ICodeEditor, ..._args: any[]): Promise { const editorService = accessor.get(IEditorService); - const changedText = ctrl.cancelSession(); + const changedText = await ctrl.cancelSession(); if (changedText !== undefined) { const input: IUntitledTextResourceEditorInput = { forceUntitled: true, resource: undefined, contents: changedText, languageId: editor.getModel()?.getLanguageId() }; editorService.openEditor(input, SIDE_GROUP); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index c10fb758a41..bb504a95533 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -8,16 +8,14 @@ import { Barrier, raceCancellationError } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Emitter, Event } from 'vs/base/common/event'; -import { DisposableStore, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { StopWatch } from 'vs/base/common/stopwatch'; import { assertType } from 'vs/base/common/types'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon'; -import { ModelDecorationOptions, createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; -import { IModelService } from 'vs/editor/common/services/model'; import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController'; import { localize } from 'vs/nls'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; @@ -39,7 +37,6 @@ import { generateUuid } from 'vs/base/common/uuid'; import { TextEdit } from 'vs/editor/common/languages'; import { ISelection, Selection } from 'vs/editor/common/core/selection'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { IModelDeltaDecoration } from 'vs/editor/common/model'; export const enum State { CREATE_SESSION = 'CREATE_SESSION', @@ -93,12 +90,12 @@ export class InlineChatController implements IEditorContribution { return editor.getContribution(INLINE_CHAT_ID); } - private static _decoBlock = ModelDecorationOptions.register({ - description: 'inline-chat', - showIfCollapsed: false, - isWholeLine: true, - className: 'inline-chat-block-selection', - }); + // private static _decoBlock = ModelDecorationOptions.register({ + // description: 'inline-chat', + // showIfCollapsed: false, + // isWholeLine: true, + // className: 'inline-chat-block-selection', + // }); private static _promptHistory: string[] = []; private _historyOffset: number = -1; @@ -130,7 +127,6 @@ export class InlineChatController implements IEditorContribution { @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, @ILogService private readonly _logService: ILogService, @IConfigurationService private readonly _configurationService: IConfigurationService, - @IModelService private readonly _modelService: IModelService, @IDialogService private readonly _dialogService: IDialogService, @IContextKeyService contextKeyService: IContextKeyService, @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, @@ -335,22 +331,22 @@ export class InlineChatController implements IEditorContribution { this._sessionStore.clear(); - const wholeRangeDecoration = this._editor.createDecorationsCollection(); - const updateWholeRangeDecoration = () => { + // const wholeRangeDecoration = this._editor.createDecorationsCollection(); + // const updateWholeRangeDecoration = () => { - const range = this._activeSession!.wholeRange.value; - const decorations: IModelDeltaDecoration[] = []; - if (!range.isEmpty()) { - decorations.push({ - range, - options: InlineChatController._decoBlock - }); - } - wholeRangeDecoration.set(decorations); - }; - this._sessionStore.add(toDisposable(() => wholeRangeDecoration.clear())); - this._sessionStore.add(this._activeSession.wholeRange.onDidChange(updateWholeRangeDecoration)); - updateWholeRangeDecoration(); + // const range = this._activeSession!.wholeRange.value; + // const decorations: IModelDeltaDecoration[] = []; + // if (!range.isEmpty()) { + // decorations.push({ + // range, + // options: InlineChatController._decoBlock + // }); + // } + // wholeRangeDecoration.set(decorations); + // }; + // this._sessionStore.add(toDisposable(() => wholeRangeDecoration.clear())); + // this._sessionStore.add(this._activeSession.wholeRange.onDidChange(updateWholeRangeDecoration)); + // updateWholeRangeDecoration(); this._zone.value.widget.updateSlashCommands(this._activeSession.session.slashCommands ?? []); this._updatePlaceholder(); @@ -573,7 +569,7 @@ export class InlineChatController implements IEditorContribution { throw new Error('Progress in NOT supported in non-live mode'); } progressEdits.push(data.edits); - await this._makeChanges(progressEdits, false); + await this._makeChanges(data.edits, true); await this._strategy?.renderProgressChanges(); } }); @@ -595,8 +591,8 @@ export class InlineChatController implements IEditorContribution { response = new MarkdownResponse(this._activeSession.textModelN.uri, reply); } else if (reply) { const editResponse = new EditResponse(this._activeSession.textModelN.uri, this._activeSession.textModelN.getAlternativeVersionId(), reply, progressEdits); - if (editResponse.allLocalEdits.length > progressEdits.length) { - await this._makeChanges(editResponse.allLocalEdits, true); + for (let i = progressEdits.length; i < editResponse.allLocalEdits.length; i++) { + await this._makeChanges(editResponse.allLocalEdits[i], true); } response = editResponse; } else { @@ -649,30 +645,20 @@ export class InlineChatController implements IEditorContribution { return State.SHOW_RESPONSE; } - private async _makeChanges(allEdits: TextEdit[][], computeMoreMinimalEdits: boolean) { + private async _makeChanges(lastEdits: TextEdit[], computeMoreMinimalEdits: boolean) { assertType(this._activeSession); assertType(this._strategy); - if (allEdits.length === 0) { - return; - } - - // diff-changes from model0 -> modelN+1 - { - const lastEdits = allEdits[allEdits.length - 1]; - const textModelNplus1 = this._modelService.createModel(createTextBufferFactoryFromSnapshot(this._activeSession.textModelN.createSnapshot()), null, undefined, true); - textModelNplus1.applyEdits(lastEdits.map(TextEdit.asEditOperation)); - const diff = await this._editorWorkerService.computeDiff(this._activeSession.textModel0.uri, textModelNplus1.uri, { ignoreTrimWhitespace: false, maxComputationTimeMs: 5000, computeMoves: false }, 'advanced'); - this._activeSession.lastTextModelChanges = diff?.changes ?? []; - textModelNplus1.dispose(); - } - // make changes from modelN -> modelN+1 - const lastEdits = allEdits[allEdits.length - 1]; - const moreMinimalEdits = computeMoreMinimalEdits ? await this._editorWorkerService.computeHumanReadableDiff(this._activeSession.textModelN.uri, lastEdits) : undefined; + const moreMinimalEdits = computeMoreMinimalEdits ? await this._editorWorkerService.computeMoreMinimalEdits(this._activeSession.textModelN.uri, lastEdits) : undefined; const editOperations = (moreMinimalEdits ?? lastEdits).map(TextEdit.asEditOperation); this._log('edits from PROVIDER and after making them MORE MINIMAL', this._activeSession.provider.debugName, lastEdits, moreMinimalEdits); + if (editOperations.length === 0) { + // nothing left to do + return; + } + try { this._ignoreModelContentChanged = true; this._activeSession.wholeRange.trackEdits(editOperations); @@ -688,7 +674,6 @@ export class InlineChatController implements IEditorContribution { assertType(this._strategy); const { response } = this._activeSession.lastExchange!; - this._showWidget(false); let status: string | undefined; @@ -752,6 +737,7 @@ export class InlineChatController implements IEditorContribution { await this._strategy.renderChanges(response); } this._chatAccessibilityService.acceptResponse(status); + this._showWidget(false); return State.WAIT_FOR_INPUT; } @@ -910,11 +896,19 @@ export class InlineChatController implements IEditorContribution { this._messages.fire(Message.ACCEPT_SESSION); } - cancelSession() { - const result = this._activeSession?.asChangedText(); - if (this._activeSession?.lastExchange && InlineChatController.isEditOrMarkdownResponse(this._activeSession.lastExchange.response)) { - this._activeSession.provider.handleInlineChatResponseFeedback?.(this._activeSession.session, this._activeSession.lastExchange.response.raw, InlineChatResponseFeedbackKind.Undone); + async cancelSession() { + + let result: string | undefined; + if (this._activeSession) { + + const diff = await this._editorWorkerService.computeDiff(this._activeSession.textModel0.uri, this._activeSession.textModelN.uri, { ignoreTrimWhitespace: false, maxComputationTimeMs: 5000, computeMoves: false }, 'advanced'); + result = this._activeSession.asChangedText(diff?.changes ?? []); + + if (this._activeSession.lastExchange && InlineChatController.isEditOrMarkdownResponse(this._activeSession.lastExchange.response)) { + this._activeSession.provider.handleInlineChatResponseFeedback?.(this._activeSession.session, this._activeSession.lastExchange.response.raw, InlineChatResponseFeedbackKind.Undone); + } } + this._messages.fire(Message.CANCEL_SESSION); return result; } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts index e4a8b22b0aa..12dbe5db481 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Dimension, h } from 'vs/base/browser/dom'; -import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import { MutableDisposable } from 'vs/base/common/lifecycle'; import { assertType } from 'vs/base/common/types'; import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; @@ -21,7 +21,7 @@ import { LineRange } from 'vs/editor/common/core/lineRange'; import { DetailedLineRangeMapping } from 'vs/editor/common/diff/rangeMapping'; import { Position } from 'vs/editor/common/core/position'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; -import { ScrollType } from 'vs/editor/common/editorCommon'; +import { IEditorDecorationsCollection, ScrollType } from 'vs/editor/common/editorCommon'; import { ILogService } from 'vs/platform/log/common/log'; import { lineRangeAsRange, invertLineRange } from 'vs/workbench/contrib/inlineChat/browser/utils'; import { ResourceLabel } from 'vs/workbench/browser/labels'; @@ -35,22 +35,24 @@ import { ILanguageService } from 'vs/editor/common/languages/language'; import { FoldingController } from 'vs/editor/contrib/folding/browser/folding'; import { WordHighlighterContribution } from 'vs/editor/contrib/wordHighlighter/browser/wordHighlighter'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { generateUuid } from 'vs/base/common/uuid'; export class InlineChatLivePreviewWidget extends ZoneWidget { - private static readonly _hideId = 'overlayDiff'; + private readonly _hideId = `overlayDiff:${generateUuid()}`; private readonly _elements = h('div.inline-chat-diff-widget@domNode'); - private readonly _sessionStore = this._disposables.add(new DisposableStore()); + private readonly _decorationCollection: IEditorDecorationsCollection; private readonly _diffEditor: IDiffEditor; + private _dim: Dimension | undefined; private _isVisible: boolean = false; - private _isDiffLocked: boolean = false; constructor( editor: ICodeEditor, private readonly _session: Session, + onDidChangeDiff: (() => void) | undefined, @IInstantiationService instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, @ILogService private readonly _logService: ILogService, @@ -60,6 +62,8 @@ export class InlineChatLivePreviewWidget extends ZoneWidget { super.create(); assertType(editor.hasModel()); + this._decorationCollection = editor.createDecorationsCollection(); + const diffContributions = EditorExtensionsRegistry .getEditorContributions() .filter(c => c.id !== INLINE_CHAT_ID && c.id !== FoldingController.ID); @@ -80,6 +84,7 @@ export class InlineChatLivePreviewWidget extends ZoneWidget { stickyScroll: { enabled: false }, minimap: { enabled: false }, isInEmbeddedEditor: true, + useInlineViewWhenSpaceIsLimited: false, overflowWidgetsDomNode: editor.getOverflowWidgetsDomNode(), onlyShowAccessibleDiffViewer: this.accessibilityService.isScreenReaderOptimized(), }, { @@ -93,6 +98,10 @@ export class InlineChatLivePreviewWidget extends ZoneWidget { lineDecorationsWidth: editor.getLayoutInfo().decorationsWidth }); + if (onDidChangeDiff) { + this._disposables.add(this._diffEditor.onDidUpdateDiff(() => { onDidChangeDiff(); })); + } + const highlighter = WordHighlighterContribution.get(editor); if (highlighter) { this._disposables.add(highlighter.linkWordHighlighters(this._diffEditor.getModifiedEditor())); @@ -132,61 +141,67 @@ export class InlineChatLivePreviewWidget extends ZoneWidget { } override hide(): void { + this._decorationCollection.clear(); this._cleanupFullDiff(); - this._sessionStore.clear(); super.hide(); this._isVisible = false; } override show(): void { - assertType(this.editor.hasModel()); - this._sessionStore.clear(); - this._isDiffLocked = false; + throw new Error('use showForChanges'); + } + + showForChanges(changes: readonly DetailedLineRangeMapping[]): void { + const hasFocus = this._diffEditor.hasTextFocus(); this._isVisible = true; - this._sessionStore.add(this._diffEditor.onDidUpdateDiff(() => { - const result = this._diffEditor.getDiffComputationResult(); - const hasFocus = this._diffEditor.hasTextFocus(); - this._updateFromChanges(this._session.wholeRange.value, result?.changes2 ?? []); - // TODO@jrieken find a better fix for this. this is the challenge: - // the _doShowForChanges method invokes show of the zone widget which removes and adds the - // zone and overlay parts. this dettaches and reattaches the dom nodes which means they lose - // focus - if (hasFocus) { - this._diffEditor.focus(); - } - })); - this._updateFromChanges(this._session.wholeRange.value, this._session.lastTextModelChanges); - } + const onlyInserts = changes.every(change => change.original.isEmpty); - lockToDiff(): void { - this._isDiffLocked = true; - } - - private _updateFromChanges(range: Range, changes: readonly DetailedLineRangeMapping[]): void { - assertType(this.editor.hasModel()); - - if (this._isDiffLocked) { - return; - } - - if (changes.length === 0 || this._session.textModel0.getValueLength() === 0) { + if (onlyInserts || changes.length === 0 || this._session.textModel0.getValueLength() === 0) { // no change or changes to an empty file this._logService.debug('[IE] livePreview-mode: no diff'); this._cleanupFullDiff(); + this._renderInsertWithHighlight(changes); } else { // complex changes this._logService.debug('[IE] livePreview-mode: full diff'); - this._renderChangesWithFullDiff(changes, range); + this._decorationCollection.clear(); + this._renderChangesWithFullDiff(changes); } + + // TODO@jrieken find a better fix for this. this is the challenge: + // the `_updateFromChanges` method invokes show of the zone widget which removes and adds the + // zone and overlay parts. this dettaches and reattaches the dom nodes which means they lose + // focus + if (hasFocus) { + this._diffEditor.focus(); + } + } + + + private _renderInsertWithHighlight(changes: readonly DetailedLineRangeMapping[]) { + assertType(this.editor.hasModel()); + + const ranges = this._computeHiddenRanges(this.editor.getModel(), changes); + + this._decorationCollection.set([{ + range: lineRangeAsRange(ranges.modifiedHidden), + options: { + description: 'inline-chat-insert', + showIfCollapsed: false, + isWholeLine: true, + className: 'inline-chat-lines-inserted-range', + } + }]); } // --- full diff - private _renderChangesWithFullDiff(changes: readonly DetailedLineRangeMapping[], range: Range) { + private _renderChangesWithFullDiff(changes: readonly DetailedLineRangeMapping[]) { + assertType(this.editor.hasModel()); - const modified = this.editor.getModel()!; - const ranges = this._computeHiddenRanges(modified, range, changes); + const modified = this.editor.getModel(); + const ranges = this._computeHiddenRanges(modified, changes); this._hideEditorRanges(this.editor, [ranges.modifiedHidden]); this._hideEditorRanges(this._diffEditor.getOriginalEditor(), ranges.originalDiffHidden); @@ -197,25 +212,20 @@ export class InlineChatLivePreviewWidget extends ZoneWidget { const lineCountModified = ranges.modifiedHidden.length; const lineCountOriginal = ranges.originalHidden.length; - const lineHeightDiff = Math.max(lineCountModified, lineCountOriginal); - const lineHeightPadding = (this.editor.getOption(EditorOption.lineHeight) / 12) /* padding-top/bottom*/; - const heightInLines = lineHeightDiff + lineHeightPadding; + const heightInLines = Math.max(lineCountModified, lineCountOriginal); super.show(ranges.anchor, heightInLines); this._logService.debug(`[IE] diff SHOWING at ${ranges.anchor} with ${heightInLines} lines height`); } private _cleanupFullDiff() { - this.editor.setHiddenAreas([], InlineChatLivePreviewWidget._hideId); - this._diffEditor.getOriginalEditor().setHiddenAreas([], InlineChatLivePreviewWidget._hideId); - this._diffEditor.getModifiedEditor().setHiddenAreas([], InlineChatLivePreviewWidget._hideId); + this.editor.setHiddenAreas([], this._hideId); + this._diffEditor.getOriginalEditor().setHiddenAreas([], this._hideId); + this._diffEditor.getModifiedEditor().setHiddenAreas([], this._hideId); super.hide(); } - private _computeHiddenRanges(model: ITextModel, range: Range, changes: readonly DetailedLineRangeMapping[]) { - if (changes.length === 0) { - changes = [new DetailedLineRangeMapping(LineRange.fromRange(range), LineRange.fromRange(range), undefined)]; - } + private _computeHiddenRanges(model: ITextModel, changes: readonly DetailedLineRangeMapping[]) { let originalLineRange = changes[0].original; let modifiedLineRange = changes[0].modified; @@ -224,16 +234,8 @@ export class InlineChatLivePreviewWidget extends ZoneWidget { modifiedLineRange = modifiedLineRange.join(changes[i].modified); } - const startDelta = modifiedLineRange.startLineNumber - range.startLineNumber; - if (startDelta > 0) { - modifiedLineRange = new LineRange(modifiedLineRange.startLineNumber - startDelta, modifiedLineRange.endLineNumberExclusive); - originalLineRange = new LineRange(originalLineRange.startLineNumber - startDelta, originalLineRange.endLineNumberExclusive); - } - - const endDelta = range.endLineNumber - (modifiedLineRange.endLineNumberExclusive - 1); - if (endDelta > 0) { - modifiedLineRange = new LineRange(modifiedLineRange.startLineNumber, modifiedLineRange.endLineNumberExclusive + endDelta); - originalLineRange = new LineRange(originalLineRange.startLineNumber, originalLineRange.endLineNumberExclusive + endDelta); + if (originalLineRange.isEmpty) { + originalLineRange = new LineRange(originalLineRange.startLineNumber, originalLineRange.endLineNumberExclusive + 1); } const originalDiffHidden = invertLineRange(originalLineRange, this._session.textModel0); @@ -266,7 +268,7 @@ export class InlineChatLivePreviewWidget extends ZoneWidget { } else { hiddenRanges = lineRanges.map(lineRangeAsRange); } - editor.setHiddenAreas(hiddenRanges, InlineChatLivePreviewWidget._hideId); + editor.setHiddenAreas(hiddenRanges, this._hideId); this._logService.debug(`[IE] diff HIDING ${hiddenRanges} for ${editor.getId()} with ${String(editor.getModel()?.uri)}`); } @@ -286,7 +288,7 @@ export class InlineChatLivePreviewWidget extends ZoneWidget { const newDim = new Dimension(widthInPixel, heightInPixel); if (!Dimension.equals(this._dim, newDim)) { this._dim = newDim; - this._diffEditor.layout(this._dim.with(undefined, this._dim.height - 12 /* padding */)); + this._diffEditor.layout(this._dim.with(undefined, this._dim.height)); this._logService.debug('[IE] diff LAYOUT', this._dim); } } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts index 8de79727cea..b5daf7c9c27 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts @@ -23,9 +23,9 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Iterable } from 'vs/base/common/iterator'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { isCancellationError } from 'vs/base/common/errors'; -import { DetailedLineRangeMapping } from 'vs/editor/common/diff/rangeMapping'; import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { raceCancellation } from 'vs/base/common/async'; +import { LineRangeMapping } from 'vs/editor/common/diff/rangeMapping'; export type Recording = { when: Date; @@ -112,7 +112,6 @@ export class Session { private _lastInput: SessionPrompt | undefined; private _lastExpansionState: ExpansionState | undefined; - private _lastTextModelChanges: readonly DetailedLineRangeMapping[] | undefined; private _isUnstashed: boolean = false; private readonly _exchange: SessionExchange[] = []; private readonly _startTime = new Date(); @@ -187,26 +186,18 @@ export class Session { return this._exchange[this._exchange.length - 1]; } - get lastTextModelChanges() { - return this._lastTextModelChanges ?? []; - } - - set lastTextModelChanges(changes: readonly DetailedLineRangeMapping[]) { - this._lastTextModelChanges = changes; - } - get hasChangedText(): boolean { return !this.textModel0.equalsTextBuffer(this.textModelN.getTextBuffer()); } - asChangedText(): string | undefined { - if (!this._lastTextModelChanges || this._lastTextModelChanges.length === 0) { + asChangedText(changes: readonly LineRangeMapping[]): string | undefined { + if (changes.length === 0) { return undefined; } let startLine = Number.MAX_VALUE; let endLine = Number.MIN_VALUE; - for (const change of this._lastTextModelChanges) { + for (const change of changes) { startLine = Math.min(startLine, change.modified.startLineNumber); endLine = Math.max(endLine, change.modified.endLineNumberExclusive); } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 95480e8fb06..d47b03f78c1 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -3,15 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { equals, tail } from 'vs/base/common/arrays'; import { Event } from 'vs/base/common/event'; import { Lazy } from 'vs/base/common/lazy'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; -import { StableEditorScrollState } from 'vs/editor/browser/stableEditorScroll'; import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; import { Selection } from 'vs/editor/common/core/selection'; +import { DetailedLineRangeMapping, LineRangeMapping } from 'vs/editor/common/diff/rangeMapping'; import { IEditorDecorationsCollection } from 'vs/editor/common/editorCommon'; import { TextEdit } from 'vs/editor/common/languages'; import { ICursorStateComputer, IModelDecorationOptions, IModelDeltaDecoration, ITextModel, IValidEditOperation } from 'vs/editor/common/model'; @@ -132,7 +133,7 @@ export class PreviewStrategy extends EditModeStrategy { override async renderChanges(response: EditResponse): Promise { if (response.allLocalEdits.length > 0) { const allEditOperation = response.allLocalEdits.map(edits => edits.map(TextEdit.asEditOperation)); - this._widget.showEditsPreview(this._session.textModel0, allEditOperation, this._session.lastTextModelChanges); + await this._widget.showEditsPreview(this._session.textModel0, this._session.textModelN, allEditOperation); } else { this._widget.hideEditsPreview(); } @@ -238,12 +239,11 @@ export class LiveStrategy extends EditModeStrategy { protected readonly _session: Session, protected readonly _editor: ICodeEditor, protected readonly _widget: InlineChatWidget, - @IContextKeyService contextKeyService: IContextKeyService, @IConfigurationService configService: IConfigurationService, @IStorageService protected _storageService: IStorageService, @IBulkEditService protected readonly _bulkEditService: IBulkEditService, @IEditorWorkerService protected readonly _editorWorkerService: IEditorWorkerService, - @IInstantiationService private readonly _instaService: IInstantiationService, + @IInstantiationService protected readonly _instaService: IInstantiationService, ) { super(); this._diffEnabled = configService.getValue('inlineChat.showDiff'); @@ -327,9 +327,9 @@ export class LiveStrategy extends EditModeStrategy { } override async renderChanges(response: EditResponse) { - + const diff = await this._editorWorkerService.computeDiff(this._session.textModel0.uri, this._session.textModelN.uri, { ignoreTrimWhitespace: false, maxComputationTimeMs: 5000, computeMoves: false }, 'advanced'); + this._updateSummaryMessage(diff?.changes ?? []); this._inlineDiffDecorations.update(); - this._updateSummaryMessage(); if (response.singleCreateFileEdit) { this._widget.showCreatePreview(response.singleCreateFileEdit.uri, await Promise.all(response.singleCreateFileEdit.edits)); @@ -344,9 +344,9 @@ export class LiveStrategy extends EditModeStrategy { } } - protected _updateSummaryMessage() { + protected _updateSummaryMessage(mappings: readonly LineRangeMapping[]) { let linesChanged = 0; - for (const change of this._session.lastTextModelChanges) { + for (const change of mappings) { linesChanged += change.changedLineCount; } let message: string; @@ -361,19 +361,11 @@ export class LiveStrategy extends EditModeStrategy { } override getWidgetPosition(): Position | undefined { - const lastTextModelChanges = this._session.lastTextModelChanges; - let lastLineOfLocalEdits: number | undefined; - for (const change of lastTextModelChanges) { - const changeEndLineNumber = change.modified.endLineNumberExclusive - 1; - if (typeof lastLineOfLocalEdits === 'undefined' || lastLineOfLocalEdits < changeEndLineNumber) { - lastLineOfLocalEdits = changeEndLineNumber; - } - } - return lastLineOfLocalEdits ? new Position(lastLineOfLocalEdits, 1) : undefined; + return undefined; } override needsMargin(): boolean { - return !Boolean(this._session.lastTextModelChanges.length); + return true; } hasFocus(): boolean { @@ -383,46 +375,98 @@ export class LiveStrategy extends EditModeStrategy { export class LivePreviewStrategy extends LiveStrategy { - private readonly _diffZone: Lazy; private readonly _previewZone: Lazy; + private readonly _diffZonePool: InlineChatLivePreviewWidget[] = []; + private _currentLineRangeGroups: DetailedLineRangeMapping[][] = []; constructor( session: Session, editor: ICodeEditor, widget: InlineChatWidget, - @IContextKeyService contextKeyService: IContextKeyService, @IConfigurationService configService: IConfigurationService, @IStorageService storageService: IStorageService, @IBulkEditService bulkEditService: IBulkEditService, @IEditorWorkerService editorWorkerService: IEditorWorkerService, @IInstantiationService instaService: IInstantiationService, ) { - super(session, editor, widget, contextKeyService, configService, storageService, bulkEditService, editorWorkerService, instaService); + super(session, editor, widget, configService, storageService, bulkEditService, editorWorkerService, instaService); - this._diffZone = new Lazy(() => instaService.createInstance(InlineChatLivePreviewWidget, editor, session)); this._previewZone = new Lazy(() => instaService.createInstance(InlineChatFileCreatePreviewWidget, editor)); } override dispose(): void { - this._diffZone.rawValue?.hide(); - this._diffZone.rawValue?.dispose(); + for (const zone of this._diffZonePool) { + zone.hide(); + zone.dispose(); + } this._previewZone.rawValue?.hide(); this._previewZone.rawValue?.dispose(); super.dispose(); } - override async renderProgressChanges(): Promise { - if (!this._diffZone.value.isVisible) { - this._diffZone.value.show(); + + private async _renderDiffZones() { + const diff = await this._editorWorkerService.computeDiff(this._session.textModel0.uri, this._session.textModelN.uri, { ignoreTrimWhitespace: false, maxComputationTimeMs: 5000, computeMoves: false }, 'advanced'); + if (!diff || diff.changes.length === 0) { + return; } + + const groups: DetailedLineRangeMapping[][] = []; + let group = [diff.changes[0]]; + groups.push(group); + + for (let i = 1; i < diff.changes.length; i++) { + const last = tail(group); + const next = diff.changes[i]; + + // when the distance between the two changes is less than 75% of the total number of lines changed + // they get merged into the same group + const treshold = Math.ceil((next.modified.length + last.modified.length) * .75); + if (next.modified.startLineNumber - last.modified.endLineNumberExclusive <= treshold) { + group.push(next); + } else { + group = [next]; + groups.push(group); + } + } + + const beforeAndNowAreEqual = equals(this._currentLineRangeGroups, groups, (groupA, groupB) => { + return equals(groupA, groupB, (mappingA, mappingB) => { + return mappingA.original.equals(mappingB.original) && mappingA.modified.equals(mappingB.modified); + }); + }); + + if (beforeAndNowAreEqual) { + return; + } + + this._updateSummaryMessage(diff.changes); + this._currentLineRangeGroups = groups; + + const handleDiff = () => { + this._renderDiffZones(); + }; + + // create enough zones + while (groups.length > this._diffZonePool.length) { + this._diffZonePool.push(this._instaService.createInstance(InlineChatLivePreviewWidget, this._editor, this._session, this._diffZonePool.length === 0 ? handleDiff : undefined)); + } + for (let i = 0; i < groups.length; i++) { + this._diffZonePool[i].showForChanges(groups[i]); + } + // hide unused zones + for (let i = groups.length; i < this._diffZonePool.length; i++) { + this._diffZonePool[i].hide(); + } + } + + override async renderProgressChanges(): Promise { + return this._renderDiffZones(); } override async renderChanges(response: EditResponse) { - this._updateSummaryMessage(); - if (this._diffEnabled) { - this._diffZone.value.show(); - } + await this._renderDiffZones(); if (response.singleCreateFileEdit) { this._previewZone.value.showCreation(this._session.wholeRange.value.collapseToEnd(), response.singleCreateFileEdit.uri, await Promise.all(response.singleCreateFileEdit.edits)); @@ -431,30 +475,19 @@ export class LivePreviewStrategy extends LiveStrategy { } } - override async undoChanges(response: EditResponse): Promise { - this._diffZone.value.lockToDiff(); - super.undoChanges(response); - } - - protected override _doToggleDiff(): void { - const scrollState = StableEditorScrollState.capture(this._editor); - if (this._diffEnabled) { - this._diffZone.value.show(); - } else { - this._diffZone.value.hide(); - } - scrollState.restore(this._editor); - } - override hasFocus(): boolean { - return super.hasFocus() || this._diffZone.value.hasFocus() || this._previewZone.value.hasFocus(); + return super.hasFocus() || Boolean(this._previewZone.rawValue?.hasFocus()) || this._diffZonePool.some(zone => zone.isVisible && zone.hasFocus()); } override getWidgetPosition(): Position | undefined { - if (this._session.lastTextModelChanges.length) { - return this._session.wholeRange.value.getStartPosition().delta(-1); + for (let i = this._diffZonePool.length - 1; i >= 0; i--) { + const zone = this._diffZonePool[i]; + if (zone.isVisible && zone.position) { + // above last view zone + return zone.position; + } } - return this._session.wholeRange.value.getStartPosition().delta(-1); + return undefined; } } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index db86fea8ea0..a4223bb8d8d 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -36,7 +36,6 @@ import { FileKind } from 'vs/platform/files/common/files'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { LanguageSelector } from 'vs/editor/common/languageSelector'; import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; -import { DetailedLineRangeMapping } from 'vs/editor/common/diff/rangeMapping'; import { invertLineRange, lineRangeAsRange } from 'vs/workbench/contrib/inlineChat/browser/utils'; import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon'; import { LineRange } from 'vs/editor/common/core/lineRange'; @@ -62,6 +61,7 @@ import { MenuId } from 'vs/platform/actions/common/actions'; import { editorForeground, inputBackground, editorBackground } from 'vs/platform/theme/common/colorRegistry'; import { CodeBlockPart } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; import { Lazy } from 'vs/base/common/lazy'; +import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; const defaultAriaLabel = localize('aria-label', "Inline Chat Input"); @@ -215,7 +215,8 @@ export class InlineChatWidget { @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, @IConfigurationService private readonly _configurationService: IConfigurationService, @IContextMenuService private readonly _contextMenuService: IContextMenuService, - @IAccessibleViewService private readonly _accessibleViewService: IAccessibleViewService + @IAccessibleViewService private readonly _accessibleViewService: IAccessibleViewService, + @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService ) { // input editor logic @@ -664,27 +665,29 @@ export class InlineChatWidget { // --- preview - showEditsPreview(textModelv0: ITextModel, allEdits: ISingleEditOperation[][], changes: readonly DetailedLineRangeMapping[]) { - if (changes.length === 0) { + async showEditsPreview(textModel0: ITextModel, textModelN: ITextModel, allEdits: ISingleEditOperation[][]) { + + const diff = await this._editorWorkerService.computeDiff(textModel0.uri, textModelN.uri, { ignoreTrimWhitespace: false, maxComputationTimeMs: 5000, computeMoves: false }, 'advanced'); + if (!diff || diff.changes.length === 0) { this.hideEditsPreview(); return; } this._elements.previewDiff.classList.remove('hidden'); - const languageSelection: ILanguageSelection = { languageId: textModelv0.getLanguageId(), onDidChange: Event.None }; - const modified = this._modelService.createModel(createTextBufferFactoryFromSnapshot(textModelv0.createSnapshot()), languageSelection, undefined, true); + const languageSelection: ILanguageSelection = { languageId: textModel0.getLanguageId(), onDidChange: Event.None }; + const modified = this._modelService.createModel(createTextBufferFactoryFromSnapshot(textModel0.createSnapshot()), languageSelection, undefined, true); for (const edits of allEdits) { modified.applyEdits(edits, false); } - this._previewDiffEditor.value.setModel({ original: textModelv0, modified }); + this._previewDiffEditor.value.setModel({ original: textModel0, modified }); // joined ranges - let originalLineRange = changes[0].original; - let modifiedLineRange = changes[0].modified; - for (let i = 1; i < changes.length; i++) { - originalLineRange = originalLineRange.join(changes[i].original); - modifiedLineRange = modifiedLineRange.join(changes[i].modified); + let originalLineRange = diff.changes[0].original; + let modifiedLineRange = diff.changes[0].modified; + for (let i = 1; i < diff.changes.length; i++) { + originalLineRange = originalLineRange.join(diff.changes[i].original); + modifiedLineRange = modifiedLineRange.join(diff.changes[i].modified); } // apply extra padding @@ -695,10 +698,10 @@ export class InlineChatWidget { const newEndLineModified = Math.min(modifiedLineRange.endLineNumberExclusive + pad, modified.getLineCount()); modifiedLineRange = new LineRange(modifiedLineRange.startLineNumber, newEndLineModified); - const newEndLineOriginal = Math.min(originalLineRange.endLineNumberExclusive + pad, textModelv0.getLineCount()); + const newEndLineOriginal = Math.min(originalLineRange.endLineNumberExclusive + pad, textModel0.getLineCount()); originalLineRange = new LineRange(originalLineRange.startLineNumber, newEndLineOriginal); - const hiddenOriginal = invertLineRange(originalLineRange, textModelv0); + const hiddenOriginal = invertLineRange(originalLineRange, textModel0); const hiddenModified = invertLineRange(modifiedLineRange, modified); this._previewDiffEditor.value.getOriginalEditor().setHiddenAreas(hiddenOriginal.map(lineRangeAsRange), 'diff-hidden'); this._previewDiffEditor.value.getModifiedEditor().setHiddenAreas(hiddenModified.map(lineRangeAsRange), 'diff-hidden'); diff --git a/src/vs/workbench/contrib/inlineChat/browser/utils.ts b/src/vs/workbench/contrib/inlineChat/browser/utils.ts index 880c7778b11..a2a77ac6f2d 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/utils.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/utils.ts @@ -12,8 +12,12 @@ export function invertLineRange(range: LineRange, model: ITextModel): LineRange[ return []; } const result: LineRange[] = []; - result.push(new LineRange(1, range.startLineNumber)); - result.push(new LineRange(range.endLineNumberExclusive, model.getLineCount() + 1)); + if (range.startLineNumber > 1) { + result.push(new LineRange(1, range.startLineNumber)); + } + if (range.endLineNumberExclusive < model.getLineCount() + 1) { + result.push(new LineRange(range.endLineNumberExclusive, model.getLineCount() + 1)); + } return result.filter(r => !r.isEmpty); } diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index bb25fb4e308..822c5f8064c 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -173,7 +173,7 @@ suite('InteractiveChatController', function () { const run = ctrl.run({ message: 'Hello', autoSend: true }); await p; assert.ok(ctrl.getWidgetPosition() !== undefined); - ctrl.cancelSession(); + await ctrl.cancelSession(); await run; @@ -205,7 +205,7 @@ suite('InteractiveChatController', function () { assert.ok(session); assert.deepStrictEqual(session.wholeRange.value, new Range(1, 1, 1, 6)); - ctrl.cancelSession(); + await ctrl.cancelSession(); d.dispose(); }); @@ -235,7 +235,7 @@ suite('InteractiveChatController', function () { assert.ok(session); assert.deepStrictEqual(session.wholeRange.value, new Range(1, 1, 1, 6)); - ctrl.cancelSession(); + await ctrl.cancelSession(); d.dispose(); }); @@ -298,7 +298,7 @@ suite('InteractiveChatController', function () { assert.deepStrictEqual(session.wholeRange.value, new Range(1, 1, 4, 12)); - ctrl.cancelSession(); + await ctrl.cancelSession(); await r; }); diff --git a/src/vs/workbench/contrib/markers/browser/markersView.ts b/src/vs/workbench/contrib/markers/browser/markersView.ts index 5845464df98..0d4e22ab569 100644 --- a/src/vs/workbench/contrib/markers/browser/markersView.ts +++ b/src/vs/workbench/contrib/markers/browser/markersView.ts @@ -236,7 +236,7 @@ export class MarkersView extends FilterViewPane implements IMarkersView { } public override focus(): void { - if (this.widget.getHTMLElement() === document.activeElement) { + if (dom.isActiveElement(this.widget.getHTMLElement())) { return; } @@ -772,7 +772,7 @@ export class MarkersView extends FilterViewPane implements IMarkersView { private updateRangeHighlights() { this.rangeHighlightDecorations.removeHighlightRange(); - if (this.widget.getHTMLElement() === document.activeElement) { + if (dom.isActiveElement(this.widget.getHTMLElement())) { this.highlightCurrentSelectedMarkerRange(); } } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts b/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts index 8a240876cba..a6ede9416aa 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts @@ -30,7 +30,7 @@ import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { ILogService } from 'vs/platform/log/common/log'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { showWindowLogActionId } from 'vs/workbench/services/log/common/logConstants'; -import { getActiveElement } from 'vs/base/browser/dom'; +import { getActiveElement, getWindow } from 'vs/base/browser/dom'; let _logging: boolean = false; function toggleLogging() { @@ -309,7 +309,7 @@ export class NotebookClipboardContribution extends Disposable { } private _focusInsideEmebedMonaco(editor: INotebookEditor) { - const windowSelection = window.getSelection(); + const windowSelection = getWindow(editor.getDomNode()).getSelection(); if (windowSelection?.rangeCount !== 1) { return false; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 03772b8851d..e2cceb4b1ec 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -1996,7 +1996,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD return false; } - const windowSelection = window.getSelection(); + const windowSelection = DOM.getWindow(this.getDomNode()).getSelection(); if (windowSelection?.rangeCount !== 1) { return false; } diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css index e5dd093c6bd..a2bfc42d085 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -531,7 +531,11 @@ line-height: 15px; /** For some reason, this is needed, otherwise will take up 20px height */ font-family: var(--monaco-monospace-font); + font-size: 11px; color: var(--vscode-textPreformat-foreground); + background-color: var(--vscode-textPreformat-background); + padding: 1px 3px; + border-radius: 4px; } .settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-markdown .monaco-tokenized-source { @@ -597,7 +601,12 @@ } .monaco-select-box-dropdown-container > .select-box-details-pane > .select-box-description-markdown code { + font-family: var(--monaco-monospace-font); + font-size: 12px; color: var(--vscode-textPreformat-foreground); + background-color: var(--vscode-textPreformat-background); + padding: 2px 5px; + border-radius: 4px; } .monaco-select-box-dropdown-container > .select-box-details-pane > .select-box-description-markdown a, diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index 377191ab525..924f9f477a1 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -33,6 +33,7 @@ import { MANAGE_TRUST_COMMAND_ID, WorkspaceTrustContext } from 'vs/workbench/con import { IQuickDiffService } from 'vs/workbench/contrib/scm/common/quickDiff'; import { QuickDiffService } from 'vs/workbench/contrib/scm/common/quickDiffService'; import { SCMSyncViewPane } from 'vs/workbench/contrib/scm/browser/scmSyncViewPane'; +import { getActiveElement } from 'vs/base/browser/dom'; ModesRegistry.registerLanguage({ id: 'scminput', @@ -227,7 +228,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis localize('scm.providerCountBadge.auto', "Only show count badge for Source Control Provider when non-zero."), localize('scm.providerCountBadge.visible', "Show Source Control Provider count badges.") ], - description: localize('scm.providerCountBadge', "Controls the count badges on Source Control Provider headers. These headers only appear when there is more than one provider."), + markdownDescription: localize('scm.providerCountBadge', "Controls the count badges on Source Control Provider headers. These headers appear in the \"Source Control\", and \"Source Control Sync\" views when there is more than one provider or when the {0} setting is enabled, as well as in the \"Source Control Repositories\" view.", '\`#scm.alwaysShowRepositories#\`'), default: 'hidden' }, 'scm.defaultViewMode': { @@ -308,7 +309,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ primary: KeyMod.CtrlCmd | KeyCode.Enter, handler: accessor => { const contextKeyService = accessor.get(IContextKeyService); - const context = contextKeyService.getContext(document.activeElement); + const context = contextKeyService.getContext(getActiveElement()); const repositoryId = context.getValue('scmRepository'); if (!repositoryId) { @@ -336,7 +337,7 @@ const viewNextCommitCommand = { handler: (accessor: ServicesAccessor) => { const contextKeyService = accessor.get(IContextKeyService); const scmService = accessor.get(ISCMService); - const context = contextKeyService.getContext(document.activeElement); + const context = contextKeyService.getContext(getActiveElement()); const repositoryId = context.getValue('scmRepository'); const repository = repositoryId ? scmService.getRepository(repositoryId) : undefined; repository?.input.showNextHistoryValue(); @@ -349,7 +350,7 @@ const viewPreviousCommitCommand = { handler: (accessor: ServicesAccessor) => { const contextKeyService = accessor.get(IContextKeyService); const scmService = accessor.get(ISCMService); - const context = contextKeyService.getContext(document.activeElement); + const context = contextKeyService.getContext(getActiveElement()); const repositoryId = context.getValue('scmRepository'); const repository = repositoryId ? scmService.getRepository(repositoryId) : undefined; repository?.input.showPreviousHistoryValue(); diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts index ab4e907d21b..3cd3d80fb18 100644 --- a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts @@ -5,6 +5,7 @@ import 'vs/css!./media/scm'; import { localize } from 'vs/nls'; +import { Event } from 'vs/base/common/event'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane'; import { append, $ } from 'vs/base/browser/dom'; import { IListVirtualDelegate, IListContextMenuEvent, IListEvent } from 'vs/base/browser/ui/list/list'; @@ -24,6 +25,7 @@ import { RepositoryRenderer } from 'vs/workbench/contrib/scm/browser/scmReposito import { collectContextMenuActions, getActionViewItemProvider } from 'vs/workbench/contrib/scm/browser/util'; import { Orientation } from 'vs/base/browser/ui/sash/sash'; import { Iterable } from 'vs/base/common/iterator'; +import { DisposableStore } from 'vs/base/common/lifecycle'; class ListDelegate implements IListVirtualDelegate { @@ -39,6 +41,7 @@ class ListDelegate implements IListVirtualDelegate { export class SCMRepositoriesViewPane extends ViewPane { private list!: WorkbenchList; + private readonly disposables = new DisposableStore(); constructor( options: IViewPaneOptions, @@ -61,6 +64,14 @@ export class SCMRepositoriesViewPane extends ViewPane { const listContainer = append(container, $('.scm-view.scm-repositories-view')); + const updateProviderCountVisibility = () => { + const value = this.configurationService.getValue<'hidden' | 'auto' | 'visible'>('scm.providerCountBadge'); + listContainer.classList.toggle('hide-provider-counts', value === 'hidden'); + listContainer.classList.toggle('auto-provider-counts', value === 'auto'); + }; + this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.providerCountBadge'), this.disposables)(updateProviderCountVisibility)); + updateProviderCountVisibility(); + const delegate = new ListDelegate(); const renderer = this.instantiationService.createInstance(RepositoryRenderer, getActionViewItemProvider(this.instantiationService)); const identityProvider = { getId: (r: ISCMRepository) => r.provider.id }; @@ -179,4 +190,9 @@ export class SCMRepositoriesViewPane extends ViewPane { this.list.setFocus([selection[0]]); } } + + override dispose(): void { + this.disposables.dispose(); + super.dispose(); + } } diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index f1434f91db9..6bc3f38e680 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -1072,7 +1072,7 @@ export class SearchView extends ViewPane { } private updateTextFromFindWidget(controller: CommonFindController, { allowSearchOnType = true }): boolean { - if (!this.searchConfig.seedWithNearestWord && (window.getSelection()?.toString() ?? '') === '') { + if (!this.searchConfig.seedWithNearestWord && (dom.getActiveWindow().getSelection()?.toString() ?? '') === '') { return false; } @@ -1275,7 +1275,7 @@ export class SearchView extends ViewPane { } private getSearchTextFromEditor(allowUnselectedWord: boolean, editor?: IEditor): string | null { - if (dom.isAncestor(document.activeElement, this.getContainer())) { + if (dom.isAncestorOfActiveElement(this.getContainer())) { return null; } diff --git a/src/vs/workbench/contrib/speech/common/speechService.ts b/src/vs/workbench/contrib/speech/common/speechService.ts index c071ce0746d..021a676e580 100644 --- a/src/vs/workbench/contrib/speech/common/speechService.ts +++ b/src/vs/workbench/contrib/speech/common/speechService.ts @@ -47,6 +47,8 @@ export interface ISpeechService { readonly onDidRegisterSpeechProvider: Event; readonly onDidUnregisterSpeechProvider: Event; + readonly hasSpeechProvider: boolean; + registerSpeechProvider(identifier: string, provider: ISpeechProvider): IDisposable; createSpeechToTextSession(token: CancellationToken): ISpeechToTextSession; @@ -62,6 +64,8 @@ export class SpeechService implements ISpeechService { private readonly _onDidUnregisterSpeechProvider = new Emitter(); readonly onDidUnregisterSpeechProvider = this._onDidUnregisterSpeechProvider.event; + get hasSpeechProvider(): boolean { return this.providers.size > 0; } + private readonly providers = new Map(); constructor(@ILogService private readonly logService: ILogService) { } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 66e6b70ea35..751c368ded3 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -865,6 +865,12 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // The container changed, reattach this._container = container; this._container.appendChild(this._wrapperElement); + + // If xterm is already attached, call open again to pick up any changes to the window. + if (this.xterm?.raw.element) { + this.xterm.raw.open(this.xterm.raw.element); + } + this.xterm?.refresh(); setTimeout(() => this._initDragAndDrop(container)); diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts index 057c0ab530f..0540afae829 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts @@ -3,11 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Emitter } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IModelService } from 'vs/editor/common/services/model'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ResultKind } from 'vs/platform/keybinding/common/keybindingResolver'; 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'; @@ -28,17 +31,18 @@ export class TerminalAccessibleBufferProvider extends DisposableStore implements private _bufferTracker: BufferContentTracker, customHelp: () => string, @IModelService _modelService: IModelService, - @IConfigurationService _configurationService: IConfigurationService, + @IConfigurationService configurationService: IConfigurationService, @IContextKeyService _contextKeyService: IContextKeyService, - @ITerminalService _terminalService: ITerminalService + @ITerminalService _terminalService: ITerminalService, + @IKeybindingService private readonly _keybindingService: IKeybindingService ) { super(); this.options.customHelp = customHelp; - this.options.position = _configurationService.getValue(TerminalSettingId.AccessibleViewPreserveCursorPosition) ? 'initial-bottom' : 'bottom'; + 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 => { + this.add(configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(TerminalSettingId.AccessibleViewPreserveCursorPosition)) { - this.options.position = _configurationService.getValue(TerminalSettingId.AccessibleViewPreserveCursorPosition) ? 'initial-bottom' : 'bottom'; + this.options.position = configurationService.getValue(TerminalSettingId.AccessibleViewPreserveCursorPosition) ? 'initial-bottom' : 'bottom'; } })); this._focusedInstance = _terminalService.activeInstance; @@ -50,6 +54,13 @@ export class TerminalAccessibleBufferProvider extends DisposableStore implements })); } + onKeyDown(e: IKeyboardEvent): void { + if (!shouldFocusTerminal(e.browserEvent, this._keybindingService)) { + return; + } + this._instance.focus(); + } + onClose() { this._instance.focus(); } @@ -115,3 +126,14 @@ export class TerminalAccessibleBufferProvider extends DisposableStore implements } } export interface ICommandWithEditorLine { command: ITerminalCommand | ICurrentPartialCommand; lineNumber: number } + +function shouldFocusTerminal(event: KeyboardEvent, keybindingService: IKeybindingService): boolean { + const standardKeyboardEvent = new StandardKeyboardEvent(event); + const resolveResult = keybindingService.softDispatch(standardKeyboardEvent, standardKeyboardEvent.target); + + const isValidChord = resolveResult.kind === ResultKind.MoreChordsNeeded; + if (keybindingService.inChordMode || isValidChord) { + return false; + } + return event.key.length === 1 && !event.ctrlKey && !event.altKey && !event.metaKey && !event.shiftKey; +} diff --git a/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThroughPart.ts b/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThroughPart.ts index 002c6ef9c5f..29f5aace84c 100644 --- a/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThroughPart.ts +++ b/src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThroughPart.ts @@ -32,7 +32,7 @@ import { UILabelProvider } from 'vs/base/common/keybindingLabels'; import { OS, OperatingSystem } from 'vs/base/common/platform'; import { deepClone } from 'vs/base/common/objects'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { addDisposableListener, Dimension, safeInnerHtml, size } from 'vs/base/browser/dom'; +import { addDisposableListener, Dimension, getWindow, safeInnerHtml, size } from 'vs/base/browser/dom'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -156,7 +156,7 @@ export class WalkThroughPart extends EditorPane { this.content.addEventListener('click', event => { for (let node = event.target as HTMLElement; node; node = node.parentNode as HTMLElement) { if (node instanceof HTMLAnchorElement && node.href) { - const baseElement = window.document.getElementsByTagName('base')[0] || window.location; + const baseElement = node.ownerDocument.getElementsByTagName('base')[0] || getWindow(node).location; if (baseElement && node.href.indexOf(baseElement.href) >= 0 && node.hash) { const scrollTarget = this.content.querySelector(node.hash); const innerContent = this.content.firstElementChild; diff --git a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts index 200a6e33a42..02d63db45ad 100644 --- a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts @@ -408,9 +408,9 @@ suite('EditorGroupsService', () => { const currentOptions = part.partOptions; assert.ok(currentOptions); - disposables.add(part.enforcePartOptions({ showTabs: false })); - assert.strictEqual(part.partOptions.showTabs, false); - assert.strictEqual(newOptions.showTabs, false); + disposables.add(part.enforcePartOptions({ showTabs: 'single' })); + assert.strictEqual(part.partOptions.showTabs, 'single'); + assert.strictEqual(newOptions.showTabs, 'single'); assert.strictEqual(oldOptions, currentOptions); }); diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index 512cca25488..89022b946b7 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -687,6 +687,10 @@ export class DefaultSettings extends Disposable { } } + if (!enumToUse && (prop.enumItemLabels || enumDescriptions || enumDescriptionsAreMarkdown)) { + console.error(`The setting ${key} has enum-related fields, but doesn't have an enum field. This setting may render improperly in the Settings editor.`); + } + result.push({ key, value, @@ -706,6 +710,7 @@ export class DefaultSettings extends Disposable { enum: enumToUse, enumDescriptions: enumDescriptions, enumDescriptionsAreMarkdown: enumDescriptionsAreMarkdown, + enumItemLabels: prop.enumItemLabels, uniqueItems: prop.uniqueItems, tags: prop.tags, disallowSyncIgnore: prop.disallowSyncIgnore, @@ -714,7 +719,6 @@ export class DefaultSettings extends Disposable { deprecationMessage: prop.markdownDeprecationMessage || prop.deprecationMessage, deprecationMessageIsMarkdown: !!prop.markdownDeprecationMessage, validator: createValidator(prop), - enumItemLabels: prop.enumItemLabels, allKeysAreBoolean, editPresentation: prop.editPresentation, order: prop.order, @@ -1058,7 +1062,7 @@ class SettingsContentBuilder { setting.descriptionRanges.push({ startLineNumber: this.lineCountWithOffset, startColumn: this.lastLine.indexOf(line) + 1, endLineNumber: this.lineCountWithOffset, endColumn: this.lastLine.length }); } - if (setting.enumDescriptions && setting.enumDescriptions.some(desc => !!desc)) { + if (setting.enum && setting.enumDescriptions?.some(desc => !!desc)) { setting.enumDescriptions.forEach((desc, i) => { const displayEnum = escapeInvisibleChars(String(setting.enum![i])); const line = desc ? diff --git a/src/vs/workbench/services/voiceRecognition/electron-sandbox/voiceTranscriptionWorklet.ts b/src/vs/workbench/services/voiceRecognition/electron-sandbox/voiceTranscriptionWorklet.ts deleted file mode 100644 index cb0083c81eb..00000000000 --- a/src/vs/workbench/services/voiceRecognition/electron-sandbox/voiceTranscriptionWorklet.ts +++ /dev/null @@ -1,127 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -declare class AudioWorkletProcessor { - - readonly port: MessagePort; - - process(inputs: [Float32Array[]], outputs: [Float32Array[]]): boolean; -} - -interface IVoiceTranscriptionWorkletOptions extends AudioWorkletNodeOptions { - processorOptions: { - readonly bufferTimespan: number; - readonly vadThreshold: number; - }; -} - -class VoiceTranscriptionWorklet extends AudioWorkletProcessor { - - private startTime: number | undefined = undefined; - private stopped: boolean = false; - - private buffer: Float32Array[] = []; - - private sharedProcessConnection: MessagePort | undefined = undefined; - - constructor(private readonly options: IVoiceTranscriptionWorkletOptions) { - super(); - - this.registerListeners(); - } - - private registerListeners() { - this.port.onmessage = event => { - switch (event.data) { - case 'vscode:startVoiceTranscription': { - this.sharedProcessConnection = event.ports[0]; - - this.sharedProcessConnection.onmessage = event => { - if (this.stopped) { - return; - } - - if (typeof event.data === 'string') { - this.port.postMessage(event.data); - } - }; - - this.sharedProcessConnection.start(); - break; - } - - case 'vscode:stopVoiceTranscription': { - this.stopped = true; - - this.sharedProcessConnection?.close(); - this.sharedProcessConnection = undefined; - - break; - } - } - }; - } - - override process(inputs: [Float32Array[]]): boolean { - if (this.startTime === undefined) { - this.startTime = Date.now(); - } - - const inputChannelData = inputs[0][0]; - if ((!(inputChannelData instanceof Float32Array))) { - return !this.stopped; - } - - this.buffer.push(inputChannelData.slice(0)); - - if (Date.now() - this.startTime > this.options.processorOptions.bufferTimespan && this.sharedProcessConnection) { - const buffer = this.joinFloat32Arrays(this.buffer); - this.buffer = []; - - // Send buffer to shared process for transcription. - // Send an empty buffer if it appears to be silence - // so that we can still trigger the transcription - // service and let it know about this. - - this.sharedProcessConnection.postMessage(this.appearsToBeSilence(buffer) ? new Float32Array(0) : buffer); - - this.startTime = Date.now(); - } - - return !this.stopped; - } - - private appearsToBeSilence(data: Float32Array): boolean { - - // This is the most simple Voice Activity Detection (VAD) - // and it is based on the Root Mean Square (RMS) of the signal - // with a certain threshold. Good for testing but probably - // not suitable for shipping to stable (TODO@bpasero). - - let sum = 0; - for (let i = 0; i < data.length; i++) { - sum += data[i] * data[i]; - } - - const rms = Math.sqrt(sum / data.length); - - return rms < this.options.processorOptions.vadThreshold; - } - - private joinFloat32Arrays(float32Arrays: Float32Array[]): Float32Array { - const result = new Float32Array(float32Arrays.reduce((prev, curr) => prev + curr.length, 0)); - - let offset = 0; - for (const float32Array of float32Arrays) { - result.set(float32Array, offset); - offset += float32Array.length; - } - - return result; - } -} - -// @ts-ignore -registerProcessor('voice-transcription-worklet', VoiceTranscriptionWorklet); diff --git a/src/vs/workbench/services/voiceRecognition/electron-sandbox/workbenchVoiceRecognitionService.ts b/src/vs/workbench/services/voiceRecognition/electron-sandbox/workbenchVoiceRecognitionService.ts deleted file mode 100644 index 0f279ac26ea..00000000000 --- a/src/vs/workbench/services/voiceRecognition/electron-sandbox/workbenchVoiceRecognitionService.ts +++ /dev/null @@ -1,208 +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 { localize } from 'vs/nls'; -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { Emitter, Event } from 'vs/base/common/event'; -import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; -import { DeferredPromise } from 'vs/base/common/async'; -import { FileAccess } from 'vs/base/common/network'; -import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; -import { INotificationService } from 'vs/platform/notification/common/notification'; - -export const IWorkbenchVoiceRecognitionService = createDecorator('workbenchVoiceRecognitionService'); - -export interface IWorkbenchVoiceRecognitionOptions { - - /** - * Optional event that is fired when the user cancels the voice recognition. - */ - readonly onDidCancel?: () => void; -} - -export interface IWorkbenchVoiceRecognitionService { - - readonly _serviceBrand: undefined; - - /** - * Starts listening to the microphone transcribing the voice to text. Microphone - * recording starts when the returned promise is resolved. - * - * @param cancellation a cancellation token to stop transcribing and - * listening to the microphone. - */ - transcribe(cancellation: CancellationToken, options?: IWorkbenchVoiceRecognitionOptions): Promise>; -} - -interface IVoiceTranscriptionWorkletOptions extends AudioWorkletNodeOptions { - processorOptions: { - readonly bufferTimespan: number; - readonly vadThreshold: number; - }; -} - -class VoiceTranscriptionWorkletNode extends AudioWorkletNode { - - constructor( - context: BaseAudioContext, - options: IVoiceTranscriptionWorkletOptions, - private readonly onDidTranscribe: Emitter, - private readonly sharedProcessService: ISharedProcessService - ) { - super(context, 'voice-transcription-worklet', options); - - this.registerListeners(); - } - - private registerListeners(): void { - this.port.onmessage = e => { - if (typeof e.data === 'string') { - this.onDidTranscribe.fire(e.data); - } - }; - } - - async start(token: CancellationToken): Promise { - token.onCancellationRequested(() => this.stop()); - - const sharedProcessConnection = await this.sharedProcessService.createRawConnection(); - - if (token.isCancellationRequested) { - this.stop(); - return; - } - - this.port.postMessage('vscode:startVoiceTranscription', [sharedProcessConnection]); - } - - private stop(): void { - this.port.postMessage('vscode:stopVoiceTranscription'); - this.disconnect(); - } -} - -export class WorkbenchVoiceRecognitionService implements IWorkbenchVoiceRecognitionService { - - declare readonly _serviceBrand: undefined; - - private static readonly AUDIO_SAMPLING_RATE = 16000; - private static readonly AUDIO_BIT_DEPTH = 16; - private static readonly AUDIO_CHANNELS = 1; - - private static readonly BUFFER_TIMESPAN = 1000; - private static readonly VAD_THRESHOLD = 0.02; - - constructor( - @IProgressService private readonly progressService: IProgressService, - @ISharedProcessService private readonly sharedProcessService: ISharedProcessService, - @INotificationService private readonly notificationService: INotificationService - ) { } - - async transcribe(cancellation: CancellationToken, options?: IWorkbenchVoiceRecognitionOptions): Promise> { - const cts = new CancellationTokenSource(cancellation); - - const onDidTranscribe = new Emitter(); - cts.token.onCancellationRequested(() => { - onDidTranscribe.dispose(); - options?.onDidCancel?.(); - }); - - await this.doTranscribe(onDidTranscribe, cts); - - return onDidTranscribe.event; - } - - private doTranscribe(onDidTranscribe: Emitter, cts: CancellationTokenSource): Promise { - const recordingReady = new DeferredPromise(); - cts.token.onCancellationRequested(() => recordingReady.complete()); - - this.progressService.withProgress({ - location: ProgressLocation.Window, - title: localize('voiceTranscription', "Voice Transcription"), - cancellable: true - }, async progress => { - const recordingDone = new DeferredPromise(); - try { - progress.report({ message: localize('voiceTranscriptionGettingReady', "Getting microphone ready...") }); - - const microphoneDevice = await navigator.mediaDevices.getUserMedia({ - audio: { - sampleRate: WorkbenchVoiceRecognitionService.AUDIO_SAMPLING_RATE, - sampleSize: WorkbenchVoiceRecognitionService.AUDIO_BIT_DEPTH, - channelCount: WorkbenchVoiceRecognitionService.AUDIO_CHANNELS, - autoGainControl: true, - noiseSuppression: true, - echoCancellation: false - } - }); - - if (cts.token.isCancellationRequested) { - return; - } - - const audioContext = new AudioContext({ - sampleRate: WorkbenchVoiceRecognitionService.AUDIO_SAMPLING_RATE, - latencyHint: 'interactive' - }); - - const microphoneSource = audioContext.createMediaStreamSource(microphoneDevice); - - cts.token.onCancellationRequested(() => { - try { - for (const track of microphoneDevice.getTracks()) { - track.stop(); - } - - microphoneSource.disconnect(); - audioContext.close(); - } finally { - recordingDone.complete(); - } - }); - - await audioContext.audioWorklet.addModule(FileAccess.asBrowserUri('vs/workbench/services/voiceRecognition/electron-sandbox/voiceTranscriptionWorklet.js').toString(true)); - - if (cts.token.isCancellationRequested) { - return; - } - - const voiceTranscriptionTarget = new VoiceTranscriptionWorkletNode(audioContext, { - channelCount: WorkbenchVoiceRecognitionService.AUDIO_CHANNELS, - channelCountMode: 'explicit', - processorOptions: { - bufferTimespan: WorkbenchVoiceRecognitionService.BUFFER_TIMESPAN, - vadThreshold: WorkbenchVoiceRecognitionService.VAD_THRESHOLD - } - }, onDidTranscribe, this.sharedProcessService); - await voiceTranscriptionTarget.start(cts.token); - - if (cts.token.isCancellationRequested) { - return; - } - - microphoneSource.connect(voiceTranscriptionTarget); - - progress.report({ message: localize('voiceTranscriptionRecording', "Recording from microphone...") }); - recordingReady.complete(); - - return recordingDone.p; - } catch (error) { - this.notificationService.error(localize('voiceTranscriptionError', "Voice transcription failed: {0}", error.message)); - - recordingReady.error(error); - recordingDone.error(error); - } - }, () => { - cts.cancel(); - }); - - return recordingReady.p; - } -} - -// Register Service -registerSingleton(IWorkbenchVoiceRecognitionService, WorkbenchVoiceRecognitionService, InstantiationType.Delayed); diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 4f5ab26bb35..f590f1f8815 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -75,7 +75,6 @@ import 'vs/workbench/services/environment/electron-sandbox/shellEnvironmentServi import 'vs/workbench/services/integrity/electron-sandbox/integrityService'; import 'vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService'; import 'vs/workbench/services/checksum/electron-sandbox/checksumService'; -import 'vs/workbench/services/voiceRecognition/electron-sandbox/workbenchVoiceRecognitionService'; import 'vs/platform/remote/electron-sandbox/sharedProcessTunnelService'; import 'vs/workbench/services/tunnel/electron-sandbox/tunnelService'; import 'vs/platform/diagnostics/electron-sandbox/diagnosticsService'; diff --git a/src/vscode-dts/vscode.proposed.interactiveUserActions.d.ts b/src/vscode-dts/vscode.proposed.interactiveUserActions.d.ts index f9b400c1d94..e038f1f2ed6 100644 --- a/src/vscode-dts/vscode.proposed.interactiveUserActions.d.ts +++ b/src/vscode-dts/vscode.proposed.interactiveUserActions.d.ts @@ -64,7 +64,7 @@ declare module 'vscode' { export interface InteractiveSessionFollowupAction { // eslint-disable-next-line local/vscode-dts-string-type-literals kind: 'followUp'; - followup: InteractiveSessionFollowup; + followup: InteractiveSessionReplyFollowup; } export type InteractiveSessionUserAction = InteractiveSessionVoteAction | InteractiveSessionCopyAction | InteractiveSessionInsertAction | InteractiveSessionTerminalAction | InteractiveSessionCommandAction; diff --git a/yarn.lock b/yarn.lock index 6f519ddb78f..9937e7f3471 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10375,45 +10375,45 @@ xtend@~4.0.0, xtend@~4.0.1: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -xterm-addon-canvas@0.6.0-beta.31: - version "0.6.0-beta.31" - resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.6.0-beta.31.tgz#04ebde31c5e01b2595b966a2861deaec9927e1cb" - integrity sha512-/Dz90IF5FQqzAitKi3k/JEyyRMhSuQG8PVtB2NwOlWUcE3Ukp6gJMFdkyfOOt0Lx/8oyWR7xoDgKY3bxbzpkGQ== +xterm-addon-canvas@0.6.0-beta.32: + version "0.6.0-beta.32" + resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.6.0-beta.32.tgz#c9e74dd72fcc981a2e0cbd0b82827676bc5c74b9" + integrity sha512-Xw7oE4dbS+x+pu6cGW1bDSXcVviuorLz1OLaYw46jjmDezIqQIIEMhSMOprExFEWgeRQ9AEN4lPqw6aH87V74w== xterm-addon-image@0.6.0-beta.21: version "0.6.0-beta.21" resolved "https://registry.yarnpkg.com/xterm-addon-image/-/xterm-addon-image-0.6.0-beta.21.tgz#e3708bc504c56a23ff31f12a2eeb335331a92aac" integrity sha512-8/PTaXVPa4kQ0xzVeuZZk10OpbZBj2cgfwhM2B0ChSPvwrk0lX+ksnXdtDKH3tg+JYvo7fIhNXtkr4NwWt7VJQ== -xterm-addon-search@0.14.0-beta.30: - version "0.14.0-beta.30" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.14.0-beta.30.tgz#a84321ded127ab13a0bdbf901d2524900330f6ea" - integrity sha512-e5qb68lmpxQ1cG4oJKq9NC61oV2xGynRyruB2luerGeXPhqkGj9RSDeOqgCWbnQNTfBmkROzrn02MeJAsoqvGQ== +xterm-addon-search@0.14.0-beta.31: + version "0.14.0-beta.31" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.14.0-beta.31.tgz#933ca5d2d642dacad29f2cfbd50830cff83bc274" + integrity sha512-JRY1ukhoh32D0AMz78xpumQkLgkcP9d3GXj6gzVHZZsjLAMDaJYEubYq1bUhM7IGHUyg+x0sdRJyx7d6fJpiQg== -xterm-addon-serialize@0.12.0-beta.30: - version "0.12.0-beta.30" - resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.12.0-beta.30.tgz#80c4101f45a463ef139705bcd3dcaf0811f51ea4" - integrity sha512-nZP0ip5bd9LBoCTN9vCnn4iLatF4RRwzLupQf9r2N9x1bULzTZ1kAXAQe5gghsXjSEDDtyY2LzGigqTd2KVAqQ== +xterm-addon-serialize@0.12.0-beta.31: + version "0.12.0-beta.31" + resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.12.0-beta.31.tgz#2a95dc1e12f4097e2894b04c9cb8fff0bc0b858c" + integrity sha512-h2rWR+Lfi1Iv4VkLUlrBMYh5Mdq8vux2BKyCJe6a1ZnEu5Dzb0VuiNxfTKXTCT5M83nMn7TCB9TX0E8z6bs7xw== -xterm-addon-unicode11@0.7.0-beta.30: - version "0.7.0-beta.30" - resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.7.0-beta.30.tgz#2de2c412d41823f31b66f68c7d8d0fb9e1a66cd3" - integrity sha512-pLSSBxwCOD5aShGnk6VveLHpjDwEDrIci2WnVcuWIbPaqHkB16d6l17jJ50843TaW66k1Np3ZCpDteOoC0Z6Kw== +xterm-addon-unicode11@0.7.0-beta.31: + version "0.7.0-beta.31" + resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.7.0-beta.31.tgz#abcba752172323f31312bd8a3f9b6a049dbca6e3" + integrity sha512-vvBKJbBoLbeIf2++6D16VnOOwevZE3nyO/PDZ7cyTJK1eYR73rr8ZbjUrH92YoTu4Z8MpZFepGQOgK/vlAQMwQ== -xterm-addon-webgl@0.17.0-beta.30: - version "0.17.0-beta.30" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.17.0-beta.30.tgz#e4d7b18eb8f2b0be6ee8bf35185e91b33570e67f" - integrity sha512-SjdfIOmx9xunom2Bk//iQ2DoqYlvAsunEWD3nxdED0oYYf1SPlKxt3I47YHWVshacw6QPZEJHVXJ6K+kHlel/Q== +xterm-addon-webgl@0.17.0-beta.31: + version "0.17.0-beta.31" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.17.0-beta.31.tgz#3cd29b4858e3f4f6dd5a8dd969454e85e1f43baa" + integrity sha512-vYHj+HlTcqUlFFVuoCTjlgh89/lIoSkZ7Nc87cwSFTrJsl07qoKutmpupqFXyjhbEA1fQY2SuQLx08Gmf2jWkQ== -xterm-headless@5.4.0-beta.31: - version "5.4.0-beta.31" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.4.0-beta.31.tgz#9538553c7426222f94d7da7ed467e699ebaeeedd" - integrity sha512-EE/ZlsZcBE5VOkjQU/KdRL4gvSkfrC2P7VxrmK1+PLc6+QMjPxs60A4Pun3mIIS0MFfN23p6hmN22GAXVckCXA== +xterm-headless@5.4.0-beta.32: + version "5.4.0-beta.32" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.4.0-beta.32.tgz#0d5cd35e1a0372888055ff0b06dfe17457979a6c" + integrity sha512-DQduq8KSoQZyRrQAFB+FkcY2UMxCW39P1/duOpksebc6PT9pbGkyPe5s+AdUQGiYzriEpzVtKUzDcquoVmpPhA== -xterm@5.4.0-beta.31: - version "5.4.0-beta.31" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.4.0-beta.31.tgz#598f66cfa49609d4e4935fbaf00aadff8e23d174" - integrity sha512-lAuiiWxxU8s0UaDwuJZupoBOtb9bY5ouBkOufnfpLK05ACm0046TPxs3bg05jPUI8y5y/qLgKqK0L5TxAiZ8WA== +xterm@5.4.0-beta.32: + version "5.4.0-beta.32" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.4.0-beta.32.tgz#1b4242cf1c0c1a5a1070da58d3f11956b537130a" + integrity sha512-mWTwEiNBFMF89oqVfi6qTM2Py5gC1Mwvslx1KxmI2Ukgh9v3CrqKDhj29eY1ZeAo0uuYknFWKyuexqp+3SHJCA== y18n@^3.2.1: version "3.2.2"