diff --git a/.configurations/configuration.dsc.yaml b/.configurations/configuration.dsc.yaml index 41839b86f23249..255da69a5bb7ba 100644 --- a/.configurations/configuration.dsc.yaml +++ b/.configurations/configuration.dsc.yaml @@ -12,11 +12,11 @@ properties: - resource: Microsoft.WinGet.DSC/WinGetPackage id: npm directives: - description: Install NodeJS version >=16.17.x and <17 + description: Install NodeJS version >=18.15.x and <19 allowPrerelease: true settings: id: OpenJS.NodeJS.LTS - version: "16.20.0" + version: "18.18.0" source: winget - resource: NpmDsc/NpmPackage id: yarn diff --git a/.vscode/notebooks/endgame.github-issues b/.vscode/notebooks/endgame.github-issues index 5bd52b7826ec76..254d74b132f456 100644 --- a/.vscode/notebooks/endgame.github-issues +++ b/.vscode/notebooks/endgame.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-livepreview repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-l10n repo:microsoft/vscode-remote-tunnels\n\n$MILESTONE=milestone:\"August 2023\"" + "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-livepreview repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-l10n repo:microsoft/vscode-remote-tunnels\r\n\r\n$MILESTONE=milestone:\"September 2023\"" }, { "kind": 1, diff --git a/.vscode/notebooks/my-endgame.github-issues b/.vscode/notebooks/my-endgame.github-issues index 6978af0b6f0900..0419c013063088 100644 --- a/.vscode/notebooks/my-endgame.github-issues +++ b/.vscode/notebooks/my-endgame.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-livepreview repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-l10n repo:microsoft/vscode-remote-tunnels\r\n\r\n$MILESTONE=milestone:\"August 2023\"\r\n\r\n$MINE=assignee:@me" + "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-livepreview repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-l10n repo:microsoft/vscode-remote-tunnels\n\n$MILESTONE=milestone:\"September 2023\"\n\n$MINE=assignee:@me\n" }, { "kind": 1, @@ -22,7 +22,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:pr is:open" + "value": "$REPOS $MILESTONE $MINE is:pr is:open\n" }, { "kind": 1, @@ -32,7 +32,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue is:open -label:iteration-plan -label:endgame-plan -label:testplan-item" + "value": "$REPOS $MILESTONE $MINE is:issue is:open -label:iteration-plan -label:endgame-plan -label:testplan-item\n" }, { "kind": 1, @@ -42,7 +42,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:feature-request -label:verification-needed -label:on-testplan -label:verified -label:*duplicate" + "value": "$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:feature-request -label:verification-needed -label:on-testplan -label:verified -label:*duplicate\n" }, { "kind": 1, @@ -52,7 +52,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS is:issue is:open author:@me label:testplan-item" + "value": "$REPOS is:issue is:open author:@me label:testplan-item\n" }, { "kind": 1, @@ -62,7 +62,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:feature-request label:verification-needed -label:verified" + "value": "$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:feature-request label:verification-needed -label:verified\n" }, { "kind": 1, @@ -77,7 +77,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MINE is:issue is:open label:testplan-item" + "value": "$REPOS $MINE is:issue is:open label:testplan-item\n" }, { "kind": 1, @@ -87,7 +87,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed -assignee:@me -label:verified -label:z-author-verified label:feature-request label:verification-needed -label:verification-steps-needed -label:unreleased" + "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed -assignee:@me -label:verified -label:z-author-verified label:feature-request label:verification-needed -label:verification-steps-needed -label:unreleased\n" }, { "kind": 1, @@ -102,7 +102,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue is:open -label:endgame-plan -label:testplan-item -label:iteration-plan" + "value": "$REPOS $MILESTONE $MINE is:issue is:open -label:endgame-plan -label:testplan-item -label:iteration-plan\n" }, { "kind": 1, @@ -112,7 +112,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue is:open label:bug" + "value": "$REPOS $MILESTONE $MINE is:issue is:open label:bug\n" }, { "kind": 1, @@ -127,7 +127,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue label:bug label:verification-steps-needed" + "value": "$REPOS $MILESTONE $MINE is:issue label:bug label:verification-steps-needed\n" }, { "kind": 1, @@ -137,7 +137,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue label:bug label:verification-found" + "value": "$REPOS $MILESTONE $MINE is:issue label:bug label:verification-found\n" }, { "kind": 1, @@ -147,7 +147,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed author:@me sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:triage-needed -label:verification-found" + "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed author:@me sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:triage-needed -label:verification-found\n" }, { "kind": 1, @@ -157,7 +157,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found -author:aeschli -author:alexdima -author:alexr00 -author:AmandaSilver -author:andreamah -author:bamurtaugh -author:bpasero -author:chrisdias -author:chrmarti -author:Chuxel -author:claudiaregio -author:connor4312 -author:dbaeumer -author:deepak1556 -author:devinvalenciano -author:digitarald -author:DonJayamanne -author:egamma -author:fiveisprime -author:gregvanl -author:hediet -author:isidorn -author:joaomoreno -author:joyceerhl -author:jrieken -author:karrtikr -author:kieferrm -author:lramos15 -author:lszomoru -author:meganrogge -author:misolori -author:mjbvz -author:rebornix -author:roblourens -author:rzhao271 -author:sandy081 -author:sbatten -author:stevencl -author:tanhakabir -author:TylerLeonhardt -author:Tyriar -author:weinand -author:amunger -author:karthiknadig -author:eleanorjboyd -author:Yoyokrazy -author:paulacamargo25 -author:ulugbekna -author:aiday-mar" + "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found -author:aeschli -author:alexdima -author:alexr00 -author:AmandaSilver -author:andreamah -author:bamurtaugh -author:bpasero -author:chrisdias -author:chrmarti -author:Chuxel -author:claudiaregio -author:connor4312 -author:dbaeumer -author:deepak1556 -author:devinvalenciano -author:digitarald -author:DonJayamanne -author:egamma -author:fiveisprime -author:gregvanl -author:hediet -author:isidorn -author:joaomoreno -author:joyceerhl -author:jrieken -author:karrtikr -author:kieferrm -author:lramos15 -author:lszomoru -author:meganrogge -author:misolori -author:mjbvz -author:rebornix -author:roblourens -author:rzhao271 -author:sandy081 -author:sbatten -author:stevencl -author:tanhakabir -author:TylerLeonhardt -author:Tyriar -author:weinand -author:amunger -author:karthiknadig -author:eleanorjboyd -author:Yoyokrazy -author:paulacamargo25 -author:ulugbekna -author:aiday-mar\n" }, { "kind": 1, @@ -167,7 +167,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed -author:@me sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found" + "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed -author:@me sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found\n" }, { "kind": 1, @@ -177,7 +177,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue label:bug label:verification-steps-needed -label:verified" + "value": "$REPOS $MILESTONE -$MINE is:issue label:bug label:verification-steps-needed -label:verified\n" }, { "kind": 1, @@ -187,6 +187,6 @@ { "kind": 2, "language": "github-issues", - "value": "repo:microsoft/vscode $MILESTONE $MINE is:issue is:closed reason:completed label:feature-request -label:on-release-notes\r\nrepo:microsoft/vscode $MILESTONE $MINE is:issue is:closed reason:completed label:engineering -label:on-release-notes\r\nrepo:microsoft/vscode $MILESTONE $MINE is:issue is:closed reason:completed label:plan-item -label:on-release-notes" + "value": "$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:feature-request -label:on-release-notes\n$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:engineering -label:on-release-notes\n$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:plan-item -label:on-release-notes\n" } ] \ No newline at end of file diff --git a/.vscode/notebooks/verification.github-issues b/.vscode/notebooks/verification.github-issues index 1b81dcd0148cd7..1af66192b91ae1 100644 --- a/.vscode/notebooks/verification.github-issues +++ b/.vscode/notebooks/verification.github-issues @@ -12,7 +12,7 @@ { "kind": 2, "language": "github-issues", - "value": "$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-livepreview repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-l10n repo:microsoft/vscode-remote-tunnels\n$milestone=milestone:\"August 2023\"" + "value": "$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-livepreview repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-l10n repo:microsoft/vscode-remote-tunnels\r\n$milestone=milestone:\"September 2023\"" }, { "kind": 1, diff --git a/.vscode/notebooks/vscode-dev.github-issues b/.vscode/notebooks/vscode-dev.github-issues index 013c3331dca08f..851c7a9db3dac2 100644 --- a/.vscode/notebooks/vscode-dev.github-issues +++ b/.vscode/notebooks/vscode-dev.github-issues @@ -2,7 +2,7 @@ { "kind": 2, "language": "github-issues", - "value": "$milestone=milestone:\"August 2023\"" + "value": "$milestone=milestone:\"September 2023\"" }, { "kind": 1, diff --git a/.yarnrc b/.yarnrc index 92a2a8bc8c2df8..b0814477f164f5 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,5 +1,5 @@ disturl "https://electronjs.org/headers" -target "25.8.2" -ms_build_id "23977753" +target "25.8.4" +ms_build_id "24154031" runtime "electron" build_from_source "true" diff --git a/CodeQL.yml b/CodeQL.yml index eecb813a96f9ac..ca99d172935b66 100644 --- a/CodeQL.yml +++ b/CodeQL.yml @@ -21,6 +21,7 @@ path_classifiers: - "out-build" - "out-vscode" - "**/out/**" + - ".build/distro/cli-patches/index.js" # The default behavior is to tag library code as `library`. Results are hidden # for library code. You can tag further files as being library code by adding them diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt index 6863bf7828f18d..5a80241397ee93 100644 --- a/ThirdPartyNotices.txt +++ b/ThirdPartyNotices.txt @@ -1563,7 +1563,7 @@ SOFTWARE. --------------------------------------------------------- -jlelong/vscode-latex-basics 1.5.3 - MIT +jlelong/vscode-latex-basics 1.5.2 - MIT https://github.com/jlelong/vscode-latex-basics Copyright (c) vscode-latex-basics authors @@ -2246,7 +2246,7 @@ SOFTWARE. --------------------------------------------------------- -redhat-developer/vscode-java 1.21.0 - MIT +redhat-developer/vscode-java 1.22.0 - MIT https://github.com/redhat-developer/vscode-java The MIT License (MIT) diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index dedc227caa7776..8bdec848ec47ef 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -77,7 +77,7 @@ steps: - script: | set -e export npm_config_arch=$(VSCODE_ARCH) - export npm_config_node_gyp=$(which node-gyp) + npm i -g node-gyp for i in {1..5}; do # try 5 times yarn --frozen-lockfile --check-files && break diff --git a/build/checksums/electron.txt b/build/checksums/electron.txt index 1e5ab89cce1dd0..b4af222afdc3e7 100644 --- a/build/checksums/electron.txt +++ b/build/checksums/electron.txt @@ -1,27 +1,27 @@ -990cfa83850fc2934ddf435737ea16c68e222136107aba177d961af839a61765 *electron-v25.8.2-darwin-arm64-symbols.zip -f6ab7cd71e50fae41cae0192108df98afc0a30583d945d3e673d8483d4dddefe *electron-v25.8.2-darwin-arm64.zip -2feec213e4b2a4728ef5f5ec833ee8663d801a06b7ed1f172eee4ba3f9f51339 *electron-v25.8.2-darwin-x64-symbols.zip -04027c3ecb90d8252f1adbd887d9567d5f16eee126f8df753a29fa79d9b61d6b *electron-v25.8.2-darwin-x64.zip -4a9a49d6003ac86ccba5495ff782f584215cee36f9335772cdc0581911f5f6a1 *electron-v25.8.2-linux-arm64-symbols.zip -1b15ad0e26785850f9ab2112d3183669aac4b702c474ab7ad31954c649b3351f *electron-v25.8.2-linux-arm64.zip -4bdd180dff11013d6c2d25c578e5398ac9c7dd3b6255080836868e1cd55e4c13 *electron-v25.8.2-linux-armv7l-symbols.zip -db4271335284d2d327130063cca6a4adee0678504098c0347e2cb89c4ddefd7a *electron-v25.8.2-linux-armv7l.zip -ab9c35e7a6df6b2335f273711cc16c94f982f4d55b2c0bba554187f43864242e *electron-v25.8.2-linux-x64-symbols.zip -08c211d4199096d0ff9c475720f25ff5c089d624660b4c1f1970a413e20312b7 *electron-v25.8.2-linux-x64.zip -d3bd6ab0be3c4d93a7a19f9da0c391c1ef8daa85027bf00f9ac75a97e7027fcf *electron-v25.8.2-win32-arm64-pdb.zip -cc6c369bd9d2fc7664bbd5138be5527432e17ec6eb7c7b5405e641e0468dc280 *electron-v25.8.2-win32-arm64-symbols.zip -d4083f48cc1f7bb311a4a53814e2cd7c343c159f4bcd4ee368d20564e1d34a36 *electron-v25.8.2-win32-arm64.zip -276b0ba63d6dbb78f99caf2c58213415f22cb4f90ec6e6485214f3174482458c *electron-v25.8.2-win32-ia32-pdb.zip -45551352678e250c7970c6258d55e1c6451131d1e5f1ea053390bd9d9364d33c *electron-v25.8.2-win32-ia32-symbols.zip -fc9798e973b6a872d7250754b29caa48b0f0ad7c33f39ab61008525b29111049 *electron-v25.8.2-win32-ia32.zip -491795df7e70effeae68a11e39675ca32677b44865d10151a2eea71dad2c2815 *electron-v25.8.2-win32-x64-pdb.zip -fa5962a52022c16fb59d79c7528d50ab314b6a8d1e6b90f4c940bb3a0f061923 *electron-v25.8.2-win32-x64-symbols.zip -fb5786dc18a10cc29b4d765f34af2f9f9d9866f82007468b55d800dd43feb196 *electron-v25.8.2-win32-x64.zip -b1ad00396c949d0f37fe3920eb4f667663d7453dce4f141bcbfc560140a85d84 *ffmpeg-v25.8.2-darwin-arm64.zip -81a3dff9b95d294a7955d67e013ecf65b0c1b929104cea82fcf22774c067d457 *ffmpeg-v25.8.2-darwin-x64.zip -bd52d57ff97fb56ac01a3482af905d04f0d4e9c13c53858c6d9f99957eca82da *ffmpeg-v25.8.2-linux-arm64.zip -9b3d09177fa1e63e2a6beecfa70aeec30aeb5c1873ff21128a68051c4e23f95d *ffmpeg-v25.8.2-linux-armv7l.zip -edc7b1c9f1a0733f109a2c0375a4e40c5bfe0bf28b7f06dcc76e1ada0aa2f125 *ffmpeg-v25.8.2-linux-x64.zip -aacd68ce8a56e707e20fdcf08ea66348dbb1717b87f9c7e3d80da106fa5aa45b *ffmpeg-v25.8.2-win32-arm64.zip -29411956a1ab20f63926a764893effd113c0ef0349cbaa355a95c1c1046d5d8d *ffmpeg-v25.8.2-win32-ia32.zip -80ccd09de3a73e1a3b27ad2aa2a8a3eb5753290b7eee3d2e5ea08837876c3f66 *ffmpeg-v25.8.2-win32-x64.zip +db3e9eb9f47f465bb63d15de486ea1d9274233b24bbe451038bfbaf48f9b0e39 *electron-v25.8.4-darwin-arm64-symbols.zip +5d83e2094a26bfe22e4c80e660ab088ec94ae3cc2d518c6efcac338f48cc0266 *electron-v25.8.4-darwin-arm64.zip +6fdd506328c65a9d8205425a463098210743c9ef79a546738b91a91d56100447 *electron-v25.8.4-darwin-x64-symbols.zip +d4015cd251e58ef074d1f7f3e99bfbbe4cd6b690981f376fc642b2de955e8750 *electron-v25.8.4-darwin-x64.zip +b46da627829a84cdf84b5570f95e044d38660fb0e58712757e834ff13b43c72d *electron-v25.8.4-linux-arm64-symbols.zip +fbb6e06417b1741b94d59a6de5dcf3262bfb3fc98cffbcad475296c42d1cbe94 *electron-v25.8.4-linux-arm64.zip +2569c260b4bb90894c5e63e175d3ee9665525e928d7c70158c6a9d98cb82f6a9 *electron-v25.8.4-linux-armv7l-symbols.zip +6301e6fde3e7c8149a5eca84c3817ba9ad3ffcb72e79318a355f025d7d3f8408 *electron-v25.8.4-linux-armv7l.zip +63580a081a4481eec2773606e9cd50c3468758741f11a14d6c47ab716c064896 *electron-v25.8.4-linux-x64-symbols.zip +0cbbcaf90f3dc79dedec97d073ffe954530316523479c31b11781a141f8a87f6 *electron-v25.8.4-linux-x64.zip +8860faaaabcc15a531733dd164c858a1cc1bffefdbba7ec54f7687db796f93f3 *electron-v25.8.4-win32-arm64-pdb.zip +e909628b4c984b3472c58b3897214e59f55ce69bee99229cdf1451a281865176 *electron-v25.8.4-win32-arm64-symbols.zip +1355293a73da3e5d3f06a6c95c81a5124c4f26be2ec1035ccfcfeccd4c766f5d *electron-v25.8.4-win32-arm64.zip +597cbfd2b9d542a289296d792ed9be40c3e97499207675766265a710454f76f5 *electron-v25.8.4-win32-ia32-pdb.zip +3a0ee0d1435382cfdf727ed70e6c8edd233363dcdadde5c1c6ec170fff243a99 *electron-v25.8.4-win32-ia32-symbols.zip +13efcbfc4a0a62339b4450c5d71d14230978e25eb410dcc7d3408b413391eead *electron-v25.8.4-win32-ia32.zip +fef9e5ec4d146e6b310137140cee2a1172964e7584540088b1bc7fd1df15f1ff *electron-v25.8.4-win32-x64-pdb.zip +1227ec90ae2fb30e01d4c6814af1adae983b78ea832dea0520caaa8a05ac0390 *electron-v25.8.4-win32-x64-symbols.zip +0bbe72439cab1e72dee5fb850fdb1b17ea16fef61aa3dae93c562687737084f1 *electron-v25.8.4-win32-x64.zip +41e5b5392efcb1b47826f20e2f867dac6026dd435b92f50acb58bfae99b96e08 *ffmpeg-v25.8.4-darwin-arm64.zip +bad5ed7f10eef768c95a134cbd6754e9c347eb8bfae871e65975afb96cc49b86 *ffmpeg-v25.8.4-darwin-x64.zip +bd52d57ff97fb56ac01a3482af905d04f0d4e9c13c53858c6d9f99957eca82da *ffmpeg-v25.8.4-linux-arm64.zip +9b3d09177fa1e63e2a6beecfa70aeec30aeb5c1873ff21128a68051c4e23f95d *ffmpeg-v25.8.4-linux-armv7l.zip +edc7b1c9f1a0733f109a2c0375a4e40c5bfe0bf28b7f06dcc76e1ada0aa2f125 *ffmpeg-v25.8.4-linux-x64.zip +84ec373f124f628ce7d8964e000e79cd1448acec05b92417207baecf9b0f039a *ffmpeg-v25.8.4-win32-arm64.zip +ce6b46e5395f0f715ff694399580eded7e976c0dd8668304e4b087967fea711f *ffmpeg-v25.8.4-win32-ia32.zip +7506346ff7a98377eca26464370a7c5a8c44d010d5c46a8357fa107980582fac *ffmpeg-v25.8.4-win32-x64.zip diff --git a/cglicenses.json b/cglicenses.json index 121cbc30c502ed..006eaf1f96809a 100644 --- a/cglicenses.json +++ b/cglicenses.json @@ -536,5 +536,31 @@ "prependLicenseText": [ "Copyright (c) cacheable-request authors" ] + }, + { + "name": "@vscode/ts-package-manager", + "fullLicenseText": [ + "MIT License", + "", + "Copyright (c) Microsoft Corporation.", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy", + "of this software and associated documentation files (the \"Software\"), to deal", + "in the Software without restriction, including without limitation the rights", + "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", + "copies of the Software, and to permit persons to whom the Software is", + "furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in all", + "copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", + "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", + "SOFTWARE" + ] } ] diff --git a/cgmanifest.json b/cgmanifest.json index 39a03ec0cbe3bd..8cbbecf6915058 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -528,12 +528,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "67b2739f44ce98aad493170fecc696f76dc12de1" + "commitHash": "415301c477b600502cf264e93318dda551288829" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "25.8.2" + "version": "25.8.4" }, { "component": { diff --git a/cli/ThirdPartyNotices.txt b/cli/ThirdPartyNotices.txt index 09856f72402ae2..c1f96966354401 100644 --- a/cli/ThirdPartyNotices.txt +++ b/cli/ThirdPartyNotices.txt @@ -2112,7 +2112,7 @@ SOFTWARE. --------------------------------------------------------- dialoguer 0.10.4 - MIT -https://github.com/mitsuhiko/dialoguer +https://github.com/console-rs/dialoguer The MIT License (MIT) @@ -3366,7 +3366,7 @@ DEALINGS IN THE SOFTWARE. hermit-abi 0.1.19 - MIT/Apache-2.0 hermit-abi 0.3.1 - MIT OR Apache-2.0 -https://github.com/hermitcore/rusty-hermit +https://github.com/hermit-os/hermit-rs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated @@ -4230,6 +4230,39 @@ DEALINGS IN THE SOFTWARE. --------------------------------------------------------- +libz-sys 1.1.12 - MIT OR Apache-2.0 +https://github.com/rust-lang/libz-sys + +Copyright (c) 2014 Alex Crichton +Copyright (c) 2020 Josh Triplett + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +--------------------------------------------------------- + +--------------------------------------------------------- + link-cplusplus 1.0.9 - MIT OR Apache-2.0 https://github.com/dtolnay/link-cplusplus diff --git a/extensions/cpp/cgmanifest.json b/extensions/cpp/cgmanifest.json index 03ec3e1ac1412c..fa28dd73898fa5 100644 --- a/extensions/cpp/cgmanifest.json +++ b/extensions/cpp/cgmanifest.json @@ -10,6 +10,31 @@ } }, "license": "MIT", + "licenseDetail": [ + [ + "MIT License", + "", + "Copyright (c) 2019 Jeff Hykin", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy", + "of this software and associated documentation files (the \"Software\"), to deal", + "in the Software without restriction, including without limitation the rights", + "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", + "copies of the Software, and to permit persons to whom the Software is", + "furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in all", + "copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", + "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", + "SOFTWARE." + ] + ], "version": "1.17.4", "description": "The original JSON grammars were derived from https://github.com/atom/language-c which was originally converted from the C TextMate bundle https://github.com/textmate/c.tmbundle." }, @@ -68,4 +93,4 @@ } ], "version": 1 -} \ No newline at end of file +} diff --git a/extensions/css-language-features/server/package.json b/extensions/css-language-features/server/package.json index 874ce1e820206b..617b8950600546 100644 --- a/extensions/css-language-features/server/package.json +++ b/extensions/css-language-features/server/package.json @@ -11,7 +11,7 @@ "browser": "./dist/browser/cssServerMain", "dependencies": { "@vscode/l10n": "^0.0.16", - "vscode-css-languageservice": "^6.2.7", + "vscode-css-languageservice": "^6.2.9", "vscode-languageserver": "^8.2.0-next.3", "vscode-uri": "^3.0.7" }, diff --git a/extensions/css-language-features/server/yarn.lock b/extensions/css-language-features/server/yarn.lock index 296912060e4407..4442a3d9505f7a 100644 --- a/extensions/css-language-features/server/yarn.lock +++ b/extensions/css-language-features/server/yarn.lock @@ -17,14 +17,14 @@ resolved "https://registry.yarnpkg.com/@vscode/l10n/-/l10n-0.0.16.tgz#f075db346d0b08419a12540171b230bd803c42be" integrity sha512-JT5CvrIYYCrmB+dCana8sUqJEcGB1ZDXNLMQ2+42bW995WmNoenijWMUdZfwmuQUTQcEVVIa2OecZzTYWUW9Cg== -vscode-css-languageservice@^6.2.7: - version "6.2.7" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-6.2.7.tgz#d64e347e9a432d2b9c1a12d1ea5bc77996a2e9dc" - integrity sha512-Jd8wpIg5kJ15CfrieoEPvu3gGFc36sbM3qXCtjVq5zrnLEX5NhHxikMDtf8AgQsYklXiDqiZLKoBnzkJtRbTHQ== +vscode-css-languageservice@^6.2.9: + version "6.2.9" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-6.2.9.tgz#5bdd12012bf15069fb671734130deca0a0acbc0c" + integrity sha512-9MsOvAi+VycKomQ7KEq4o/hLtjHHrtRLLl8lM9nMcH8cxfNI7/6jVXmsV/7pdbDWu9L3DZhsspN1eMXZwiOymw== dependencies: "@vscode/l10n" "^0.0.16" vscode-languageserver-textdocument "^1.0.8" - vscode-languageserver-types "^3.17.3" + vscode-languageserver-types "3.17.3" vscode-uri "^3.0.7" vscode-jsonrpc@8.2.0-next.2: @@ -45,16 +45,16 @@ vscode-languageserver-textdocument@^1.0.8: resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.8.tgz#9eae94509cbd945ea44bca8dcfe4bb0c15bb3ac0" integrity sha512-1bonkGqQs5/fxGT5UchTgjGVnfysL0O8v1AYMBjqTbWQTFn721zaPGDYFkOKtfDgFiSgXM3KwaG3FMGfW4Ed9Q== +vscode-languageserver-types@3.17.3: + version "3.17.3" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz#72d05e47b73be93acb84d6e311b5786390f13f64" + integrity sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA== + vscode-languageserver-types@3.17.4-next.2: version "3.17.4-next.2" resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.4-next.2.tgz#4099ff39b38edbd2680df13bfb1c05f0c07bfe8d" integrity sha512-r6tXyCXyXQH7b6VHkvRT0Nd9v+DWQiosgTR6HQajCb4iJ1myr3KgueWEGBF1Ph5/YAiDy8kXUhf8dHl7wE1H2A== -vscode-languageserver-types@^3.17.3: - version "3.17.3" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz#72d05e47b73be93acb84d6e311b5786390f13f64" - integrity sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA== - vscode-languageserver@^8.2.0-next.3: version "8.2.0-next.3" resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-8.2.0-next.3.tgz#72e4998392260173fb0c35d2d556fb4015f56ce3" diff --git a/extensions/git/src/api/api1.ts b/extensions/git/src/api/api1.ts index afa4ad1bcaf602..8168b7201fd678 100644 --- a/extensions/git/src/api/api1.ts +++ b/extensions/git/src/api/api1.ts @@ -177,6 +177,10 @@ export class ApiRepository implements Repository { return this.repository.getBranches(query, cancellationToken); } + getBranchBase(name: string): Promise { + return this.repository.getBranchBase(name); + } + setBranchUpstream(name: string, upstream: string): Promise { return this.repository.setBranchUpstream(name, upstream); } diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index d972ce59f7e4cd..8dd2b7addb3831 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -219,6 +219,7 @@ export interface Repository { deleteBranch(name: string, force?: boolean): Promise; getBranch(name: string): Promise; getBranches(query: BranchQuery, cancellationToken?: CancellationToken): Promise; + getBranchBase(name: string): Promise; setBranchUpstream(name: string, upstream: string): Promise; getRefs(query: RefQuery, cancellationToken?: CancellationToken): Promise; diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index f8e080e76fd58c..2a3fc098d52d75 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -1083,6 +1083,17 @@ export class Repository { return parseGitCommits(result.stdout); } + async reflog(ref: string, pattern: string): Promise { + const args = ['reflog', ref, `--grep-reflog=${pattern}`]; + const result = await this.exec(args); + if (result.exitCode) { + return []; + } + + return result.stdout.split('\n') + .filter(entry => !!entry); + } + async bufferString(object: string, encoding: string = 'utf8', autoGuessEncoding = false): Promise { const stdout = await this.buffer(object); @@ -2402,7 +2413,7 @@ export class Repository { args.push('--ignore-case'); } - if (/^refs\/(head|remotes)\//i.test(name)) { + if (/^refs\/(heads|remotes)\//i.test(name)) { args.push(name); } else { args.push(`refs/heads/${name}`, `refs/remotes/${name}`); @@ -2544,6 +2555,18 @@ export class Repository { return { ahead: Number(ahead) || 0, behind: Number(behind) || 0 }; } + async revParse(ref: string): Promise { + try { + const result = await this.exec(['rev-parse', ref]); + if (result.stderr) { + return undefined; + } + return result.stdout.trim(); + } catch (err) { + return undefined; + } + } + async updateSubmodules(paths: string[]): Promise { const args = ['submodule', 'update']; diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index df928afdfc710e..d3a64bb4bb52bf 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -9,7 +9,7 @@ import { Repository, Resource } from './repository'; import { IDisposable } from './util'; import { toGitUri } from './uri'; import { SyncActionButton } from './actionButton'; -import { Status } from './api/git'; +import { RefType, Status } from './api/git'; export class GitHistoryProvider implements SourceControlHistoryProvider, FileDecorationProvider, IDisposable { @@ -76,9 +76,11 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec } const optionsRef = options.limit.id; + const historyItemGroupIdRef = await this.repository.revParse(historyItemGroupId) ?? ''; + const [commits, summary] = await Promise.all([ - this.repository.log({ range: `${optionsRef}..${historyItemGroupId}`, sortByAuthorDate: true }), - this.getSummaryHistoryItem(optionsRef, historyItemGroupId) + this.repository.log({ range: `${optionsRef}..${historyItemGroupIdRef}`, sortByAuthorDate: true }), + this.getSummaryHistoryItem(optionsRef, historyItemGroupIdRef) ]); const historyItems = commits.length === 0 ? [] : [summary]; @@ -100,13 +102,19 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec } async provideHistoryItemChanges(historyItemId: string): Promise { - const [ref1, ref2] = historyItemId.includes('..') - ? historyItemId.split('..') - : [`${historyItemId}^`, historyItemId]; + // The "All Changes" history item uses a special id + // which is a commit range instead of a single commit id + let [originalRef, modifiedRef] = historyItemId.includes('..') + ? historyItemId.split('..') : [undefined, historyItemId]; + + if (!originalRef) { + const commit = await this.repository.getCommit(modifiedRef); + originalRef = commit.parents.length > 0 ? commit.parents[0] : `${modifiedRef}^`; + } const historyItemChangesUri: Uri[] = []; const historyItemChanges: SourceControlHistoryItemChange[] = []; - const changes = await this.repository.diffBetween(ref1, ref2); + const changes = await this.repository.diffBetween(originalRef, modifiedRef); for (const change of changes) { const historyItemUri = change.uri.with({ @@ -116,8 +124,8 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec // History item change historyItemChanges.push({ uri: historyItemUri, - originalUri: toGitUri(change.originalUri, ref1), - modifiedUri: toGitUri(change.originalUri, ref2), + originalUri: toGitUri(change.originalUri, originalRef), + modifiedUri: toGitUri(change.originalUri, modifiedRef), renameUri: change.renameUri, }); @@ -142,9 +150,23 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec return this.currentHistoryItemGroup.upstream; } - // Default branch - const defaultBranch = await this.repository.getDefaultBranch(); - return defaultBranch.name ? { id: `refs/heads/${defaultBranch.name}`, label: defaultBranch.name } : undefined; + // Branch base + const branchBase = await this.repository.getBranchBase(historyItemGroupId); + + if (branchBase?.name && branchBase?.type === RefType.Head) { + return { + id: `refs/heads/${branchBase.name}`, + label: branchBase.name + }; + } + if (branchBase?.name && branchBase.remote && branchBase?.type === RefType.RemoteHead) { + return { + id: `refs/remotes/${branchBase.remote}/${branchBase.name}`, + label: `${branchBase.remote}/${branchBase.name}` + }; + } + + return undefined; } async resolveHistoryItemGroupCommonAncestor(refId1: string, refId2: string): Promise<{ id: string; ahead: number; behind: number } | undefined> { diff --git a/extensions/git/src/operation.ts b/extensions/git/src/operation.ts index 29eff86a59ba8e..5573d089178951 100644 --- a/extensions/git/src/operation.ts +++ b/extensions/git/src/operation.ts @@ -54,6 +54,7 @@ export const enum OperationKind { RevertFiles = 'RevertFiles', RevertFilesNoProgress = 'RevertFilesNoProgress', RevList = 'RevList', + RevParse = 'RevParse', SetBranchUpstream = 'SetBranchUpstream', Show = 'Show', Stage = 'Stage', @@ -70,7 +71,7 @@ export type Operation = AddOperation | ApplyOperation | BlameOperation | BranchO GetBranchOperation | GetBranchesOperation | GetCommitTemplateOperation | GetObjectDetailsOperation | GetRefsOperation | GetRemoteRefsOperation | HashObjectOperation | IgnoreOperation | LogOperation | LogFileOperation | MergeOperation | MergeAbortOperation | MergeBaseOperation | MoveOperation | PostCommitCommandOperation | PullOperation | PushOperation | RemoteOperation | RenameBranchOperation | RemoveOperation | - ResetOperation | RebaseOperation | RebaseAbortOperation | RebaseContinueOperation | RevertFilesOperation | RevListOperation | + ResetOperation | RebaseOperation | RebaseAbortOperation | RebaseContinueOperation | RevertFilesOperation | RevListOperation | RevParseOperation | SetBranchUpstreamOperation | ShowOperation | StageOperation | StatusOperation | StashOperation | SubmoduleUpdateOperation | SyncOperation | TagOperation; @@ -119,6 +120,7 @@ export type RebaseAbortOperation = BaseOperation & { kind: OperationKind.RebaseA export type RebaseContinueOperation = BaseOperation & { kind: OperationKind.RebaseContinue }; export type RevertFilesOperation = BaseOperation & { kind: OperationKind.RevertFiles }; export type RevListOperation = BaseOperation & { kind: OperationKind.RevList }; +export type RevParseOperation = BaseOperation & { kind: OperationKind.RevParse }; export type SetBranchUpstreamOperation = BaseOperation & { kind: OperationKind.SetBranchUpstream }; export type ShowOperation = BaseOperation & { kind: OperationKind.Show }; export type StageOperation = BaseOperation & { kind: OperationKind.Stage }; @@ -173,6 +175,7 @@ export const Operation = { RebaseContinue: { kind: OperationKind.RebaseContinue, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as RebaseContinueOperation, RevertFiles: (showProgress: boolean) => ({ kind: OperationKind.RevertFiles, blocking: false, readOnly: false, remote: false, retry: false, showProgress } as RevertFilesOperation), RevList: { kind: OperationKind.RevList, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as RevListOperation, + RevParse: { kind: OperationKind.RevParse, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as RevParseOperation, SetBranchUpstream: { kind: OperationKind.SetBranchUpstream, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as SetBranchUpstreamOperation, Show: { kind: OperationKind.Show, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as ShowOperation, Stage: { kind: OperationKind.Stage, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as StageOperation, diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 1065762a3fb56a..3b14f3cfc82d36 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1439,10 +1439,6 @@ export class Repository implements Disposable { await this.run(Operation.Move, () => this.repository.move(from, to)); } - async getDefaultBranch(): Promise { - return await this.run(Operation.GetBranch, () => this.repository.getDefaultBranch()); - } - async getBranch(name: string): Promise { return await this.run(Operation.GetBranch, () => this.repository.getBranch(name)); } @@ -1454,6 +1450,84 @@ export class Repository implements Disposable { }); } + async getBranchBase(ref: string): Promise { + const branch = await this.getBranch(ref); + const branchMergeBaseConfigKey = `branch.${branch.name}.vscode-merge-base`; + + // Upstream + if (branch.upstream) { + return await this.getBranch(`refs/remotes/${branch.upstream.remote}/${branch.upstream.name}`); + } + + // Git config + try { + const mergeBase = await this.getConfig(branchMergeBaseConfigKey); + if (mergeBase) { + return await this.getBranch(mergeBase); + } + } catch (err) { } + + // Reflog + const branchFromReflog = await this.getBranchBaseFromReflog(ref); + if (branchFromReflog) { + await this.setConfig(branchMergeBaseConfigKey, branchFromReflog.name!); + return branchFromReflog; + } + + // Default branch + const defaultBranch = await this.getDefaultBranch(); + if (defaultBranch) { + await this.setConfig(branchMergeBaseConfigKey, defaultBranch.name!); + return defaultBranch; + } + + return undefined; + } + + private async getBranchBaseFromReflog(ref: string): Promise { + try { + const reflogEntries = await this.repository.reflog(ref, 'branch: Created from *.'); + if (reflogEntries.length !== 1) { + return undefined; + } + + // Branch created from an explicit branch + const match = reflogEntries[0].match(/branch: Created from (?.*)$/); + if (match && match.length === 2 && match[1] !== 'HEAD') { + return await this.getBranch(match[1]); + } + + // Branch created from HEAD + const headReflogEntries = await this.repository.reflog('HEAD', `checkout: moving from .* to ${ref.replace('refs/heads/', '')}`); + if (headReflogEntries.length === 0) { + return undefined; + } + + const match2 = headReflogEntries[headReflogEntries.length - 1].match(/checkout: moving from ([^\s]+)\s/); + if (match2 && match2.length === 2) { + return await this.getBranch(match2[1]); + } + + } + catch (err) { } + + return undefined; + } + + private async getDefaultBranch(): Promise { + try { + const defaultBranchResult = await this.repository.exec(['symbolic-ref', '--short', 'refs/remotes/origin/HEAD']); + if (defaultBranchResult.stdout.trim() === '' || defaultBranchResult.stderr) { + return undefined; + } + + return this.getBranch(defaultBranchResult.stdout.trim()); + } + catch (err) { } + + return undefined; + } + async getRefs(query: RefQuery = {}, cancellationToken?: CancellationToken): Promise { const config = workspace.getConfiguration('git'); let defaultSort = config.get<'alphabetically' | 'committerdate'>('branchSortOrder'); @@ -1532,6 +1606,10 @@ export class Repository implements Disposable { return await this.run(Operation.RevList, () => this.repository.getCommitCount(range)); } + async revParse(ref: string): Promise { + return await this.run(Operation.RevParse, () => this.repository.revParse(ref)); + } + async reset(treeish: string, hard?: boolean): Promise { await this.run(Operation.Reset, () => this.repository.reset(treeish, hard)); } diff --git a/extensions/github/yarn.lock b/extensions/github/yarn.lock index dcf0f5c776b071..9166903b63ba7e 100644 --- a/extensions/github/yarn.lock +++ b/extensions/github/yarn.lock @@ -544,9 +544,9 @@ graphql-tag@^2.10.3: tslib "^2.1.0" graphql@^16.0.0: - version "16.6.0" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.6.0.tgz#c2dcffa4649db149f6282af726c8c83f1c7c5fdb" - integrity sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw== + version "16.8.1" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07" + integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw== has@^1.0.3: version "1.0.3" diff --git a/extensions/html-language-features/server/package.json b/extensions/html-language-features/server/package.json index 1816dca28f58b7..d6f3ca88b15666 100644 --- a/extensions/html-language-features/server/package.json +++ b/extensions/html-language-features/server/package.json @@ -10,8 +10,8 @@ "main": "./out/node/htmlServerMain", "dependencies": { "@vscode/l10n": "^0.0.16", - "vscode-css-languageservice": "^6.2.7", - "vscode-html-languageservice": "^5.0.7", + "vscode-css-languageservice": "^6.2.9", + "vscode-html-languageservice": "^5.1.0", "vscode-languageserver": "^8.2.0-next.3", "vscode-languageserver-textdocument": "^1.0.8", "vscode-uri": "^3.0.7" diff --git a/extensions/html-language-features/server/yarn.lock b/extensions/html-language-features/server/yarn.lock index 22f945e7f840bd..2bc2e2e0f59d81 100644 --- a/extensions/html-language-features/server/yarn.lock +++ b/extensions/html-language-features/server/yarn.lock @@ -17,20 +17,20 @@ resolved "https://registry.yarnpkg.com/@vscode/l10n/-/l10n-0.0.16.tgz#f075db346d0b08419a12540171b230bd803c42be" integrity sha512-JT5CvrIYYCrmB+dCana8sUqJEcGB1ZDXNLMQ2+42bW995WmNoenijWMUdZfwmuQUTQcEVVIa2OecZzTYWUW9Cg== -vscode-css-languageservice@^6.2.7: - version "6.2.7" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-6.2.7.tgz#d64e347e9a432d2b9c1a12d1ea5bc77996a2e9dc" - integrity sha512-Jd8wpIg5kJ15CfrieoEPvu3gGFc36sbM3qXCtjVq5zrnLEX5NhHxikMDtf8AgQsYklXiDqiZLKoBnzkJtRbTHQ== +vscode-css-languageservice@^6.2.9: + version "6.2.9" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-6.2.9.tgz#5bdd12012bf15069fb671734130deca0a0acbc0c" + integrity sha512-9MsOvAi+VycKomQ7KEq4o/hLtjHHrtRLLl8lM9nMcH8cxfNI7/6jVXmsV/7pdbDWu9L3DZhsspN1eMXZwiOymw== dependencies: "@vscode/l10n" "^0.0.16" vscode-languageserver-textdocument "^1.0.8" - vscode-languageserver-types "^3.17.3" + vscode-languageserver-types "3.17.3" vscode-uri "^3.0.7" -vscode-html-languageservice@^5.0.7: - version "5.0.7" - resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-5.0.7.tgz#8d27773e0197799a9db777ee4fc134cf1c669d84" - integrity sha512-jX+7/kUXrdOaRT8vqYR/jLxrGDib+Far8I7n/A6apuEl88k+mhIHZPwc6ezuLeiCKUCaLG4b0dqFwjVa7QL3/w== +vscode-html-languageservice@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-5.1.0.tgz#ba4f302eda5d8c248bcf5d173d862f241c4b48d3" + integrity sha512-cGOu5+lrz+2dDXSGS15y24lDtPaML1T8K/SfqgFbLmCZ1btYOxceFieR+ybTS2es/A67kRc62m2cKFLUQPWG5g== dependencies: "@vscode/l10n" "^0.0.16" vscode-languageserver-textdocument "^1.0.8" @@ -55,16 +55,16 @@ vscode-languageserver-textdocument@^1.0.8: resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.8.tgz#9eae94509cbd945ea44bca8dcfe4bb0c15bb3ac0" integrity sha512-1bonkGqQs5/fxGT5UchTgjGVnfysL0O8v1AYMBjqTbWQTFn721zaPGDYFkOKtfDgFiSgXM3KwaG3FMGfW4Ed9Q== +vscode-languageserver-types@3.17.3, vscode-languageserver-types@^3.17.3: + version "3.17.3" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz#72d05e47b73be93acb84d6e311b5786390f13f64" + integrity sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA== + vscode-languageserver-types@3.17.4-next.2: version "3.17.4-next.2" resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.4-next.2.tgz#4099ff39b38edbd2680df13bfb1c05f0c07bfe8d" integrity sha512-r6tXyCXyXQH7b6VHkvRT0Nd9v+DWQiosgTR6HQajCb4iJ1myr3KgueWEGBF1Ph5/YAiDy8kXUhf8dHl7wE1H2A== -vscode-languageserver-types@^3.17.3: - version "3.17.3" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz#72d05e47b73be93acb84d6e311b5786390f13f64" - integrity sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA== - vscode-languageserver@^8.2.0-next.3: version "8.2.0-next.3" resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-8.2.0-next.3.tgz#72e4998392260173fb0c35d2d556fb4015f56ce3" diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index bf132f581687d5..607bb3aabe0d85 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -145,11 +145,58 @@ "title": "%configuration.typescript%", "order": 20, "properties": { - "typescript.experimental.aiQuickFix": { - "type": "boolean", - "default": false, - "description": "%typescript.experimental.aiQuickFix%", - "scope": "resource" + "typescript.experimental.aiCodeActions": { + "type": "object", + "default": {}, + "description": "%typescript.experimental.aiCodeActions%", + "scope": "resource", + "properties": { + "classIncorrectlyImplementsInterface": { + "type": "boolean", + "default": false, + "description": "%typescript.experimental.aiCodeActions.classIncorrectlyImplementsInterface%" + }, + "classDoesntImplementInheritedAbstractMember": { + "type": "boolean", + "default": false, + "description": "%typescript.experimental.aiCodeActions.classDoesntImplementInheritedAbstractMember%" + }, + "missingFunctionDeclaration": { + "type": "boolean", + "default": false, + "description": "%typescript.experimental.aiCodeActions.missingFunctionDeclaration%" + }, + "inferAndAddTypes": { + "type": "boolean", + "default": false, + "description": "%typescript.experimental.aiCodeActions.inferAndAddTypes%" + }, + "addNameToNamelessParameter": { + "type": "boolean", + "default": false, + "description": "%typescript.experimental.aiCodeActions.addNameToNamelessParameter%" + }, + "extractConstant": { + "type": "boolean", + "default": false, + "description": "%typescript.experimental.aiCodeActions.extractConstant%" + }, + "extractFunction": { + "type": "boolean", + "default": false, + "description": "%typescript.experimental.aiCodeActions.extractFunction%" + }, + "extractType": { + "type": "boolean", + "default": false, + "description": "%typescript.experimental.aiCodeActions.extractType%" + }, + "extractInterface": { + "type": "boolean", + "default": false, + "description": "%typescript.experimental.aiCodeActions.extractInterface%" + } + } }, "typescript.tsdk": { "type": "string", diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index dc902befdb28ef..355f8cbddfef73 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -8,7 +8,16 @@ "configuration.suggest.completeFunctionCalls": "Complete functions with their parameter signature.", "configuration.suggest.includeAutomaticOptionalChainCompletions": "Enable/disable showing completions on potentially undefined values that insert an optional chain call. Requires strict null checks to be enabled.", "configuration.suggest.includeCompletionsForImportStatements": "Enable/disable auto-import-style completions on partially-typed import statements.", - "typescript.experimental.aiQuickFix": "Enable/disable AI-assisted quick fixes. Requires an extension providing AI chat functionality.", + "typescript.experimental.aiCodeActions": "Enable/disable AI-assisted code actions. Requires an extension providing AI chat functionality.", + "typescript.experimental.aiCodeActions.classIncorrectlyImplementsInterface": "Enable/disable AI assistance for Class Incorrectly Implements Interface quickfix. Requires an extension providing AI chat functionality.", + "typescript.experimental.aiCodeActions.classDoesntImplementInheritedAbstractMember": "Enable/disable AI assistance for Class Doesn't Implement Inherited Abstract Member quickfix. Requires an extension providing AI chat functionality.", + "typescript.experimental.aiCodeActions.missingFunctionDeclaration": "Enable/disable AI assistance for Missing Function Declaration quickfix. Requires an extension providing AI chat functionality.", + "typescript.experimental.aiCodeActions.inferAndAddTypes": "Enable/disable AI assistance for Infer and Add Types refactor. Requires an extension providing AI chat functionality.", + "typescript.experimental.aiCodeActions.addNameToNamelessParameter": "Enable/disable AI assistance for Add Name to Nameless Parameter quickfix. Requires an extension providing AI chat functionality.", + "typescript.experimental.aiCodeActions.extractConstant": "Enable/disable AI assistance for Extract Constant refactor. Requires an extension providing AI chat functionality.", + "typescript.experimental.aiCodeActions.extractFunction": "Enable/disable AI assistance for Extract Function refactor. Requires an extension providing AI chat functionality.", + "typescript.experimental.aiCodeActions.extractType": "Enable/disable AI assistance for Extract Type refactor. Requires an extension providing AI chat functionality.", + "typescript.experimental.aiCodeActions.extractInterface": "Enable/disable AI assistance for Extract Interface refactor. Requires an extension providing AI chat functionality.", "typescript.tsdk.desc": "Specifies the folder path to the tsserver and `lib*.d.ts` files under a TypeScript install to use for IntelliSense, for example: `./node_modules/typescript/lib`.\n\n- When specified as a user setting, the TypeScript version from `typescript.tsdk` automatically replaces the built-in TypeScript version.\n- When specified as a workspace setting, `typescript.tsdk` allows you to switch to use that workspace version of TypeScript for IntelliSense with the `TypeScript: Select TypeScript version` command.\n\nSee the [TypeScript documentation](https://code.visualstudio.com/docs/typescript/typescript-compiling#_using-newer-typescript-versions) for more detail about managing TypeScript versions.", "typescript.disableAutomaticTypeAcquisition": "Disables [automatic type acquisition](https://code.visualstudio.com/docs/nodejs/working-with-javascript#_typings-and-automatic-type-acquisition). Automatic type acquisition fetches `@types` packages from npm to improve IntelliSense for external libraries.", "typescript.enablePromptUseWorkspaceTsdk": "Enables prompting of users to use the TypeScript version configured in the workspace for Intellisense.", diff --git a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts index bc2442046aa168..e0fadc0cc43c61 100644 --- a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts +++ b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts @@ -18,6 +18,7 @@ import { DiagnosticsManager } from './diagnostics'; import FileConfigurationManager from './fileConfigurationManager'; import { applyCodeActionCommands, getEditForCodeAction } from './util/codeAction'; import { conditionalRegistration, requireSomeCapability } from './util/dependentRegistration'; +import { Expand, EditorChatFollowUp_Args, CompositeCommand, EditorChatFollowUp } from './util/copilot'; type ApplyCodeActionCommand_args = { readonly document: vscode.TextDocument; @@ -26,42 +27,6 @@ type ApplyCodeActionCommand_args = { readonly followupAction?: Command; }; -class EditorChatFollowUp implements Command { - - id: string = '_typescript.quickFix.editorChatFollowUp'; - - constructor(private readonly prompt: string, private readonly document: vscode.TextDocument, private readonly range: vscode.Range, private readonly client: ITypeScriptServiceClient) { - } - - async execute() { - const findScopeEndLineFromNavTree = (startLine: number, navigationTree: Proto.NavigationTree[]): vscode.Range | undefined => { - for (const node of navigationTree) { - const range = typeConverters.Range.fromTextSpan(node.spans[0]); - if (startLine === range.start.line) { - return range; - } else if (startLine > range.start.line && startLine <= range.end.line && node.childItems) { - return findScopeEndLineFromNavTree(startLine, node.childItems); - } - } - return undefined; - }; - const filepath = this.client.toOpenTsFilePath(this.document); - if (!filepath) { - return; - } - const response = await this.client.execute('navtree', { file: filepath }, nulToken); - if (response.type !== 'response' || !response.body?.childItems) { - return; - } - const startLine = this.range.start.line; - const enclosingRange = findScopeEndLineFromNavTree(startLine, response.body.childItems); - if (!enclosingRange) { - return; - } - await vscode.commands.executeCommand('vscode.editorChat.start', { initialRange: enclosingRange, message: this.prompt, autoSend: true }); - } -} - class ApplyCodeActionCommand implements Command { public static readonly ID = '_typescript.applyCodeActionCommand'; public readonly id = ApplyCodeActionCommand.ID; @@ -255,8 +220,10 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider{ + message: 'Add types to this code. Add separate interfaces when possible. Do not change the code except for adding types.', + expand: { kind: 'navtree-function', pos: diagnostic.range.start }, + document + }], + title: '' + }; + actions.push(inferFromBody); + } + else if (action.fixName === fixNames.addNameToNamelessParameter && vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions.addNameToNamelessParameter')) { + const newText = action.changes.map(change => change.textChanges.map(textChange => textChange.newText).join('')).join(''); + title = 'Add meaningful parameter name with Copilot'; + message = `Rename the parameter ${newText} with a more meaningful name.`; + expand = { + kind: 'navtree-function', + pos: diagnostic.range.start + }; + } } - const codeAction = new VsCodeCodeAction(tsAction, tsAction.description, vscode.CodeActionKind.QuickFix); - codeAction.edit = getEditForCodeAction(this.client, tsAction); + const codeAction = new VsCodeCodeAction(action, title, vscode.CodeActionKind.QuickFix); + codeAction.edit = getEditForCodeAction(this.client, action); codeAction.diagnostics = [diagnostic]; codeAction.command = { command: ApplyCodeActionCommand.ID, - arguments: [{ action: tsAction, diagnostic, document, followupAction }], + arguments: [{ action: action, diagnostic, document }], title: '' }; - return codeAction; + if (expand && message !== undefined) { + codeAction.command = { + command: CompositeCommand.ID, + title: '', + arguments: [codeAction.command, { + command: EditorChatFollowUp.ID, + title: '', + arguments: [{ + message, + expand, + document + }], + }], + }; + } + actions.push(codeAction); + return actions; } private addFixAllForTsCodeAction( diff --git a/extensions/typescript-language-features/src/languageFeatures/refactor.ts b/extensions/typescript-language-features/src/languageFeatures/refactor.ts index ae36fbffb22afc..061756576c5022 100644 --- a/extensions/typescript-language-features/src/languageFeatures/refactor.ts +++ b/extensions/typescript-language-features/src/languageFeatures/refactor.ts @@ -20,6 +20,7 @@ import { coalesce } from '../utils/arrays'; import { nulToken } from '../utils/cancellation'; import FormattingOptionsManager from './fileConfigurationManager'; import { conditionalRegistration, requireSomeCapability } from './util/dependentRegistration'; +import { EditorChatFollowUp, EditorChatFollowUp_Args, CompositeCommand } from './util/copilot'; function toWorkspaceEdit(client: ITypeScriptServiceClient, edits: readonly Proto.FileCodeEdits[]): vscode.WorkspaceEdit { const workspaceEdit = new vscode.WorkspaceEdit(); @@ -34,17 +35,6 @@ function toWorkspaceEdit(client: ITypeScriptServiceClient, edits: readonly Proto } -class CompositeCommand implements Command { - public static readonly ID = '_typescript.compositeCommand'; - public readonly id = CompositeCommand.ID; - - public async execute(...commands: vscode.Command[]): Promise { - for (const command of commands) { - await vscode.commands.executeCommand(command.command, ...(command.arguments ?? [])); - } - } -} - namespace DidApplyRefactoringCommand { export interface Args { readonly action: string; @@ -355,15 +345,17 @@ class InlinedCodeAction extends vscode.CodeAction { public readonly refactor: Proto.ApplicableRefactorInfo, public readonly action: Proto.RefactorActionInfo, public readonly range: vscode.Range, + public readonly copilotRename?: (info: Proto.RefactorEditInfo) => vscode.Command, ) { - super(action.description, InlinedCodeAction.getKind(action)); + const title = copilotRename ? action.description + ' and suggest a name with Copilot.' : action.description; + super(title, InlinedCodeAction.getKind(action)); if (action.notApplicableReason) { this.disabled = { reason: action.notApplicableReason }; } this.command = { - title: action.description, + title, command: DidApplyRefactoringCommand.ID, arguments: [{ action: action.name }], }; @@ -395,18 +387,21 @@ class InlinedCodeAction extends vscode.CodeAction { if (response.body.renameLocation) { // Disable renames in interactive playground https://github.com/microsoft/vscode/issues/75137 if (this.document.uri.scheme !== fileSchemes.walkThroughSnippet) { + if (this.copilotRename && this.command) { + this.command.title = 'Copilot: ' + this.command.title; + } this.command = { command: CompositeCommand.ID, title: '', arguments: coalesce([ this.command, - { + this.copilotRename ? this.copilotRename(response.body) : { command: 'editor.action.rename', arguments: [[ this.document.uri, typeConverters.Position.fromLocation(response.body.renameLocation) ]] - } + }, ]) }; } @@ -456,7 +451,6 @@ class SelectCodeAction extends vscode.CodeAction { }; } } - type TsCodeAction = InlinedCodeAction | MoveToFileCodeAction | SelectCodeAction; class TypeScriptRefactorProvider implements vscode.CodeActionProvider { @@ -471,6 +465,7 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider vscode.Command) | undefined; + if (vscode.workspace.getConfiguration('typescript', null).get('experimental.aiCodeActions')) { + if (Extract_Constant.matches(action) && vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions.extractConstant') + || Extract_Function.matches(action) && vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions.extractFunction') + || Extract_Type.matches(action) && vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions.extractType') + || Extract_Interface.matches(action) && vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions.extractInterface')) { + const newName = Extract_Constant.matches(action) ? 'newLocal' + : Extract_Function.matches(action) ? 'newFunction' + : Extract_Type.matches(action) ? 'NewType' + : Extract_Interface.matches(action) ? 'NewInterface' + : ''; + copilotRename = info => ({ + title: '', + command: EditorChatFollowUp.ID, + arguments: [{ + message: `Rename ${newName} to a better name based on usage.`, + expand: Extract_Constant.matches(action) ? { + kind: 'navtree-function', + pos: typeConverters.Position.fromLocation(info.renameLocation!), + } : { + kind: 'refactor-info', + refactor: info, + }, + document, + }] + }); + } + + } + codeAction = new InlinedCodeAction(this.client, document, refactor, action, rangeOrSelection, copilotRename); } codeAction.isPreferred = TypeScriptRefactorProvider.isPreferred(action, allActions); diff --git a/extensions/typescript-language-features/src/languageFeatures/util/copilot.ts b/extensions/typescript-language-features/src/languageFeatures/util/copilot.ts new file mode 100644 index 00000000000000..697cf78954dbd0 --- /dev/null +++ b/extensions/typescript-language-features/src/languageFeatures/util/copilot.ts @@ -0,0 +1,149 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { Command } from '../../commands/commandManager'; +import { nulToken } from '../../utils/cancellation'; +import type * as Proto from '../../tsServer/protocol/protocol'; +import * as typeConverters from '../../typeConverters'; +import { ITypeScriptServiceClient } from '../../typescriptService'; + +export class EditorChatFollowUp implements Command { + public static readonly ID = '_typescript.quickFix.editorChatReplacement2'; + public readonly id = EditorChatFollowUp.ID; + + constructor( + private readonly client: ITypeScriptServiceClient, + ) { } + + async execute({ message, document, expand }: EditorChatFollowUp_Args) { + const initialRange = + expand.kind === 'navtree-function' + ? await findScopeEndLineFromNavTree( + this.client, + document, + expand.pos.line + ) + : expand.kind === 'refactor-info' + ? await findEditScope( + this.client, + document, + expand.refactor.edits.flatMap((e) => e.textChanges) + ) + : expand.kind === 'code-action' + ? await findEditScope( + this.client, + document, + expand.action.changes.flatMap((c) => c.textChanges) + ) + : expand.range; + await vscode.commands.executeCommand('vscode.editorChat.start', { + initialRange, + message, + autoSend: true, + }); + } +} +export interface EditorChatFollowUp_Args { + readonly message: string; + readonly document: vscode.TextDocument; + readonly expand: Expand; +} + +export class CompositeCommand implements Command { + public static readonly ID = '_typescript.compositeCommand'; + public readonly id = CompositeCommand.ID; + + public async execute(...commands: vscode.Command[]): Promise { + for (const command of commands) { + await vscode.commands.executeCommand( + command.command, + ...(command.arguments ?? []) + ); + } + } +} + +export type Expand = + | { kind: 'none'; readonly range: vscode.Range } + | { kind: 'navtree-function'; readonly pos: vscode.Position } + | { kind: 'refactor-info'; readonly refactor: Proto.RefactorEditInfo } + | { kind: 'code-action'; readonly action: Proto.CodeAction }; + +function findScopeEndLineFromNavTreeWorker( + startLine: number, + navigationTree: Proto.NavigationTree[] +): vscode.Range | undefined { + for (const node of navigationTree) { + const range = typeConverters.Range.fromTextSpan(node.spans[0]); + if (startLine === range.start.line) { + return range; + } else if ( + startLine > range.start.line && + startLine <= range.end.line && + node.childItems + ) { + return findScopeEndLineFromNavTreeWorker(startLine, node.childItems); + } + } + return undefined; +} + +async function findScopeEndLineFromNavTree( + client: ITypeScriptServiceClient, + document: vscode.TextDocument, + startLine: number +) { + const filepath = client.toOpenTsFilePath(document); + if (!filepath) { + return; + } + const response = await client.execute( + 'navtree', + { file: filepath }, + nulToken + ); + if (response.type !== 'response' || !response.body?.childItems) { + return; + } + return findScopeEndLineFromNavTreeWorker(startLine, response.body.childItems); +} + +async function findEditScope( + client: ITypeScriptServiceClient, + document: vscode.TextDocument, + edits: Proto.CodeEdit[] +): Promise { + let first = typeConverters.Position.fromLocation(edits[0].start); + let firstEdit = edits[0]; + let lastEdit = edits[0]; + let last = typeConverters.Position.fromLocation(edits[0].start); + for (const edit of edits) { + const start = typeConverters.Position.fromLocation(edit.start); + const end = typeConverters.Position.fromLocation(edit.end); + if (start.compareTo(first) < 0) { + first = start; + firstEdit = edit; + } + if (end.compareTo(last) > 0) { + last = end; + lastEdit = edit; + } + } + const text = document.getText(); + const startIndex = text.indexOf(firstEdit.newText); + const start = startIndex > -1 ? document.positionAt(startIndex) : first; + const endIndex = text.lastIndexOf(lastEdit.newText); + const end = + endIndex > -1 + ? document.positionAt(endIndex + lastEdit.newText.length) + : last; + const expandEnd = await findScopeEndLineFromNavTree( + client, + document, + end.line + ); + return new vscode.Range(start, expandEnd?.end ?? end); +} diff --git a/extensions/typescript-language-features/src/tsServer/protocol/fixNames.ts b/extensions/typescript-language-features/src/tsServer/protocol/fixNames.ts index 82df3b9cc46aab..c4234010302cf8 100644 --- a/extensions/typescript-language-features/src/tsServer/protocol/fixNames.ts +++ b/extensions/typescript-language-features/src/tsServer/protocol/fixNames.ts @@ -16,5 +16,9 @@ export const fixImport = 'import'; export const forgottenThisPropertyAccess = 'forgottenThisPropertyAccess'; export const removeUnnecessaryAwait = 'removeUnnecessaryAwait'; export const spelling = 'spelling'; +export const inferFromUsage = 'inferFromUsage'; +export const addNameToNamelessParameter = 'addNameToNamelessParameter'; +export const fixMissingFunctionDeclaration = 'fixMissingFunctionDeclaration'; +export const fixClassDoesntImplementInheritedAbstractMember = 'fixClassDoesntImplementInheritedAbstractMember'; export const unreachableCode = 'fixUnreachableCode'; export const unusedIdentifier = 'unusedIdentifier'; diff --git a/extensions/typescript-language-features/tsconfig.json b/extensions/typescript-language-features/tsconfig.json index 59169c18bfdcb8..73957dde9321e4 100644 --- a/extensions/typescript-language-features/tsconfig.json +++ b/extensions/typescript-language-features/tsconfig.json @@ -11,6 +11,6 @@ "include": [ "src/**/*", "../../src/vscode-dts/vscode.d.ts", - "../../src/vscode-dts/vscode.proposed.workspaceTrust.d.ts", + "../../src/vscode-dts/vscode.proposed.workspaceTrust.d.ts" ] } diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index 4f9f2c1965d9a6..65fa4206149b5a 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -38,7 +38,6 @@ "terminalDataWriteEvent", "terminalDimensions", "tunnels", - "envShellEvent", "testCoverage", "testObserver", "testMessageContextValue", diff --git a/package.json b/package.json index 37d9d55925f652..143e18fc874455 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", - "version": "1.83.0", - "distro": "51280fc5e9f5acdf5660e27d6b77f53aaad386cf", + "version": "1.84.0", + "distro": "7df09ac7212a2c85067c0e2ef8c1ec89686483d2", "author": { "name": "Microsoft Corporation" }, @@ -97,7 +97,7 @@ "xterm": "5.4.0-beta.27", "xterm-addon-canvas": "0.6.0-beta.27", "xterm-addon-image": "0.6.0-beta.21", - "xterm-addon-search": "0.14.0-beta.26", + "xterm-addon-search": "0.14.0-beta.27", "xterm-addon-serialize": "0.12.0-beta.26", "xterm-addon-unicode11": "0.7.0-beta.26", "xterm-addon-webgl": "0.17.0-beta.26", @@ -148,7 +148,7 @@ "cssnano": "^4.1.11", "debounce": "^1.0.0", "deemon": "^1.8.0", - "electron": "25.8.2", + "electron": "25.8.4", "eslint": "8.36.0", "eslint-plugin-header": "3.1.1", "eslint-plugin-jsdoc": "^46.5.0", diff --git a/product.json b/product.json index acb24971c39726..a708a5f4478c0b 100644 --- a/product.json +++ b/product.json @@ -52,8 +52,8 @@ }, { "name": "ms-vscode.js-debug", - "version": "1.82.0", - "sha256": "4fba41b4b764c3f5a6591d6d9a5bdc59b417f2d799071c889c2b54163f256282", + "version": "1.83.0", + "sha256": "8e81c3ba8e3b643c54f4dccc0b9402ea605c2bee57758bdfdda61501ea8a23d9", "repo": "https://github.com/microsoft/vscode-js-debug", "metadata": { "id": "25629058-ddac-4e17-abba-74678e126c5d", diff --git a/remote/package.json b/remote/package.json index 3b74941c201d0b..c5e49b5b692867 100644 --- a/remote/package.json +++ b/remote/package.json @@ -29,7 +29,7 @@ "xterm": "5.4.0-beta.27", "xterm-addon-canvas": "0.6.0-beta.27", "xterm-addon-image": "0.6.0-beta.21", - "xterm-addon-search": "0.14.0-beta.26", + "xterm-addon-search": "0.14.0-beta.27", "xterm-addon-serialize": "0.12.0-beta.26", "xterm-addon-unicode11": "0.7.0-beta.26", "xterm-addon-webgl": "0.17.0-beta.26", diff --git a/remote/web/package.json b/remote/web/package.json index c06fcbff6cc310..d5b3de253627a5 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -14,7 +14,7 @@ "xterm": "5.4.0-beta.27", "xterm-addon-canvas": "0.6.0-beta.27", "xterm-addon-image": "0.6.0-beta.21", - "xterm-addon-search": "0.14.0-beta.26", + "xterm-addon-search": "0.14.0-beta.27", "xterm-addon-unicode11": "0.7.0-beta.26", "xterm-addon-webgl": "0.17.0-beta.26" } diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index 91d7cdfb083580..3c4dfaae98ce64 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -78,10 +78,10 @@ xterm-addon-image@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.26: - version "0.14.0-beta.26" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.14.0-beta.26.tgz#2b5b8af31613c896d354c4799624090b11cd601c" - integrity sha512-CghsGO7fJa0efClbgZH20lh/JUaQYgJ1AJTPm8luc/eDc6DWOJblU0MxIABclLgT8lagv9+sOQfO0VIkAITxig== +xterm-addon-search@0.14.0-beta.27: + version "0.14.0-beta.27" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.14.0-beta.27.tgz#b6f81eac5047253a5c664349c47498a81b6ec168" + integrity sha512-T4Exwf/rqoLHqGUUIta5Pw/i9PljvroZwLxc7RnVyDqpNsTifDn3675kS54CxwqPlv4owFhxujTDzJPCUEkM2A== xterm-addon-unicode11@0.7.0-beta.26: version "0.7.0-beta.26" diff --git a/remote/yarn.lock b/remote/yarn.lock index 27f1fd84afbe63..71c0e9e5fac8ce 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -677,10 +677,10 @@ xterm-addon-image@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.26: - version "0.14.0-beta.26" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.14.0-beta.26.tgz#2b5b8af31613c896d354c4799624090b11cd601c" - integrity sha512-CghsGO7fJa0efClbgZH20lh/JUaQYgJ1AJTPm8luc/eDc6DWOJblU0MxIABclLgT8lagv9+sOQfO0VIkAITxig== +xterm-addon-search@0.14.0-beta.27: + version "0.14.0-beta.27" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.14.0-beta.27.tgz#b6f81eac5047253a5c664349c47498a81b6ec168" + integrity sha512-T4Exwf/rqoLHqGUUIta5Pw/i9PljvroZwLxc7RnVyDqpNsTifDn3675kS54CxwqPlv4owFhxujTDzJPCUEkM2A== xterm-addon-serialize@0.12.0-beta.26: version "0.12.0-beta.26" diff --git a/src/vs/base/browser/ui/icons/iconSelectBox.ts b/src/vs/base/browser/ui/icons/iconSelectBox.ts index a39532699b2bfa..6b3fe557bf934c 100644 --- a/src/vs/base/browser/ui/icons/iconSelectBox.ts +++ b/src/vs/base/browser/ui/icons/iconSelectBox.ts @@ -45,6 +45,7 @@ export class IconSelectBox extends Disposable { protected inputBox: InputBox | undefined; private scrollableElement: DomScrollableElement | undefined; + private iconsContainer: HTMLElement | undefined; private iconIdElement: HighlightedLabel | undefined; private readonly iconContainerWidth = 36; private readonly iconContainerHeight = 36; @@ -70,8 +71,7 @@ export class IconSelectBox extends Disposable { inputBoxStyles: this.options.inputBoxStyles, })); - const iconsContainer = dom.$('.icon-select-icons-container', { id: `${this.domId}_icons` }); - iconsContainer.style.paddingRight = '10px'; + const iconsContainer = this.iconsContainer = dom.$('.icon-select-icons-container', { id: `${this.domId}_icons` }); iconsContainer.role = 'listbox'; iconsContainer.tabIndex = 0; this.scrollableElement = disposables.add(new DomScrollableElement(iconsContainer, { @@ -218,16 +218,22 @@ export class IconSelectBox extends Disposable { this.domNode.style.width = `${dimension.width}px`; this.domNode.style.height = `${dimension.height}px`; - const iconsContainerWidth = dimension.width - 40; + const iconsContainerWidth = dimension.width - 30; this.numberOfElementsPerRow = Math.floor(iconsContainerWidth / this.iconContainerWidth); if (this.numberOfElementsPerRow === 0) { throw new Error('Insufficient width'); } const extraSpace = iconsContainerWidth % this.iconContainerWidth; - const margin = Math.floor(extraSpace / this.numberOfElementsPerRow); + const iconElementMargin = Math.floor(extraSpace / this.numberOfElementsPerRow); for (const { element } of this.renderedIcons) { - element.style.marginRight = `${margin}px`; + element.style.marginRight = `${iconElementMargin}px`; + } + + const containerPadding = extraSpace % this.numberOfElementsPerRow; + if (this.iconsContainer) { + this.iconsContainer.style.paddingLeft = `${Math.floor(containerPadding / 2)}px`; + this.iconsContainer.style.paddingRight = `${Math.ceil(containerPadding / 2)}px`; } if (this.scrollableElement) { diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index 061003dbc9a43b..5bec50e32808b4 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -505,7 +505,7 @@ export class MenuBar extends Disposable { // If below minimium menu threshold, show the overflow menu only as hamburger menu - if (this.numMenusShown - 1 <= showableMenus.length / 2) { + if (this.numMenusShown - 1 <= showableMenus.length / 4) { for (const menuBarMenu of showableMenus) { menuBarMenu.buttonElement.style.visibility = 'hidden'; } diff --git a/src/vs/base/common/actions.ts b/src/vs/base/common/actions.ts index b42e5cdb61239e..bb71e5555d6d87 100644 --- a/src/vs/base/common/actions.ts +++ b/src/vs/base/common/actions.ts @@ -265,7 +265,7 @@ export function toAction(props: { id: string; label: string; enabled?: boolean; class: undefined, enabled: props.enabled ?? true, checked: props.checked ?? false, - run: async (...args: unknown[]) => props.run(), + run: async (...args: unknown[]) => props.run(...args), tooltip: props.label }; } diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index bd0f90aa878e23..9b510f822513e5 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -347,7 +347,7 @@ export function coalesce(array: ReadonlyArray): T[] { /** * Remove all falsy values from `array`. The original array IS modified. */ -export function coalesceInPlace(array: Array): void { +export function coalesceInPlace(array: Array): asserts array is Array { let to = 0; for (let i = 0; i < array.length; i++) { if (!!array[i]) { diff --git a/src/vs/base/common/product.ts b/src/vs/base/common/product.ts index ea129e9298a5a6..3a935977ec4cc9 100644 --- a/src/vs/base/common/product.ts +++ b/src/vs/base/common/product.ts @@ -114,6 +114,7 @@ export interface IProductConfiguration { readonly languageExtensionTips?: readonly string[]; readonly trustedExtensionUrlPublicKeys?: IStringDictionary; readonly trustedExtensionAuthAccess?: readonly string[]; + readonly trustedExtensionProtocolHandlers?: readonly string[]; readonly commandPaletteSuggestedCommandIds?: string[]; diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index 38d7e5857138e4..f943347519e1d6 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -1129,7 +1129,11 @@ export namespace ProxyChannel { } } - return target.apply(handler, args); + let res = target.apply(handler, args); + if (!(res instanceof Promise)) { + res = Promise.resolve(res); + } + return res; } throw new ErrorNoTelemetry(`Method not found: ${command}`); diff --git a/src/vs/base/parts/ipc/node/ipc.net.ts b/src/vs/base/parts/ipc/node/ipc.net.ts index ed30467961b7c5..e47b6c70b234ca 100644 --- a/src/vs/base/parts/ipc/node/ipc.net.ts +++ b/src/vs/base/parts/ipc/node/ipc.net.ts @@ -201,6 +201,11 @@ interface ISocketTracer { traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void; } +interface FrameOptions { + compressed: boolean; + opcode: number; +} + /** * See https://tools.ietf.org/html/rfc6455#section-5.2 */ @@ -219,7 +224,8 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT fin: 0, compressed: false, firstFrameOfMessage: true, - mask: 0 + mask: 0, + opcode: 0 }; public get permessageDeflate(): boolean { @@ -256,7 +262,7 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT inflateBytes, recordInflateBytes, this._onData, - (data, compressed) => this._write(data, compressed) + (data, options) => this._write(data, options) )); this._register(this._flowManager.onError((err) => { // zlib errors are fatal, since we have no idea how to recover @@ -319,12 +325,12 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT let start = 0; while (start < buffer.byteLength) { - this._flowManager.writeMessage(buffer.slice(start, Math.min(start + Constants.MaxWebSocketMessageLength, buffer.byteLength))); + this._flowManager.writeMessage(buffer.slice(start, Math.min(start + Constants.MaxWebSocketMessageLength, buffer.byteLength)), { compressed: true, opcode: 0x02 /* Binary frame */ }); start += Constants.MaxWebSocketMessageLength; } } - private _write(buffer: VSBuffer, compressed: boolean): void { + private _write(buffer: VSBuffer, { compressed, opcode }: FrameOptions): void { if (this._isEnded) { // Avoid ERR_STREAM_WRITE_AFTER_END return; @@ -341,12 +347,10 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT } const header = VSBuffer.alloc(headerLen); - if (compressed) { - // The RSV1 bit indicates a compressed frame - header.writeUInt8(0b11000010, 0); - } else { - header.writeUInt8(0b10000010, 0); - } + // The RSV1 bit indicates a compressed frame + const compressedFlag = compressed ? 0b01000000 : 0; + const opcodeFlag = opcode & 0b00001111; + header.writeUInt8(0b10000000 | compressedFlag | opcodeFlag, 0); if (buffer.byteLength < 126) { header.writeUInt8(buffer.byteLength, 1); } else if (buffer.byteLength < 2 ** 16) { @@ -390,6 +394,8 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT const firstByte = peekHeader.readUInt8(0); const finBit = (firstByte & 0b10000000) >>> 7; const rsv1Bit = (firstByte & 0b01000000) >>> 6; + const opcode = (firstByte & 0b00001111); + const secondByte = peekHeader.readUInt8(1); const hasMask = (secondByte & 0b10000000) >>> 7; const len = (secondByte & 0b01111111); @@ -403,8 +409,9 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT } this._state.firstFrameOfMessage = Boolean(finBit); this._state.mask = 0; + this._state.opcode = opcode; - this.traceSocketEvent(SocketDiagnosticsEventType.WebSocketNodeSocketPeekedHeader, { headerSize: this._state.readLen, compressed: this._state.compressed, fin: this._state.fin }); + this.traceSocketEvent(SocketDiagnosticsEventType.WebSocketNodeSocketPeekedHeader, { headerSize: this._state.readLen, compressed: this._state.compressed, fin: this._state.fin, opcode: this._state.opcode }); } else if (this._state.state === ReadState.ReadHeader) { // read entire header @@ -446,7 +453,7 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT this._state.readLen = len; this._state.mask = mask; - this.traceSocketEvent(SocketDiagnosticsEventType.WebSocketNodeSocketPeekedHeader, { bodySize: this._state.readLen, compressed: this._state.compressed, fin: this._state.fin, mask: this._state.mask }); + this.traceSocketEvent(SocketDiagnosticsEventType.WebSocketNodeSocketPeekedHeader, { bodySize: this._state.readLen, compressed: this._state.compressed, fin: this._state.fin, mask: this._state.mask, opcode: this._state.opcode }); } else if (this._state.state === ReadState.ReadBody) { // read body @@ -461,7 +468,12 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT this._state.readLen = Constants.MinHeaderByteSize; this._state.mask = 0; - this._flowManager.acceptFrame(body, this._state.compressed, !!this._state.fin); + if (this._state.opcode <= 0x02 /* Continuation frame or Text frame or binary frame */) { + this._flowManager.acceptFrame(body, this._state.compressed, !!this._state.fin); + } else if (this._state.opcode === 0x09 /* Ping frame */) { + // Ping frames could be send by some browsers e.g. Firefox + this._flowManager.writeMessage(body, { compressed: false, opcode: 0x0A /* Pong frame */ }); + } } } } @@ -483,7 +495,7 @@ class WebSocketFlowManager extends Disposable { private readonly _zlibInflateStream: ZlibInflateStream | null; private readonly _zlibDeflateStream: ZlibDeflateStream | null; - private readonly _writeQueue: VSBuffer[] = []; + private readonly _writeQueue: { data: VSBuffer; options: FrameOptions }[] = []; private readonly _readQueue: { data: VSBuffer; isCompressed: boolean; isLastFrameOfMessage: boolean }[] = []; private readonly _onDidFinishProcessingReadQueue = this._register(new Emitter()); @@ -509,7 +521,7 @@ class WebSocketFlowManager extends Disposable { inflateBytes: VSBuffer | null, recordInflateBytes: boolean, private readonly _onData: Emitter, - private readonly _writeFn: (data: VSBuffer, compressed: boolean) => void + private readonly _writeFn: (data: VSBuffer, options: FrameOptions) => void ) { super(); if (permessageDeflate) { @@ -526,8 +538,8 @@ class WebSocketFlowManager extends Disposable { } } - public writeMessage(message: VSBuffer): void { - this._writeQueue.push(message); + public writeMessage(data: VSBuffer, options: FrameOptions): void { + this._writeQueue.push({ data, options }); this._processWriteQueue(); } @@ -538,12 +550,12 @@ class WebSocketFlowManager extends Disposable { } this._isProcessingWriteQueue = true; while (this._writeQueue.length > 0) { - const message = this._writeQueue.shift()!; - if (this._zlibDeflateStream) { - const data = await this._deflateMessage(this._zlibDeflateStream, message); - this._writeFn(data, true); + const { data, options } = this._writeQueue.shift()!; + if (this._zlibDeflateStream && options.compressed) { + const compressedData = await this._deflateMessage(this._zlibDeflateStream, data); + this._writeFn(compressedData, options); } else { - this._writeFn(message, false); + this._writeFn(data, { ...options, compressed: false }); } } this._isProcessingWriteQueue = false; diff --git a/src/vs/base/parts/ipc/test/node/ipc.net.test.ts b/src/vs/base/parts/ipc/test/node/ipc.net.test.ts index 42d77c57bdc76e..8255c780ccf5b5 100644 --- a/src/vs/base/parts/ipc/test/node/ipc.net.test.ts +++ b/src/vs/base/parts/ipc/test/node/ipc.net.test.ts @@ -603,6 +603,8 @@ suite('WebSocketNodeSocket', () => { private readonly _onClose = new Emitter(); public readonly onClose = this._onClose.event; + public writtenData: VSBuffer[] = []; + public traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void { } @@ -610,6 +612,10 @@ suite('WebSocketNodeSocket', () => { super(); } + public write(data: VSBuffer): void { + this.writtenData.push(data); + } + public fireData(data: number[]): void { this._onData.fire(VSBuffer.wrap(toUint8Array(data))); } @@ -746,6 +752,40 @@ suite('WebSocketNodeSocket', () => { assert.strictEqual(receivingSideOnDataCallCount, 4); }); + test('issue #194284: ping/pong opcodes are supported', async () => { + + const disposables = new DisposableStore(); + const socket = new FakeNodeSocket(); + const webSocket = disposables.add(new WebSocketNodeSocket(socket, false, null, false)); + + let receivedData: string = ''; + disposables.add(webSocket.onData((buff) => { + receivedData += fromCharCodeArray(fromUint8Array(buff.buffer)); + })); + + // A single-frame non-compressed text message that contains "Hello" + socket.fireData([0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]); + + // A ping message that contains "data" + socket.fireData([0x89, 0x04, 0x64, 0x61, 0x74, 0x61]); + + // Another single-frame non-compressed text message that contains "Hello" + socket.fireData([0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]); + + assert.strictEqual(receivedData, 'HelloHello'); + assert.deepStrictEqual( + socket.writtenData.map(x => fromUint8Array(x.buffer)), + [ + // A pong message that contains "data" + [0x8A, 0x04, 0x64, 0x61, 0x74, 0x61] + ] + ); + + disposables.dispose(); + + return receivedData; + }); + function generateRandomBuffer(size: number): VSBuffer { const buff = VSBuffer.alloc(size); for (let i = 0; i < size; i++) { diff --git a/src/vs/base/worker/workerMain.ts b/src/vs/base/worker/workerMain.ts index ec18c5ca0f4688..35c685ff771548 100644 --- a/src/vs/base/worker/workerMain.ts +++ b/src/vs/base/worker/workerMain.ts @@ -84,7 +84,7 @@ text = `${text}\n//# sourceURL=${loaderSrc}`; const func = ( trustedTypesPolicy - ? globalThis.eval(trustedTypesPolicy.createScript('', text) as unknown as string) + ? globalThis.eval(trustedTypesPolicy.createScript('', text) as unknown as string) // CodeQL [SM01632] fetch + eval is used on the web worker instead of importScripts if possible because importScripts is synchronous and we observed deadlocks on Safari : new Function(text) // CodeQL [SM01632] fetch + eval is used on the web worker instead of importScripts if possible because importScripts is synchronous and we observed deadlocks on Safari ); func.call(globalThis); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 2d491e240ffb00..8eaea72ba029cd 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -41,8 +41,6 @@ import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper'; import { getResolvedShellEnv } from 'vs/platform/shell/node/shellEnv'; -import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/common/extensionUrlTrust'; -import { ExtensionUrlTrustService } from 'vs/platform/extensionManagement/node/extensionUrlTrustService'; import { IExtensionHostStarter, ipcExtensionHostStarterChannelName } from 'vs/platform/extensions/common/extensionHostStarter'; import { ExtensionHostStarter } from 'vs/platform/extensions/electron-main/extensionHostStarter'; import { IExternalTerminalMainService } from 'vs/platform/externalTerminal/electron-main/externalTerminal'; @@ -968,9 +966,6 @@ export class CodeApplication extends Disposable { // Menubar services.set(IMenubarMainService, new SyncDescriptor(MenubarMainService)); - // Extension URL Trust - services.set(IExtensionUrlTrustService, new SyncDescriptor(ExtensionUrlTrustService)); - // Extension Host Starter services.set(IExtensionHostStarter, new SyncDescriptor(ExtensionHostStarter)); @@ -1118,10 +1113,6 @@ export class CodeApplication extends Disposable { const urlChannel = ProxyChannel.fromService(accessor.get(IURLService), disposables); mainProcessElectronServer.registerChannel('url', urlChannel); - // Extension URL Trust - const extensionUrlTrustChannel = ProxyChannel.fromService(accessor.get(IExtensionUrlTrustService), disposables); - mainProcessElectronServer.registerChannel('extensionUrlTrust', extensionUrlTrustChannel); - // Webview Manager const webviewChannel = ProxyChannel.fromService(accessor.get(IWebviewManagerService), disposables); mainProcessElectronServer.registerChannel('webview', webviewChannel); diff --git a/src/vs/code/node/sharedProcess/sharedProcessMain.ts b/src/vs/code/node/sharedProcess/sharedProcessMain.ts index 48791c1b7bbdba..6a26fb9ad17a75 100644 --- a/src/vs/code/node/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/node/sharedProcess/sharedProcessMain.ts @@ -56,7 +56,7 @@ import { ICustomEndpointTelemetryService, ITelemetryService } from 'vs/platform/ import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetryIpc'; import { TelemetryLogAppender } from 'vs/platform/telemetry/common/telemetryLogAppender'; import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; -import { supportsTelemetry, ITelemetryAppender, NullAppender, NullTelemetryService, getPiiPathsFromEnvironment, isInternalTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; +import { supportsTelemetry, ITelemetryAppender, NullAppender, NullTelemetryService, getPiiPathsFromEnvironment, isInternalTelemetry, isLoggingOnly } from 'vs/platform/telemetry/common/telemetryUtils'; import { CustomEndpointTelemetryService } from 'vs/platform/telemetry/node/customEndpointTelemetryService'; import { ExtensionStorageService, IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage'; import { IgnoredExtensionsManagementService, IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; @@ -158,6 +158,8 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter { instantiationService.invokeFunction(accessor => { const logService = accessor.get(ILogService); + const telemetryService = accessor.get(ITelemetryService); + const userDataProfilesService = accessor.get(IUserDataProfilesService); // Log info logService.trace('sharedProcess configuration', JSON.stringify(this.configuration)); @@ -167,6 +169,10 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter { // Error handler this.registerErrorHandler(logService); + + // Report Profiles Info + this.reportProfilesInfo(telemetryService, userDataProfilesService); + this._register(userDataProfilesService.onDidChangeProfiles(() => this.reportProfilesInfo(telemetryService, userDataProfilesService))); }); // Instantiate Contributions @@ -289,7 +295,7 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter { if (supportsTelemetry(productService, environmentService)) { const logAppender = new TelemetryLogAppender(logService, loggerService, environmentService, productService); appenders.push(logAppender); - if (productService.aiConfig?.ariaKey) { + if (!isLoggingOnly(productService, environmentService) && productService.aiConfig?.ariaKey) { const collectorAppender = new OneDataSystemAppender(requestService, internalTelemetry, 'monacoworkbench', null, productService.aiConfig.ariaKey); this._register(toDisposable(() => collectorAppender.flush())); // Ensure the 1DS appender is disposed so that it flushes remaining data appenders.push(collectorAppender); @@ -444,6 +450,20 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter { }); } + private reportProfilesInfo(telemetryService: ITelemetryService, userDataProfilesService: IUserDataProfilesService): void { + type ProfilesInfoClassification = { + owner: 'sandy081'; + comment: 'Report profiles information'; + count: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of profiles' }; + }; + type ProfilesInfoEvent = { + count: number; + }; + telemetryService.publicLog2('profilesInfo', { + count: userDataProfilesService.profiles.length + }); + } + handledClientConnection(e: MessageEvent): boolean { // This filter on message port messages will look for diff --git a/src/vs/editor/browser/config/migrateOptions.ts b/src/vs/editor/browser/config/migrateOptions.ts index 362b68ce840f33..1e1067a3046c7b 100644 --- a/src/vs/editor/browser/config/migrateOptions.ts +++ b/src/vs/editor/browser/config/migrateOptions.ts @@ -192,3 +192,22 @@ registerEditorSettingMigration('experimental.stickyScroll.maxLineCount', (value, } } }); + +// Code Actions on Save +// registerEditorSettingMigration('codeActionsOnSave', (value, read, write) => { +// if (value && typeof value === 'object') { +// let toBeModified = false; +// const newValue = {} as any; +// for (const entry of Object.entries(value)) { +// if (typeof entry[1] === 'boolean') { +// toBeModified = true; +// newValue[entry[0]] = entry[1] ? 'explicit' : 'never'; +// } else { +// newValue[entry[0]] = entry[1]; +// } +// } +// if (toBeModified) { +// write(`codeActionsOnSave`, newValue); +// } +// } +// }); diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditorSash.ts b/src/vs/editor/browser/widget/diffEditor/diffEditorSash.ts index 3e6664ff84628f..b5aeba205efdf4 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditorSash.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditorSash.ts @@ -47,6 +47,7 @@ export class DiffEditorSash extends Disposable { const enabled = this._options.enableSplitViewResizing.read(reader); this._sash.state = enabled ? SashState.Enabled : SashState.Disabled; this.sashLeft.read(reader); + this._dimensions.height.read(reader); this._sash.layout(); })); } @@ -55,6 +56,7 @@ export class DiffEditorSash extends Disposable { this._sash.orthogonalEndSash = sashes.bottom; } + /** @pure */ private _computeSashLeft(desiredRatio: number, reader: IReader | undefined): number { const contentWidth = this._dimensions.width.read(reader); const midPoint = Math.floor(this._options.splitViewDefaultRatio.read(reader) * contentWidth); diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditorViewModel.ts b/src/vs/editor/browser/widget/diffEditor/diffEditorViewModel.ts index 28914cc7421573..6b81646b0f9459 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditorViewModel.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditorViewModel.ts @@ -14,12 +14,13 @@ import { ISerializedLineRange, LineRange } from 'vs/editor/common/core/lineRange import { DefaultLinesDiffComputer } from 'vs/editor/common/diff/defaultLinesDiffComputer/defaultLinesDiffComputer'; import { IDocumentDiff } from 'vs/editor/common/diff/documentDiffProvider'; import { MovedText } from 'vs/editor/common/diff/linesDiffComputer'; -import { DetailedLineRangeMapping } from 'vs/editor/common/diff/rangeMapping'; +import { DetailedLineRangeMapping, RangeMapping } from 'vs/editor/common/diff/rangeMapping'; import { IDiffEditorModel, IDiffEditorViewModel } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import { TextEditInfo } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/beforeEditPositionMapper'; import { combineTextEditInfos } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/combineTextEditInfos'; import { DiffEditorOptions } from './diffEditorOptions'; +import { optimizeSequenceDiffs } from 'vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations'; export class DiffEditorViewModel extends Disposable implements IDiffEditorViewModel { private readonly _isDiffUpToDate = observableValue(this, false); @@ -189,6 +190,7 @@ export class DiffEditorViewModel extends Disposable implements IDiffEditorViewMo documentDiffProvider.onChangeSignal.read(reader); readHotReloadableExport(DefaultLinesDiffComputer, reader); + readHotReloadableExport(optimizeSequenceDiffs, reader); this._isDiffUpToDate.set(false, undefined); @@ -214,6 +216,7 @@ export class DiffEditorViewModel extends Disposable implements IDiffEditorViewMo return; } + result = normalizeDocumentDiff(result, model.original, model.modified); result = applyOriginalEdits(result, originalTextEditInfos, model.original, model.modified) ?? result; result = applyModifiedEdits(result, modifiedTextEditInfos, model.original, model.modified) ?? result; @@ -283,6 +286,35 @@ export class DiffEditorViewModel extends Disposable implements IDiffEditorViewMo } } +function normalizeDocumentDiff(diff: IDocumentDiff, original: ITextModel, modified: ITextModel): IDocumentDiff { + return { + changes: diff.changes.map(c => new DetailedLineRangeMapping( + c.original, + c.modified, + c.innerChanges ? c.innerChanges.map(i => normalizeRangeMapping(i, original, modified)) : undefined + )), + moves: diff.moves, + identical: diff.identical, + quitEarly: diff.quitEarly, + }; +} + +function normalizeRangeMapping(rangeMapping: RangeMapping, original: ITextModel, modified: ITextModel): RangeMapping { + let originalRange = rangeMapping.originalRange; + let modifiedRange = rangeMapping.modifiedRange; + if ( + (originalRange.endColumn !== 1 || modifiedRange.endColumn !== 1) && + originalRange.endColumn === original.getLineMaxColumn(originalRange.endLineNumber) + && modifiedRange.endColumn === modified.getLineMaxColumn(modifiedRange.endLineNumber) + && originalRange.endLineNumber < original.getLineCount() + && modifiedRange.endLineNumber < modified.getLineCount() + ) { + originalRange = originalRange.setEndPosition(originalRange.endLineNumber + 1, 1); + modifiedRange = modifiedRange.setEndPosition(modifiedRange.endLineNumber + 1, 1); + } + return new RangeMapping(originalRange, modifiedRange); +} + interface SerializedState { collapsedRegions: { range: ISerializedLineRange }[]; } diff --git a/src/vs/editor/browser/widget/diffEditor/lineAlignment.ts b/src/vs/editor/browser/widget/diffEditor/lineAlignment.ts index 02502e99971e67..893e4000c93b97 100644 --- a/src/vs/editor/browser/widget/diffEditor/lineAlignment.ts +++ b/src/vs/editor/browser/widget/diffEditor/lineAlignment.ts @@ -141,6 +141,7 @@ export class ViewZoneManager extends Disposable { domNode: document.createElement('div'), heightInPx: modifiedTopPaddingVal, showInHiddenAreas: true, + suppressMouseDown: true, }); } const originalTopPaddingVal = this._originalTopPadding.read(reader); @@ -150,6 +151,7 @@ export class ViewZoneManager extends Disposable { domNode: document.createElement('div'), heightInPx: originalTopPaddingVal, showInHiddenAreas: true, + suppressMouseDown: true, }); } @@ -237,6 +239,7 @@ export class ViewZoneManager extends Disposable { domNode: createFakeLinesDiv(), heightInPx: (count - 1) * modLineHeight, showInHiddenAreas: true, + suppressMouseDown: true, }); } } @@ -249,6 +252,7 @@ export class ViewZoneManager extends Disposable { marginDomNode, setZoneId(id) { zoneId = id; }, showInHiddenAreas: true, + suppressMouseDown: true, }); } @@ -261,6 +265,7 @@ export class ViewZoneManager extends Disposable { heightInPx: a.modifiedHeightInPx, marginDomNode, showInHiddenAreas: true, + suppressMouseDown: true, }); } else { const delta = a.modifiedHeightInPx - a.originalHeightInPx; @@ -274,6 +279,7 @@ export class ViewZoneManager extends Disposable { domNode: createFakeLinesDiv(), heightInPx: delta, showInHiddenAreas: true, + suppressMouseDown: true, }); } else { if (syncedMovedText?.lineRangeMapping.modified.delta(-1).deltaLength(2).contains(a.modifiedRange.endLineNumberExclusive - 1)) { @@ -297,6 +303,7 @@ export class ViewZoneManager extends Disposable { heightInPx: -delta, marginDomNode, showInHiddenAreas: true, + suppressMouseDown: true, }); } } @@ -316,6 +323,7 @@ export class ViewZoneManager extends Disposable { domNode: createFakeLinesDiv(), heightInPx: delta, showInHiddenAreas: true, + suppressMouseDown: true, }); } else { modViewZones.push({ @@ -323,6 +331,7 @@ export class ViewZoneManager extends Disposable { domNode: createFakeLinesDiv(), heightInPx: -delta, showInHiddenAreas: true, + suppressMouseDown: true, }); } } diff --git a/src/vs/editor/common/core/offsetRange.ts b/src/vs/editor/common/core/offsetRange.ts index 02053528c4c0c4..7e78d30b591778 100644 --- a/src/vs/editor/common/core/offsetRange.ts +++ b/src/vs/editor/common/core/offsetRange.ts @@ -5,10 +5,15 @@ import { BugIndicatingError } from 'vs/base/common/errors'; +export interface IOffsetRange { + readonly start: number; + readonly endExclusive: number; +} + /** * A range of offsets (0-based). */ -export class OffsetRange { +export class OffsetRange implements IOffsetRange { public static addRange(range: OffsetRange, sortedRanges: OffsetRange[]): void { let i = 0; while (i < sortedRanges.length && sortedRanges[i].endExclusive < range.start) { diff --git a/src/vs/editor/common/diff/defaultLinesDiffComputer/computeMovedLines.ts b/src/vs/editor/common/diff/defaultLinesDiffComputer/computeMovedLines.ts index 75ad5f23d5247b..aa3adeb7a299e9 100644 --- a/src/vs/editor/common/diff/defaultLinesDiffComputer/computeMovedLines.ts +++ b/src/vs/editor/common/diff/defaultLinesDiffComputer/computeMovedLines.ts @@ -307,14 +307,12 @@ function joinCloseConsecutiveMoves(moves: LineRangeMapping[]): LineRangeMapping[ function removeMovesInSameDiff(changes: DetailedLineRangeMapping[], moves: LineRangeMapping[]) { const changesMonotonous = new MonotonousArray(changes); moves = moves.filter(m => { - const diffBeforeOriginalMove = changesMonotonous.findLastMonotonous(c => c.original.endLineNumberExclusive <= m.original.startLineNumber) + const diffBeforeEndOfMoveOriginal = changesMonotonous.findLastMonotonous(c => c.original.endLineNumberExclusive < m.original.endLineNumberExclusive) || new LineRangeMapping(new LineRange(1, 1), new LineRange(1, 1)); + const diffBeforeEndOfMoveModified = findLastMonotonous(changes, c => c.modified.endLineNumberExclusive < m.modified.endLineNumberExclusive); - const modifiedDistToPrevDiff = m.modified.startLineNumber - diffBeforeOriginalMove.modified.endLineNumberExclusive; - const originalDistToPrevDiff = m.original.startLineNumber - diffBeforeOriginalMove.original.endLineNumberExclusive; - - const differentDistances = modifiedDistToPrevDiff !== originalDistToPrevDiff; - return differentDistances; + const differentDiffs = diffBeforeEndOfMoveOriginal !== diffBeforeEndOfMoveModified; + return differentDiffs; }); return moves; } diff --git a/src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts b/src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts index eb9c36b166860b..64b3b75d317495 100644 --- a/src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts +++ b/src/vs/editor/common/diff/defaultLinesDiffComputer/heuristicSequenceOptimizations.ts @@ -79,8 +79,8 @@ function joinSequenceDiffsByShifting(sequence1: ISequence, sequence2: ISequence, let d; for (d = 0; d < length; d++) { if ( - sequence1.getElement(cur.seq1Range.start + d) !== sequence1.getElement(cur.seq1Range.endExclusive + d) || - sequence2.getElement(cur.seq2Range.start + d) !== sequence2.getElement(cur.seq2Range.endExclusive + d) + !sequence1.isStronglyEqual(cur.seq1Range.start + d, cur.seq1Range.endExclusive + d) || + !sequence2.isStronglyEqual(cur.seq2Range.start + d, cur.seq2Range.endExclusive + d) ) { break; } diff --git a/src/vs/editor/common/services/modelService.ts b/src/vs/editor/common/services/modelService.ts index 4de8d88004d0d6..2bbda14a026a31 100644 --- a/src/vs/editor/common/services/modelService.ts +++ b/src/vs/editor/common/services/modelService.ts @@ -29,17 +29,6 @@ function MODEL_ID(resource: URI): string { return resource.toString(); } -function computeModelSha1(model: ITextModel): string { - // compute the sha1 - const shaComputer = new StringSHA1(); - const snapshot = model.createSnapshot(); - let text: string | null; - while ((text = snapshot.read())) { - shaComputer.update(text); - } - return shaComputer.digest(); -} - class ModelData implements IDisposable { private readonly _modelEventListeners = new DisposableStore(); @@ -337,7 +326,12 @@ export class ModelService extends Disposable implements IModelService { if (resource && this._disposedModels.has(MODEL_ID(resource))) { const disposedModelData = this._removeDisposedModel(resource)!; const elements = this._undoRedoService.getElements(resource); - const sha1IsEqual = (computeModelSha1(model) === disposedModelData.sha1); + const sha1Computer = this._getSHA1Computer(); + const sha1IsEqual = ( + sha1Computer.canComputeSHA1(model) + ? sha1Computer.computeSHA1(model) === disposedModelData.sha1 + : false + ); if (sha1IsEqual || disposedModelData.sharesUndoRedoStack) { for (const element of elements.past) { if (isEditStackElement(element) && element.matchesResource(resource)) { @@ -535,6 +529,7 @@ export class ModelService extends Disposable implements IModelService { } const maxMemory = ModelService.MAX_MEMORY_FOR_CLOSED_FILES_UNDO_STACK; + const sha1Computer = this._getSHA1Computer(); if (!maintainUndoRedoStack) { if (!sharesUndoRedoStack) { const initialUndoRedoSnapshot = modelData.model.getInitialUndoRedoSnapshot(); @@ -542,8 +537,8 @@ export class ModelService extends Disposable implements IModelService { this._undoRedoService.restoreSnapshot(initialUndoRedoSnapshot); } } - } else if (!sharesUndoRedoStack && heapSize > maxMemory) { - // the undo stack for this file would never fit in the configured memory, so don't bother with it. + } else if (!sharesUndoRedoStack && (heapSize > maxMemory || !sha1Computer.canComputeSHA1(model))) { + // the undo stack for this file would never fit in the configured memory or the file is very large, so don't bother with it. const initialUndoRedoSnapshot = modelData.model.getInitialUndoRedoSnapshot(); if (initialUndoRedoSnapshot !== null) { this._undoRedoService.restoreSnapshot(initialUndoRedoSnapshot); @@ -552,7 +547,7 @@ export class ModelService extends Disposable implements IModelService { this._ensureDisposedModelsHeapSize(maxMemory - heapSize); // We only invalidate the elements, but they remain in the undo-redo service. this._undoRedoService.setElementsValidFlag(model.uri, false, (element) => (isEditStackElement(element) && element.matchesResource(model.uri))); - this._insertDisposedModel(new DisposedModelInfo(model.uri, modelData.model.getInitialUndoRedoSnapshot(), Date.now(), sharesUndoRedoStack, heapSize, computeModelSha1(model), model.getVersionId(), model.getAlternativeVersionId())); + this._insertDisposedModel(new DisposedModelInfo(model.uri, modelData.model.getInitialUndoRedoSnapshot(), Date.now(), sharesUndoRedoStack, heapSize, sha1Computer.computeSHA1(model), model.getVersionId(), model.getAlternativeVersionId())); } delete this._models[modelId]; @@ -572,4 +567,33 @@ export class ModelService extends Disposable implements IModelService { ModelService._setModelOptionsForModel(model, newOptions, oldOptions); this._onModelModeChanged.fire({ model, oldLanguageId: oldLanguageId }); } + + protected _getSHA1Computer(): ITextModelSHA1Computer { + return new DefaultModelSHA1Computer(); + } +} + +export interface ITextModelSHA1Computer { + canComputeSHA1(model: ITextModel): boolean; + computeSHA1(model: ITextModel): string; +} + +export class DefaultModelSHA1Computer implements ITextModelSHA1Computer { + + public static MAX_MODEL_SIZE = 10 * 1024 * 1024; // takes 200ms to compute a sha1 on a 10MB model on a new machine + + canComputeSHA1(model: ITextModel): boolean { + return (model.getValueLength() <= DefaultModelSHA1Computer.MAX_MODEL_SIZE); + } + + computeSHA1(model: ITextModel): string { + // compute the sha1 + const shaComputer = new StringSHA1(); + const snapshot = model.createSnapshot(); + let text: string | null; + while ((text = snapshot.read())) { + shaComputer.update(text); + } + return shaComputer.digest(); + } } diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index 44f9fb9b434060..ec6cf6914929bb 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -572,28 +572,28 @@ export enum KeyCode { * Either the angle bracket key or the backslash key on the RT 102-key keyboard. */ IntlBackslash = 97, - Numpad0 = 98,// VK_NUMPAD0, 0x60, Numeric keypad 0 key - Numpad1 = 99,// VK_NUMPAD1, 0x61, Numeric keypad 1 key - Numpad2 = 100,// VK_NUMPAD2, 0x62, Numeric keypad 2 key - Numpad3 = 101,// VK_NUMPAD3, 0x63, Numeric keypad 3 key - Numpad4 = 102,// VK_NUMPAD4, 0x64, Numeric keypad 4 key - Numpad5 = 103,// VK_NUMPAD5, 0x65, Numeric keypad 5 key - Numpad6 = 104,// VK_NUMPAD6, 0x66, Numeric keypad 6 key - Numpad7 = 105,// VK_NUMPAD7, 0x67, Numeric keypad 7 key - Numpad8 = 106,// VK_NUMPAD8, 0x68, Numeric keypad 8 key - Numpad9 = 107,// VK_NUMPAD9, 0x69, Numeric keypad 9 key - NumpadMultiply = 108,// VK_MULTIPLY, 0x6A, Multiply key - NumpadAdd = 109,// VK_ADD, 0x6B, Add key - NUMPAD_SEPARATOR = 110,// VK_SEPARATOR, 0x6C, Separator key - NumpadSubtract = 111,// VK_SUBTRACT, 0x6D, Subtract key - NumpadDecimal = 112,// VK_DECIMAL, 0x6E, Decimal key - NumpadDivide = 113,// VK_DIVIDE, 0x6F, + Numpad0 = 98, + Numpad1 = 99, + Numpad2 = 100, + Numpad3 = 101, + Numpad4 = 102, + Numpad5 = 103, + Numpad6 = 104, + Numpad7 = 105, + Numpad8 = 106, + Numpad9 = 107, + NumpadMultiply = 108, + NumpadAdd = 109, + NUMPAD_SEPARATOR = 110, + NumpadSubtract = 111, + NumpadDecimal = 112, + NumpadDivide = 113, /** * Cover all key codes when IME is processing input. */ KEY_IN_COMPOSITION = 114, - ABNT_C1 = 115,// Brazilian (ABNT) Keyboard - ABNT_C2 = 116,// Brazilian (ABNT) Keyboard + ABNT_C1 = 115, + ABNT_C2 = 116, AudioVolumeMute = 117, AudioVolumeUp = 118, AudioVolumeDown = 119, diff --git a/src/vs/editor/common/standaloneStrings.ts b/src/vs/editor/common/standaloneStrings.ts index 268e1e6657c0bd..eb30df908506f6 100644 --- a/src/vs/editor/common/standaloneStrings.ts +++ b/src/vs/editor/common/standaloneStrings.ts @@ -20,8 +20,8 @@ export namespace AccessibilityHelpNLS { export const screenReaderModeDisabled = nls.localize("screenReaderModeDisabled", "Screen Reader Optimized Mode disabled."); export const tabFocusModeOnMsg = nls.localize("tabFocusModeOnMsg", "Pressing Tab in the current editor will move focus to the next focusable element. Toggle this behavior {0}."); export const tabFocusModeOnMsgNoKb = nls.localize("tabFocusModeOnMsgNoKb", "Pressing Tab in the current editor will move focus to the next focusable element. The command {0} is currently not triggerable by a keybinding."); - export const stickScrollKb = nls.localize("stickScrollKb", "Run the command: Focus Sticky Scroll ({0}) to focus the currently nested scopes."); - export const stickScrollNoKb = nls.localize("stickScrollNoKb", "Run the command: Focus Sticky Scroll to focus the currently nested scopes. It is currently not triggerable by a keybinding."); + export const stickScrollKb = nls.localize("stickScrollKb", "Focus Sticky Scroll ({0}) to focus the currently nested scopes."); + export const stickScrollNoKb = nls.localize("stickScrollNoKb", "Focus Sticky Scroll to focus the currently nested scopes. It is currently not triggerable by a keybinding."); export const tabFocusModeOffMsg = nls.localize("tabFocusModeOffMsg", "Pressing Tab in the current editor will insert the tab character. Toggle this behavior {0}."); export const tabFocusModeOffMsgNoKb = nls.localize("tabFocusModeOffMsgNoKb", "Pressing Tab in the current editor will insert the tab character. The command {0} is currently not triggerable by a keybinding."); export const showAccessibilityHelpAction = nls.localize("showAccessibilityHelpAction", "Show Accessibility Help"); diff --git a/src/vs/editor/contrib/codeAction/browser/codeAction.ts b/src/vs/editor/contrib/codeAction/browser/codeAction.ts index 9a203b85257a90..b017b4ba82e7d4 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeAction.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeAction.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; import { coalesce, equals, isNonEmptyArray } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { illegalArgument, isCancellationError, onUnexpectedExternalError } from 'vs/base/common/errors'; @@ -19,6 +18,7 @@ import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { IModelService } from 'vs/editor/common/services/model'; import { TextModelCancellationTokenSource } from 'vs/editor/contrib/editorState/browser/editorState'; +import * as nls from 'vs/nls'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService } from 'vs/platform/notification/common/notification'; diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionContributions.ts b/src/vs/editor/contrib/codeAction/browser/codeActionContributions.ts index e24fecf8d2f279..87f08e08c27489 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionContributions.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionContributions.ts @@ -33,3 +33,15 @@ Registry.as(Extensions.Configuration).registerConfigurat }, } }); + +Registry.as(Extensions.Configuration).registerConfiguration({ + ...editorConfigurationBaseNode, + properties: { + 'editor.codeActionWidget.includeNearbyQuickfixes': { + type: 'boolean', + scope: ConfigurationScope.LANGUAGE_OVERRIDABLE, + description: nls.localize('includeNearbyQuickfixes', "Enable/disable showing nearest quickfix within a line when not currently on a diagnostic."), + default: false, + }, + } +}); diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts index a56fb161ee21aa..3de08ca39628f1 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts @@ -4,8 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { getDomNodePagePosition } from 'vs/base/browser/dom'; +import * as aria from 'vs/base/browser/ui/aria/aria'; import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; import { IAction } from 'vs/base/common/actions'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Color } from 'vs/base/common/color'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; @@ -13,6 +16,8 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon'; import { CodeActionTriggerType } from 'vs/editor/common/languages'; +import { IModelDeltaDecoration } from 'vs/editor/common/model'; +import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { ApplyCodeActionReason, applyCodeAction } from 'vs/editor/contrib/codeAction/browser/codeAction'; import { CodeActionKeybindingResolver } from 'vs/editor/contrib/codeAction/browser/codeActionKeybindingResolver'; @@ -28,9 +33,11 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IMarkerService } from 'vs/platform/markers/common/markers'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; +import { editorFindMatchHighlight, editorFindMatchHighlightBorder } from 'vs/platform/theme/common/colorRegistry'; +import { isHighContrast } from 'vs/platform/theme/common/theme'; +import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { CodeActionAutoApply, CodeActionFilter, CodeActionItem, CodeActionSet, CodeActionTrigger, CodeActionTriggerSource } from '../common/types'; import { CodeActionModel, CodeActionsState } from './codeActionModel'; -import { CancellationToken } from 'vs/base/common/cancellation'; interface IActionShowOptions { @@ -38,6 +45,9 @@ interface IActionShowOptions { readonly fromLightbulb?: boolean; } + +const DECORATION_CLASS_NAME = 'quickfix-edit-highlight'; + export class CodeActionController extends Disposable implements IEditorContribution { public static readonly ID = 'editor.contrib.codeActionController'; @@ -72,8 +82,7 @@ export class CodeActionController extends Disposable implements IEditorContribut super(); this._editor = editor; - - this._model = this._register(new CodeActionModel(this._editor, languageFeaturesService.codeActionProvider, markerService, contextKeyService, progressService)); + this._model = this._register(new CodeActionModel(this._editor, languageFeaturesService.codeActionProvider, markerService, contextKeyService, progressService, _configurationService)); this._register(this._model.onDidChangeState(newState => this.update(newState))); this._lightBulbWidget = new Lazy(() => { @@ -232,7 +241,15 @@ export class CodeActionController extends Disposable implements IEditorContribut return undefined; } + private static readonly DECORATION = ModelDecorationOptions.register({ + description: 'quickfix-highlight', + className: DECORATION_CLASS_NAME + }); + public async showCodeActionList(actions: CodeActionSet, at: IAnchor | IPosition, options: IActionShowOptions): Promise { + + const currentDecorations = this._editor.createDecorationsCollection(); + const editorDom = this._editor.getDomNode(); if (!editorDom) { return; @@ -249,16 +266,29 @@ export class CodeActionController extends Disposable implements IEditorContribut onSelect: async (action: CodeActionItem, preview?: boolean) => { this._applyCodeAction(action, /* retrigger */ true, !!preview); this._actionWidgetService.hide(); + currentDecorations.clear(); }, onHide: () => { this._editor?.focus(); + currentDecorations.clear(); }, - onFocus: async (action: CodeActionItem, token: CancellationToken) => { + onHover: async (action: CodeActionItem, token: CancellationToken) => { await action.resolve(token); if (token.isCancellationRequested) { return; } return { canPreview: !!action.action.edit?.edits.length }; + }, + onFocus: (action: CodeActionItem | undefined) => { + if (action && action.highlightRange && action.action.diagnostics) { + const decorations: IModelDeltaDecoration[] = [{ range: action.action.diagnostics[0], options: CodeActionController.DECORATION }]; + currentDecorations.set(decorations); + const diagnostic = action.action.diagnostics[0]; + const selectionText = this._editor.getModel()?.getWordAtPosition({ lineNumber: diagnostic.startLineNumber, column: diagnostic.startColumn })?.word; + aria.status(localize('editingNewSelection', "Context: {0} at line {1} and column {2}.", selectionText, diagnostic.startLineNumber, diagnostic.startColumn)); + } else { + currentDecorations.clear(); + } } }; @@ -335,3 +365,18 @@ export class CodeActionController extends Disposable implements IEditorContribut return resultActions; } } + +registerThemingParticipant((theme, collector) => { + const addBackgroundColorRule = (selector: string, color: Color | undefined): void => { + if (color) { + collector.addRule(`.monaco-editor ${selector} { background-color: ${color}; }`); + } + }; + + addBackgroundColorRule('.quickfix-edit-highlight', theme.getColor(editorFindMatchHighlight)); + const findMatchHighlightBorder = theme.getColor(editorFindMatchHighlightBorder); + + if (findMatchHighlightBorder) { + collector.addRule(`.monaco-editor .quickfix-edit-highlight { border: 1px ${isHighContrast(theme.type) ? 'dotted' : 'solid'} ${findMatchHighlightBorder}; box-sizing: border-box; }`); + } +}); diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionModel.ts b/src/vs/editor/contrib/codeAction/browser/codeActionModel.ts index b21d5f2dc5d2ea..e09094e7ef3349 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionModel.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionModel.ts @@ -18,8 +18,9 @@ import { CodeActionProvider, CodeActionTriggerType } from 'vs/editor/common/lang import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IMarkerService } from 'vs/platform/markers/common/markers'; import { IEditorProgressService, Progress } from 'vs/platform/progress/common/progress'; -import { CodeActionSet, CodeActionTrigger, CodeActionTriggerSource } from '../common/types'; +import { CodeActionKind, CodeActionSet, CodeActionTrigger, CodeActionTriggerSource } from '../common/types'; import { getCodeActions } from './codeAction'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export const SUPPORTED_CODE_ACTIONS = new RawContextKey('supportedCodeAction', ''); @@ -135,6 +136,7 @@ const emptyCodeActionSet = Object.freeze({ hasAutoFix: false }); + export class CodeActionModel extends Disposable { private readonly _codeActionOracle = this._register(new MutableDisposable()); @@ -152,7 +154,8 @@ export class CodeActionModel extends Disposable { private readonly _registry: LanguageFeatureRegistry, private readonly _markerService: IMarkerService, contextKeyService: IContextKeyService, - private readonly _progressService?: IEditorProgressService + private readonly _progressService?: IEditorProgressService, + private readonly _configurationService?: IConfigurationService, ) { super(); this._supportedCodeActions = SUPPORTED_CODE_ACTIONS.bindTo(contextKeyService); @@ -174,6 +177,11 @@ export class CodeActionModel extends Disposable { this.setState(CodeActionsState.Empty, true); } + private _settingEnabledNearbyQuickfixes(): boolean { + const model = this._editor?.getModel(); + return this._configurationService ? this._configurationService.getValue('editor.codeActionWidget.includeNearbyQuickfixes', { resource: model?.uri }) : false; + } + private _update(): void { if (this._disposed) { return; @@ -197,12 +205,75 @@ export class CodeActionModel extends Disposable { return; } - const actions = createCancelablePromise(token => getCodeActions(this._registry, model, trigger.selection, trigger.trigger, Progress.None, token)); + const startPosition = trigger.selection.getStartPosition(); + + const actions = createCancelablePromise(async token => { + if (this._settingEnabledNearbyQuickfixes() && trigger.trigger.type === CodeActionTriggerType.Invoke && (trigger.trigger.triggerAction === CodeActionTriggerSource.QuickFix || trigger.trigger.filter?.include?.contains(CodeActionKind.QuickFix))) { + const codeActionSet = await getCodeActions(this._registry, model, trigger.selection, trigger.trigger, Progress.None, token); + + if (token.isCancellationRequested) { + return emptyCodeActionSet; + } + + // Search for quickfixes in the curret code action set. + const foundQuickfix = codeActionSet.validActions?.some(action => action.action.kind ? CodeActionKind.QuickFix.contains(new CodeActionKind(action.action.kind)) : false); + + if (!foundQuickfix) { + const allMarkers = this._markerService.read({ resource: model.uri }); + + // If markers exists, and there are no quickfixes found or length is zero, check for quickfixes on that line. + if (allMarkers.length > 0) { + const currPosition = trigger.selection.getPosition(); + let trackedPosition = currPosition; + let distance = Number.MAX_VALUE; + let toBeModified = false; + + for (const marker of allMarkers) { + const col = marker.endColumn; + const row = marker.endLineNumber; + const startRow = marker.startLineNumber; + + // Found quickfix on the same line and check relative distance to other markers + if ((row === currPosition.lineNumber || startRow === currPosition.lineNumber) && Math.abs(currPosition.column - col) < distance) { + distance = Math.abs(currPosition.column - col); + toBeModified = true; + trackedPosition = new Position(row, col); + } + } + + // Only retriggers if actually found quickfix on the same line as cursor + if (toBeModified) { + const newCodeActionTrigger: CodeActionTrigger = { + type: trigger.trigger.type, + triggerAction: trigger.trigger.triggerAction, + filter: { include: trigger.trigger.filter?.include ? trigger.trigger.filter?.include : CodeActionKind.QuickFix }, + autoApply: trigger.trigger.autoApply, + context: { notAvailableMessage: trigger.trigger.context?.notAvailableMessage || '', position: trackedPosition } + }; + + const selectionAsPosition = new Selection(trackedPosition.lineNumber, trackedPosition.column, trackedPosition.lineNumber, trackedPosition.column); + const actionsAtMarker = await getCodeActions(this._registry, model, selectionAsPosition, newCodeActionTrigger, Progress.None, token); + const currentActions = [...codeActionSet.validActions]; + if (actionsAtMarker.validActions.length !== 0) { + actionsAtMarker.validActions.forEach(action => { + action.highlightRange = action.action.isPreferred; + }); + // Already filtered through to only get quickfixes, so no need to filter again. + currentActions.push(...actionsAtMarker.validActions); + } + return { validActions: currentActions, allActions: codeActionSet.allActions, documentation: codeActionSet.documentation, hasAutoFix: codeActionSet.hasAutoFix, dispose: () => { codeActionSet.dispose(); } }; + } + } + } + } + // temporarilly hiding here as this is enabled/disabled behind a setting. + return getCodeActions(this._registry, model, trigger.selection, trigger.trigger, Progress.None, token); + }); + if (trigger.trigger.type === CodeActionTriggerType.Invoke) { this._progressService?.showWhile(actions, 250); } - - this.setState(new CodeActionsState.Triggered(trigger.trigger, trigger.selection.getStartPosition(), actions)); + this.setState(new CodeActionsState.Triggered(trigger.trigger, startPosition, actions)); }, undefined); this._codeActionOracle.value.trigger({ type: CodeActionTriggerType.Auto, triggerAction: CodeActionTriggerSource.Default }); } else { diff --git a/src/vs/editor/contrib/codeAction/common/types.ts b/src/vs/editor/contrib/codeAction/common/types.ts index 123b4c24c3d995..19a690e23dcebf 100644 --- a/src/vs/editor/contrib/codeAction/common/types.ts +++ b/src/vs/editor/contrib/codeAction/common/types.ts @@ -193,6 +193,7 @@ export class CodeActionItem { constructor( public readonly action: languages.CodeAction, public readonly provider: languages.CodeActionProvider | undefined, + public highlightRange?: boolean, ) { } async resolve(token: CancellationToken): Promise { diff --git a/src/vs/editor/contrib/codelens/browser/codelensController.ts b/src/vs/editor/contrib/codelens/browser/codelensController.ts index 2cba641d11638f..2cfcb40b456954 100644 --- a/src/vs/editor/contrib/codelens/browser/codelensController.ts +++ b/src/vs/editor/contrib/codelens/browser/codelensController.ts @@ -147,13 +147,13 @@ export class CodeLensContribution implements IEditorContribution { // no provider -> return but check with // cached lenses. they expire after 30 seconds if (cachedLenses) { - this._localToDispose.add(disposableTimeout(() => { + disposableTimeout(() => { const cachedLensesNow = this._codeLensCache.get(model); if (cachedLenses === cachedLensesNow) { this._codeLensCache.delete(model); this._onModelChange(); } - }, 30 * 1000)); + }, 30 * 1000, this._localToDispose); } return; } diff --git a/src/vs/editor/test/node/diffing/fixtures/issue-131091/1.tst b/src/vs/editor/test/node/diffing/fixtures/issue-131091/1.tst new file mode 100644 index 00000000000000..e137817eac9e5c --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/issue-131091/1.tst @@ -0,0 +1,9 @@ +{ + if (A) { + if (B) { + doit + } + } +} +C +X diff --git a/src/vs/editor/test/node/diffing/fixtures/issue-131091/2.tst b/src/vs/editor/test/node/diffing/fixtures/issue-131091/2.tst new file mode 100644 index 00000000000000..23c511b2e7ee8d --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/issue-131091/2.tst @@ -0,0 +1,7 @@ +{ + if (A && B) { + doit + } +} +C +Y diff --git a/src/vs/editor/test/node/diffing/fixtures/issue-131091/advanced.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/issue-131091/advanced.expected.diff.json new file mode 100644 index 00000000000000..51e04656b10a0f --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/issue-131091/advanced.expected.diff.json @@ -0,0 +1,40 @@ +{ + "original": { + "content": "{\n\tif (A) {\n\t\tif (B) {\n\t\t\tdoit\n\t\t}\n\t}\n}\nC\nX\n", + "fileName": "./1.tst" + }, + "modified": { + "content": "{\n\tif (A && B) {\n\t\tdoit\n\t}\n}\nC\nY\n", + "fileName": "./2.tst" + }, + "diffs": [ + { + "originalRange": "[2,6)", + "modifiedRange": "[2,4)", + "innerChanges": [ + { + "originalRange": "[2,7 -> 3,7]", + "modifiedRange": "[2,7 -> 2,11]" + }, + { + "originalRange": "[4,1 -> 4,2]", + "modifiedRange": "[3,1 -> 3,1]" + }, + { + "originalRange": "[5,1 -> 6,1]", + "modifiedRange": "[4,1 -> 4,1]" + } + ] + }, + { + "originalRange": "[9,10)", + "modifiedRange": "[7,8)", + "innerChanges": [ + { + "originalRange": "[9,1 -> 9,2 EOL]", + "modifiedRange": "[7,1 -> 7,2 EOL]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/issue-131091/legacy.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/issue-131091/legacy.expected.diff.json new file mode 100644 index 00000000000000..93ce63cca1dde7 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/issue-131091/legacy.expected.diff.json @@ -0,0 +1,27 @@ +{ + "original": { + "content": "{\n\tif (A) {\n\t\tif (B) {\n\t\t\tdoit\n\t\t}\n\t}\n}\nC\nX\n", + "fileName": "./1.tst" + }, + "modified": { + "content": "{\n\tif (A && B) {\n\t\tdoit\n\t}\n}\nC\nY\n", + "fileName": "./2.tst" + }, + "diffs": [ + { + "originalRange": "[2,8)", + "modifiedRange": "[2,6)", + "innerChanges": null + }, + { + "originalRange": "[9,10)", + "modifiedRange": "[7,8)", + "innerChanges": [ + { + "originalRange": "[9,1 -> 9,2 EOL]", + "modifiedRange": "[7,1 -> 7,2 EOL]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/loader.js b/src/vs/loader.js index a618210a168cf8..302fa3441d462b 100644 --- a/src/vs/loader.js +++ b/src/vs/loader.js @@ -705,7 +705,7 @@ var AMDLoader; }).then((text) => { text = `${text}\n//# sourceURL=${scriptSrc}`; const func = (trustedTypesPolicy - ? self.eval(trustedTypesPolicy.createScript('', text)) + ? self.eval(trustedTypesPolicy.createScript('', text)) // CodeQL [SM01632] the loader is responsible with loading code, fetch + eval is used on the web worker instead of importScripts if possible because importScripts is synchronous and we observed deadlocks on Safari : new Function(text) // CodeQL [SM01632] the loader is responsible with loading code, fetch + eval is used on the web worker instead of importScripts if possible because importScripts is synchronous and we observed deadlocks on Safari ); func.call(self); diff --git a/src/vs/platform/actionWidget/browser/actionList.ts b/src/vs/platform/actionWidget/browser/actionList.ts index 6a4c9402ca2ec5..c4bab67a19771c 100644 --- a/src/vs/platform/actionWidget/browser/actionList.ts +++ b/src/vs/platform/actionWidget/browser/actionList.ts @@ -25,7 +25,8 @@ export const previewSelectedActionCommand = 'previewSelectedCodeAction'; export interface IActionListDelegate { onHide(didCancel?: boolean): void; onSelect(action: T, preview?: boolean): void; - onFocus?(action: T, cancellationToken: CancellationToken): Promise<{ canPreview: boolean } | void>; + onHover?(action: T, cancellationToken: CancellationToken): Promise<{ canPreview: boolean } | void>; + onFocus?(action: T | undefined): void; } export interface IActionListItem { @@ -217,7 +218,7 @@ export class ActionList extends Disposable { this._register(this._list.onMouseClick(e => this.onListClick(e))); this._register(this._list.onMouseOver(e => this.onListHover(e))); - this._register(this._list.onDidChangeFocus(() => this._list.domFocus())); + this._register(this._list.onDidChangeFocus(() => this.onFocus())); this._register(this._list.onDidChangeSelection(e => this.onListSelection(e))); this._allMenuItems = items; @@ -307,11 +308,22 @@ export class ActionList extends Disposable { } } + private onFocus() { + this._list.domFocus(); + const focused = this._list.getFocus(); + if (focused.length === 0) { + return; + } + const focusIndex = focused[0]; + const element = this._list.element(focusIndex); + this._delegate.onFocus?.(element.item); + } + private async onListHover(e: IListMouseEvent>) { const element = e.element; if (element && element.item && this.focusCondition(element)) { - if (this._delegate.onFocus && !element.disabled && element.kind === ActionListItemKind.Action) { - const result = await this._delegate.onFocus(element.item, this.cts.token); + if (this._delegate.onHover && !element.disabled && element.kind === ActionListItemKind.Action) { + const result = await this._delegate.onHover(element.item, this.cts.token); element.canPreview = result ? result.canPreview : undefined; } if (e.index) { diff --git a/src/vs/platform/actions/browser/toolbar.ts b/src/vs/platform/actions/browser/toolbar.ts index d94ded3741e329..33c79ba76a3e0b 100644 --- a/src/vs/platform/actions/browser/toolbar.ts +++ b/src/vs/platform/actions/browser/toolbar.ts @@ -116,12 +116,12 @@ export class WorkbenchToolBar extends ToolBar { override setActions(_primary: readonly IAction[], _secondary: readonly IAction[] = [], menuIds?: readonly MenuId[]): void { this._sessionDisposables.clear(); - const primary = _primary.slice(); + const primary: Array = _primary.slice(); // for hiding and overflow we set some items to undefined const secondary = _secondary.slice(); const toggleActions: IAction[] = []; let toggleActionsCheckedCount: number = 0; - const extraSecondary: IAction[] = []; + const extraSecondary: Array = []; let someAreHidden = false; // unless disabled, move all hidden items to secondary group or ignore them @@ -145,7 +145,7 @@ export class WorkbenchToolBar extends ToolBar { // hidden items move into overflow or ignore if (action.hideActions.isHidden) { someAreHidden = true; - primary[i] = undefined!; + primary[i] = undefined; if (this._options?.hiddenItemStrategy !== HiddenItemStrategy.Ignore) { extraSecondary[i] = action; } @@ -156,7 +156,7 @@ export class WorkbenchToolBar extends ToolBar { // count for max if (this._options?.overflowBehavior !== undefined) { - const exemptedIds = intersection(new Set(this._options.overflowBehavior.exempted), Iterable.map(primary, a => a.id)); + const exemptedIds = intersection(new Set(this._options.overflowBehavior.exempted), Iterable.map(primary, a => a?.id)); const maxItems = this._options.overflowBehavior.maxItems - exemptedIds.size; let count = 0; @@ -170,12 +170,13 @@ export class WorkbenchToolBar extends ToolBar { continue; } if (count >= maxItems) { - primary[i] = undefined!; + primary[i] = undefined; extraSecondary[i] = action; } } } + // coalesce turns Array into IAction[] coalesceInPlace(primary); coalesceInPlace(extraSecondary); super.setActions(primary, Separator.join(extraSecondary, secondary)); diff --git a/src/vs/platform/extensionManagement/common/extensionUrlTrust.ts b/src/vs/platform/extensionManagement/common/extensionUrlTrust.ts deleted file mode 100644 index b26395646b72b3..00000000000000 --- a/src/vs/platform/extensionManagement/common/extensionUrlTrust.ts +++ /dev/null @@ -1,13 +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 { createDecorator } from 'vs/platform/instantiation/common/instantiation'; - -export const IExtensionUrlTrustService = createDecorator('extensionUrlTrustService'); - -export interface IExtensionUrlTrustService { - readonly _serviceBrand: undefined; - isExtensionUrlTrusted(extensionId: string, url: string): Promise; -} diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 9b8ba9a1b42470..f8e8204ebb09fe 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -317,7 +317,10 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi private async onDidChangeExtensionsFromAnotherSource({ added, removed }: DidChangeProfileExtensionsEvent): Promise { if (removed) { - for (const identifier of removed.extensions) { + const removedExtensions = added && this.uriIdentityService.extUri.isEqual(removed.profileLocation, added.profileLocation) + ? removed.extensions.filter(e => added.extensions.every(identifier => !areSameExtensions(identifier, e))) + : removed.extensions; + for (const identifier of removedExtensions) { this.logService.info('Extensions removed from another source', identifier.id, removed.profileLocation.toString()); this._onDidUninstallExtension.fire({ identifier, profileLocation: removed.profileLocation }); } diff --git a/src/vs/platform/extensionManagement/node/extensionUrlTrustService.ts b/src/vs/platform/extensionManagement/node/extensionUrlTrustService.ts deleted file mode 100644 index 8b638a63fe7dde..00000000000000 --- a/src/vs/platform/extensionManagement/node/extensionUrlTrustService.ts +++ /dev/null @@ -1,97 +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 * as crypto from 'crypto'; -import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/common/extensionUrlTrust'; -import { ILogService } from 'vs/platform/log/common/log'; -import { IProductService } from 'vs/platform/product/common/productService'; - -export class ExtensionUrlTrustService implements IExtensionUrlTrustService { - - declare readonly _serviceBrand: undefined; - - private trustedExtensionUrlPublicKeys = new Map(); - - constructor( - @IProductService private readonly productService: IProductService, - @ILogService private readonly logService: ILogService - ) { } - - async isExtensionUrlTrusted(extensionId: string, url: string): Promise { - if (!this.productService.trustedExtensionUrlPublicKeys) { - this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'There are no configured trusted keys'); - return false; - } - - const match = /^(.*)#([^#]+)$/.exec(url); - - if (!match) { - this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'Uri has no fragment', url); - return false; - } - - const [, originalUrl, fragment] = match; - - let keys = this.trustedExtensionUrlPublicKeys.get(extensionId); - - if (!keys) { - keys = this.productService.trustedExtensionUrlPublicKeys[extensionId]; - - if (!keys || keys.length === 0) { - this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'Extension doesn\'t have any trusted keys', extensionId); - return false; - } - - this.trustedExtensionUrlPublicKeys.set(extensionId, [...keys]); - } - - const fragmentBuffer = Buffer.from(decodeURIComponent(fragment), 'base64'); - - if (fragmentBuffer.length <= 6) { - this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'Uri fragment is not a signature', url); - return false; - } - - const timestampBuffer = fragmentBuffer.slice(0, 6); - const timestamp = fragmentBuffer.readUIntBE(0, 6); - const diff = Date.now() - timestamp; - - if (diff < 0 || diff > 3_600_000) { // 1 hour - this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'Signed uri has expired', url); - return false; - } - - const signatureBuffer = fragmentBuffer.slice(6); - const verify = crypto.createVerify('SHA256'); - verify.write(timestampBuffer); - verify.write(Buffer.from(originalUrl)); - verify.end(); - - for (let i = 0; i < keys.length; i++) { - let key = keys[i]; - - if (key === null) { // failed to be parsed before - continue; - } else if (typeof key === 'string') { // needs to be parsed - try { - key = crypto.createPublicKey({ key: Buffer.from(key, 'base64'), format: 'der', type: 'spki' }); - keys[i] = key; - } catch (err) { - this.logService.warn('ExtensionUrlTrustService#isExtensionUrlTrusted', `Failed to parse trusted extension uri public key #${i + 1} for ${extensionId}:`, err); - keys[i] = null; - continue; - } - } - - if (verify.verify(key, signatureBuffer)) { - this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'Signed uri is valid', url); - return true; - } - } - - this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'Signed uri could not be verified', url); - return false; - } -} diff --git a/src/vs/platform/files/common/fileService.ts b/src/vs/platform/files/common/fileService.ts index 8a8d6a57bda181..e17e76ee5fb5ce 100644 --- a/src/vs/platform/files/common/fileService.ts +++ b/src/vs/platform/files/common/fileService.ts @@ -14,7 +14,7 @@ import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; import { Schemas } from 'vs/base/common/network'; import { mark } from 'vs/base/common/performance'; -import { basename, dirname, extUri, extUriIgnorePathCase, IExtUri, isAbsolutePath, joinPath } from 'vs/base/common/resources'; +import { extUri, extUriIgnorePathCase, IExtUri, isAbsolutePath } from 'vs/base/common/resources'; import { consumeStream, isReadableBufferedStream, isReadableStream, listenStream, newWriteableStream, peekReadable, peekStream, transform } from 'vs/base/common/stream'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; @@ -408,17 +408,7 @@ export class FileService extends Disposable implements IFileService { // write file: buffered else { - const contents = bufferOrReadableOrStreamOrBufferedStream instanceof VSBuffer ? bufferToReadable(bufferOrReadableOrStreamOrBufferedStream) : bufferOrReadableOrStreamOrBufferedStream; - - // atomic write - if (writeFileOptions?.atomic !== false && writeFileOptions?.atomic?.postfix) { - await this.doWriteBufferedAtomic(provider, resource, joinPath(dirname(resource), `${basename(resource)}${writeFileOptions.atomic.postfix}`), writeFileOptions, contents); - } - - // non-atomic write - else { - await this.doWriteBuffered(provider, resource, writeFileOptions, contents); - } + await this.doWriteBuffered(provider, resource, writeFileOptions, bufferOrReadableOrStreamOrBufferedStream instanceof VSBuffer ? bufferToReadable(bufferOrReadableOrStreamOrBufferedStream) : bufferOrReadableOrStreamOrBufferedStream); } // events @@ -442,7 +432,11 @@ export class FileService extends Disposable implements IFileService { const atomic = !!options?.atomic; if (atomic) { if (!(provider.capabilities & FileSystemProviderCapabilities.FileAtomicWrite)) { - throw new Error(localize('writeFailedAtomicUnsupported', "Unable to atomically write file '{0}' because provider does not support it.", this.resourceForError(resource))); + throw new Error(localize('writeFailedAtomicUnsupported1', "Unable to atomically write file '{0}' because provider does not support it.", this.resourceForError(resource))); + } + + if (!(provider.capabilities & FileSystemProviderCapabilities.FileReadWrite)) { + throw new Error(localize('writeFailedAtomicUnsupported2', "Unable to atomically write file '{0}' because provider does not support unbuffered writes.", this.resourceForError(resource))); } if (unlock) { @@ -1183,28 +1177,6 @@ export class FileService extends Disposable implements IFileService { private readonly writeQueue = this._register(new ResourceQueue()); - private async doWriteBufferedAtomic(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, resource: URI, tempResource: URI, options: IWriteFileOptions | undefined, readableOrStreamOrBufferedStream: VSBufferReadable | VSBufferReadableStream | VSBufferReadableBufferedStream): Promise { - - // Write to temp resource first - await this.doWriteBuffered(provider, tempResource, options, readableOrStreamOrBufferedStream); - - try { - - // Rename over existing to ensure atomic replace - await provider.rename(tempResource, resource, { overwrite: true }); - } catch (error) { - - // Cleanup in case of rename error - try { - await provider.delete(tempResource, { recursive: false, useTrash: false, atomic: false }); - } catch (error) { - // ignore - we want the outer error to bubble up - } - - throw error; - } - } - private async doWriteBuffered(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, resource: URI, options: IWriteFileOptions | undefined, readableOrStreamOrBufferedStream: VSBufferReadable | VSBufferReadableStream | VSBufferReadableBufferedStream): Promise { return this.writeQueue.queueFor(resource, this.getExtUri(provider).providerExtUri).queue(async () => { diff --git a/src/vs/platform/files/node/diskFileSystemProvider.ts b/src/vs/platform/files/node/diskFileSystemProvider.ts index 4ea7d28e214c94..e22cfe1b45eb84 100644 --- a/src/vs/platform/files/node/diskFileSystemProvider.ts +++ b/src/vs/platform/files/node/diskFileSystemProvider.ts @@ -252,28 +252,41 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple private async doWriteFileAtomic(resource: URI, tempResource: URI, content: Uint8Array, opts: IFileWriteOptions): Promise { - // Write to temp resource first - await this.doWriteFile(tempResource, content, opts); + // Ensure to create locks for all resources involved + // since atomic write involves mutiple disk operations + // and resources. - try { + const locks = new DisposableStore(); - // Rename over existing to ensure atomic replace - await this.rename(tempResource, resource, { overwrite: true }); + try { + locks.add(await this.createResourceLock(resource)); + locks.add(await this.createResourceLock(tempResource)); - } catch (error) { + // Write to temp resource first + await this.doWriteFile(tempResource, content, opts, true /* disable write lock */); - // Cleanup in case of rename error try { - await this.delete(tempResource, { recursive: false, useTrash: false, atomic: false }); + + // Rename over existing to ensure atomic replace + await this.rename(tempResource, resource, { overwrite: true }); + } catch (error) { - // ignore - we want the outer error to bubble up - } - throw error; + // Cleanup in case of rename error + try { + await this.delete(tempResource, { recursive: false, useTrash: false, atomic: false }); + } catch (error) { + // ignore - we want the outer error to bubble up + } + + throw error; + } + } finally { + locks.dispose(); } } - private async doWriteFile(resource: URI, content: Uint8Array, opts: IFileWriteOptions): Promise { + private async doWriteFile(resource: URI, content: Uint8Array, opts: IFileWriteOptions, disableWriteLock?: boolean): Promise { let handle: number | undefined = undefined; try { const filePath = this.toFilePath(resource); @@ -293,7 +306,7 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple } // Open - handle = await this.open(resource, { create: true, unlock: opts.unlock }); + handle = await this.open(resource, { create: true, unlock: opts.unlock }, disableWriteLock); // Write content at once await this.write(handle, 0, content, 0, content.byteLength); @@ -317,14 +330,14 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple DiskFileSystemProvider.canFlush = enabled; } - async open(resource: URI, opts: IFileOpenOptions): Promise { + async open(resource: URI, opts: IFileOpenOptions, disableWriteLock?: boolean): Promise { const filePath = this.toFilePath(resource); // Writes: guard multiple writes to the same resource // behind a single lock to prevent races when writing // from multiple places at the same time to the same file let lock: IDisposable | undefined = undefined; - if (isFileOpenForWriteOptions(opts)) { + if (isFileOpenForWriteOptions(opts) && !disableWriteLock) { lock = await this.createResourceLock(resource); } @@ -756,13 +769,8 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple const locks = new DisposableStore(); try { - const [fromLock, toLock] = await Promise.all([ - this.createResourceLock(from), - this.createResourceLock(to) - ]); - - locks.add(fromLock); - locks.add(toLock); + locks.add(await this.createResourceLock(from)); + locks.add(await this.createResourceLock(to)); if (mkdir) { await Promises.mkdir(dirname(toFilePath), { recursive: true }); diff --git a/src/vs/platform/files/test/node/diskFileService.integrationTest.ts b/src/vs/platform/files/test/node/diskFileService.integrationTest.ts index fdf58c22472e16..2b5d4e04f32a8f 100644 --- a/src/vs/platform/files/test/node/diskFileService.integrationTest.ts +++ b/src/vs/platform/files/test/node/diskFileService.integrationTest.ts @@ -146,16 +146,13 @@ flakySuite('Disk File Service', function () { setup(async () => { const logService = new NullLogService(); - service = new FileService(logService); - disposables.add(service); + service = disposables.add(new FileService(logService)); - fileProvider = new TestDiskFileSystemProvider(logService); + fileProvider = disposables.add(new TestDiskFileSystemProvider(logService)); disposables.add(service.registerProvider(Schemas.file, fileProvider)); - disposables.add(fileProvider); - testProvider = new TestDiskFileSystemProvider(logService); + testProvider = disposables.add(new TestDiskFileSystemProvider(logService)); disposables.add(service.registerProvider(testSchema, testProvider)); - disposables.add(testProvider); testDir = getRandomTestPath(tmpdir(), 'vsctests', 'diskfileservice'); @@ -1818,7 +1815,14 @@ flakySuite('Disk File Service', function () { test('writeFile - buffered (atomic)', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose | FileSystemProviderCapabilities.FileAtomicWrite); - return testWriteFile(true); + let e; + try { + await testWriteFile(true); + } catch (error) { + e = error; + } + + assert.ok(e); }); test('writeFile - unbuffered (atomic)', async () => { @@ -1869,7 +1873,14 @@ flakySuite('Disk File Service', function () { test('writeFile (large file) - buffered (atomic)', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose | FileSystemProviderCapabilities.FileAtomicWrite); - return testWriteFileLarge(true); + let e; + try { + await testWriteFileLarge(true); + } catch (error) { + e = error; + } + + assert.ok(e); }); test('writeFile (large file) - unbuffered (atomic)', async () => { @@ -1890,6 +1901,29 @@ flakySuite('Disk File Service', function () { assert.strictEqual(readFileSync(resource.fsPath).toString(), newContent); } + test('writeFile (large file) - unbuffered (atomic) - concurrent writes with multiple services', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.FileAtomicWrite); + + const resource = URI.file(join(testDir, 'lorem.txt')); + + const content = readFileSync(resource.fsPath); + const newContent = content.toString() + content.toString(); + + const promises: Promise[] = []; + let suffix = 0; + for (let i = 0; i < 10; i++) { + const service = disposables.add(new FileService(new NullLogService())); + disposables.add(service.registerProvider(Schemas.file, fileProvider)); + + promises.push(service.writeFile(resource, VSBuffer.fromString(`${newContent}${++suffix}`), { atomic: { postfix: '.vsctmp' } })); + await timeout(0); + } + + await Promise.allSettled(promises); + + assert.strictEqual(readFileSync(resource.fsPath).toString(), `${newContent}${suffix}`); + }); + test('writeFile - buffered - readonly throws', async () => { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose | FileSystemProviderCapabilities.Readonly); diff --git a/src/vs/platform/request/common/request.ts b/src/vs/platform/request/common/request.ts index 6dfcb237289bdf..13edce38a5d873 100644 --- a/src/vs/platform/request/common/request.ts +++ b/src/vs/platform/request/common/request.ts @@ -177,6 +177,13 @@ function registerProxyConfigurations(scope: ConfigurationScope): void { default: true, description: localize('systemCertificates', "Controls whether CA certificates should be loaded from the OS. (On Windows and macOS, a reload of the window is required after turning this off.)"), restricted: true + }, + 'http.experimental.systemCertificatesV2': { + type: 'boolean', + tags: ['experimental'], + default: false, + description: localize('systemCertificatesV2', "Controls whether experimental loading of CA certificates from the OS should be enabled. This uses a more general approach than the default implemenation."), + restricted: true } } }; diff --git a/src/vs/platform/telemetry/common/telemetryUtils.ts b/src/vs/platform/telemetry/common/telemetryUtils.ts index c282e64cf0d746..85c0fbee4dd4c9 100644 --- a/src/vs/platform/telemetry/common/telemetryUtils.ts +++ b/src/vs/platform/telemetry/common/telemetryUtils.ts @@ -122,7 +122,7 @@ export function supportsTelemetry(productService: IProductService, environmentSe if (!environmentService.isBuilt && !environmentService.disableTelemetry) { return true; } - return !(environmentService.disableTelemetry || !productService.enableTelemetry || environmentService.extensionTestsLocationURI); + return !(environmentService.disableTelemetry || !productService.enableTelemetry); } /** @@ -133,6 +133,10 @@ export function supportsTelemetry(productService: IProductService, environmentSe * @returns True if telemetry is actually disabled and we're only logging for debug purposes */ export function isLoggingOnly(productService: IProductService, environmentService: IEnvironmentService): boolean { + // If we're testing an extension, log telemetry for debug purposes + if (environmentService.extensionTestsLocationURI) { + return true; + } // Logging only mode is only for OSS if (environmentService.isBuilt) { return false; diff --git a/src/vs/platform/userDataSync/common/extensionsMerge.ts b/src/vs/platform/userDataSync/common/extensionsMerge.ts index 9ec07dceb5098b..98204d9b4886ed 100644 --- a/src/vs/platform/userDataSync/common/extensionsMerge.ts +++ b/src/vs/platform/userDataSync/common/extensionsMerge.ts @@ -46,6 +46,8 @@ export function merge(localExtensions: ILocalSyncExtension[], remoteExtensions: localExtensions.forEach(({ identifier }) => addUUID(identifier)); remoteExtensions.forEach(({ identifier }) => addUUID(identifier)); lastSyncExtensions?.forEach(({ identifier }) => addUUID(identifier)); + skippedExtensions?.forEach(({ identifier }) => addUUID(identifier)); + lastSyncBuiltinExtensions?.forEach(identifier => addUUID(identifier)); const getKey = (extension: ISyncExtension): string => { const uuid = extension.identifier.uuid || uuids.get(extension.identifier.id.toLowerCase()); diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 59ac96cda5a7bf..928bff6241427e 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -219,6 +219,9 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ await this.syncRemoteProfiles(syncProfiles, manifest, merge, executionId, token); } } finally { + if (this.status !== SyncStatus.HasConflicts) { + this.setStatus(SyncStatus.Idle); + } this._onSyncErrors.fire(this._syncErrors); } } diff --git a/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts b/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts index 34d05f3a3a6ce8..1abd8f73766345 100644 --- a/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts @@ -1385,6 +1385,40 @@ suite('ExtensionsMerge', () => { assert.deepStrictEqual(actual.remote, null); }); + test('merge does not remove remote extension when skipped extension has uuid but remote does not has', () => { + const localExtensions = [ + aLocalSyncExtension({ identifier: { id: 'a', uuid: 'a' } }), + ]; + const remoteExtensions = [ + aRemoteSyncExtension({ identifier: { id: 'a', uuid: 'a' } }), + aRemoteSyncExtension({ identifier: { id: 'b' } }), + ]; + + const actual = merge(localExtensions, remoteExtensions, remoteExtensions, [aRemoteSyncExtension({ identifier: { id: 'b', uuid: 'b' } })], [], []); + + assert.deepStrictEqual(actual.local.added, []); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.local.updated, []); + assert.deepStrictEqual(actual.remote, null); + }); + + test('merge does not remove remote extension when last sync builtin extension has uuid but remote does not has', () => { + const localExtensions = [ + aLocalSyncExtension({ identifier: { id: 'a', uuid: 'a' } }), + ]; + const remoteExtensions = [ + aRemoteSyncExtension({ identifier: { id: 'a', uuid: 'a' } }), + aRemoteSyncExtension({ identifier: { id: 'b' } }), + ]; + + const actual = merge(localExtensions, remoteExtensions, remoteExtensions, [], [], [{ id: 'b', uuid: 'b' }]); + + assert.deepStrictEqual(actual.local.added, []); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.local.updated, []); + assert.deepStrictEqual(actual.remote, null); + }); + function anExpectedSyncExtension(extension: Partial): ISyncExtension { return { identifier: { id: 'a', uuid: 'a' }, diff --git a/src/vs/platform/window/common/window.ts b/src/vs/platform/window/common/window.ts index ae3fe215f36654..6cec55dc373ec5 100644 --- a/src/vs/platform/window/common/window.ts +++ b/src/vs/platform/window/common/window.ts @@ -137,6 +137,11 @@ export interface IWindowSettings { readonly enableMenuBarMnemonics: boolean; readonly closeWhenEmpty: boolean; readonly clickThroughInactive: boolean; + readonly density: IDensitySettings; +} + +export interface IDensitySettings { + readonly editorTabHeight: 'default' | 'compact'; } export function getTitleBarStyle(configurationService: IConfigurationService): 'native' | 'custom' { diff --git a/src/vs/server/node/server.cli.ts b/src/vs/server/node/server.cli.ts index e8ac628a02fd0e..10e1acbb917a01 100644 --- a/src/vs/server/node/server.cli.ts +++ b/src/vs/server/node/server.cli.ts @@ -86,7 +86,6 @@ const cliCommandCwd = process.env['VSCODE_CLIENT_COMMAND_CWD'] as string; const cliRemoteAuthority = process.env['VSCODE_CLI_AUTHORITY'] as string; const cliStdInFilePath = process.env['VSCODE_STDIN_FILE_PATH'] as string; - export async function main(desc: ProductDescription, args: string[]): Promise { if (!cliPipe && !cliCommand) { console.log('Command is only available in WSL or inside a Visual Studio Code terminal.'); @@ -271,7 +270,16 @@ export async function main(desc: ProductDescription, args: string[]): Promise process.stdout.write(data)); + cp.stderr.on('data', data => process.stderr.write(data)); + } else { + _cp.spawn(cliCommand, newCommandline, { cwd: cliCwd, env, stdio: 'inherit' }); + } } } else { if (parsedArgs.status) { @@ -331,6 +339,17 @@ export async function main(desc: ProductDescription, args: string[]): Promise setTimeout(res, 1000)); diff --git a/src/vs/server/node/serverServices.ts b/src/vs/server/node/serverServices.ts index 422370324f777b..9db61e83f34c4b 100644 --- a/src/vs/server/node/serverServices.ts +++ b/src/vs/server/node/serverServices.ts @@ -44,7 +44,7 @@ import { RequestService } from 'vs/platform/request/node/requestService'; import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties'; import { ITelemetryService, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; -import { getPiiPathsFromEnvironment, isInternalTelemetry, ITelemetryAppender, NullAppender, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; +import { getPiiPathsFromEnvironment, isInternalTelemetry, isLoggingOnly, ITelemetryAppender, NullAppender, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; import ErrorTelemetry from 'vs/platform/telemetry/node/errorTelemetry'; import { IPtyService, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { PtyHostService } from 'vs/platform/terminal/node/ptyHostService'; @@ -148,7 +148,7 @@ export async function setupServerServices(connectionToken: ServerConnectionToken let oneDsAppender: ITelemetryAppender = NullAppender; const isInternal = isInternalTelemetry(productService, configurationService); if (supportsTelemetry(productService, environmentService)) { - if (productService.aiConfig && productService.aiConfig.ariaKey) { + if (!isLoggingOnly(productService, environmentService) && productService.aiConfig?.ariaKey) { oneDsAppender = new OneDataSystemAppender(requestService, isInternal, eventPrefix, null, productService.aiConfig.ariaKey); disposables.add(toDisposable(() => oneDsAppender?.flush())); // Ensure the AI appender is disposed so that it flushes remaining data } diff --git a/src/vs/workbench/api/browser/mainThreadExtensionService.ts b/src/vs/workbench/api/browser/mainThreadExtensionService.ts index cbddc0c233b367..5afc945cc162a3 100644 --- a/src/vs/workbench/api/browser/mainThreadExtensionService.ts +++ b/src/vs/workbench/api/browser/mainThreadExtensionService.ts @@ -168,7 +168,7 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha if (dependencyExtension) { this._notificationService.notify({ severity: Severity.Error, - message: localize('uninstalledDep', "Cannot activate the '{0}' extension because it depends on the '{1}' extension, which is not installed. Would you like to install the extension and reload the window?", extName, dependencyExtension.displayName), + message: localize('uninstalledDep', "Cannot activate the '{0}' extension because it depends on the '{1}' extension from '{2}', which is not installed. Would you like to install the extension and reload the window?", extName, dependencyExtension.displayName, dependencyExtension.publisherDisplayName), actions: { primary: [new Action('install', localize('install missing dep', "Install and Reload"), '', true, () => this._extensionsWorkbenchService.install(dependencyExtension!) diff --git a/src/vs/workbench/api/browser/mainThreadInlineChat.ts b/src/vs/workbench/api/browser/mainThreadInlineChat.ts index 2bb8ed5e5fe83c..8cefa7a83a704d 100644 --- a/src/vs/workbench/api/browser/mainThreadInlineChat.ts +++ b/src/vs/workbench/api/browser/mainThreadInlineChat.ts @@ -69,7 +69,7 @@ export class MainThreadInlineChat implements MainThreadInlineChatShape { } async $handleProgressChunk(requestId: string, chunk: { message?: string | undefined; edits?: TextEdit[] | undefined }): Promise { - this._progresses.get(requestId)?.report(chunk); + await Promise.resolve(this._progresses.get(requestId)?.report(chunk)); } async $unregisterInteractiveEditorProvider(handle: number): Promise { diff --git a/src/vs/workbench/api/common/extHostInlineChat.ts b/src/vs/workbench/api/common/extHostInlineChat.ts index c1683d51fe8910..7e341e06a5f098 100644 --- a/src/vs/workbench/api/common/extHostInlineChat.ts +++ b/src/vs/workbench/api/common/extHostInlineChat.ts @@ -153,7 +153,7 @@ export class ExtHostInteractiveEditor implements ExtHostInlineChatShape { let done = false; const progress: vscode.Progress<{ message?: string; edits?: vscode.TextEdit[] }> = { - report: value => { + report: async value => { if (!request.live) { throw new Error('Progress reporting is only supported for live sessions'); } @@ -163,7 +163,7 @@ export class ExtHostInteractiveEditor implements ExtHostInlineChatShape { if (!value.message && !value.edits) { return; } - this._proxy.$handleProgressChunk(request.requestId, { + await this._proxy.$handleProgressChunk(request.requestId, { message: value.message, edits: value.edits?.map(typeConvert.TextEdit.from) }); diff --git a/src/vs/workbench/api/common/extHostTesting.ts b/src/vs/workbench/api/common/extHostTesting.ts index 8c9da3c5c8e8df..f8282d9b751a6f 100644 --- a/src/vs/workbench/api/common/extHostTesting.ts +++ b/src/vs/workbench/api/common/extHostTesting.ts @@ -920,7 +920,7 @@ class MirroredTestCollection extends AbstractIncrementalTestCollection { + 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 Action2 { +export class ToggleTabsVisibilityAction extends BaseToggleBooleanSettingAction { static readonly ID = 'workbench.action.toggleTabsVisibility'; @@ -495,24 +511,44 @@ export class ToggleTabsVisibilityAction extends Action2 { super({ id: ToggleTabsVisibilityAction.ID, title: { - value: localize('toggleTabs', "Toggle Tab Visibility"), - original: 'Toggle Tab Visibility' + value: localize('toggleTabs', "Toggle Editor Tab Visibility"), + original: 'Toggle Editor Tab Visibility' }, category: Categories.View, f1: true }); } - run(accessor: ServicesAccessor): Promise { - const configurationService = accessor.get(IConfigurationService); + protected override get settingId(): string { + return 'workbench.editor.showTabs'; + } +} +registerAction2(ToggleTabsVisibilityAction); - const visibility = configurationService.getValue('workbench.editor.showTabs'); - const newVisibilityValue = !visibility; +// --- Toggle Pinned Tabs On Separate Row + +export class ToggleSeparatePinnedTabsAction extends BaseToggleBooleanSettingAction { - return configurationService.updateValue('workbench.editor.showTabs', newVisibilityValue); + static readonly ID = 'workbench.action.toggleSeparatePinnedEditorTabs'; + + constructor() { + super({ + id: ToggleSeparatePinnedTabsAction.ID, + title: { + value: localize('toggleSeparatePinnedEditorTabs', "Separate Pinned Editor Tabs"), + original: 'Separate Pinned Editor Tabs' + }, + category: Categories.View, + precondition: ContextKeyExpr.has('config.workbench.editor.showTabs'), + f1: true + }); + } + + protected override get settingId(): string { + return 'workbench.editor.pinnedTabsOnSeparateRow'; } } -registerAction2(ToggleTabsVisibilityAction); +registerAction2(ToggleSeparatePinnedTabsAction); // --- Toggle Zen Mode diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index c3cc305979c884..886e023514d105 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -44,7 +44,6 @@ import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecyc import { runWhenIdle } from 'vs/base/common/async'; import { Lazy } from 'vs/base/common/lazy'; import { DEFAULT_ICON } from 'vs/workbench/services/userDataProfile/common/userDataProfileIcons'; -import { ThemeIcon } from 'vs/base/common/themables'; export class ViewContainerActivityAction extends ActivityAction { @@ -521,11 +520,7 @@ export class GlobalActivityActionViewItem extends MenuActivityActionViewItem { return; } - if (this.userDataProfileService.currentProfile.icon && this.userDataProfileService.currentProfile.icon !== DEFAULT_ICON.id) { - this.profileBadgeContent.classList.toggle('profile-icon-overlay', true); - this.profileBadgeContent.classList.toggle('profile-text-overlay', false); - append(this.profileBadgeContent, $(ThemeIcon.asCSSSelector(DEFAULT_ICON))); - } else { + if (!this.userDataProfileService.currentProfile.icon || this.userDataProfileService.currentProfile.icon === DEFAULT_ICON.id) { this.profileBadgeContent.classList.toggle('profile-text-overlay', true); this.profileBadgeContent.classList.toggle('profile-icon-overlay', false); this.profileBadgeContent.textContent = this.userDataProfileService.currentProfile.name.substring(0, 2).toUpperCase(); diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 93e78a0849d971..5fcef54660d2d7 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -44,6 +44,7 @@ import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; import { GestureEvent } from 'vs/base/browser/touch'; import { IPaneCompositePart, IPaneCompositeSelectorPart } from 'vs/workbench/browser/parts/paneCompositePart'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { DEFAULT_ICON } from 'vs/workbench/services/userDataProfile/common/userDataProfileIcons'; interface IPlaceholderViewContainer { readonly id: string; @@ -81,7 +82,6 @@ export class ActivitybarPart extends Part implements IPaneCompositeSelectorPart private static readonly ACTION_HEIGHT = 48; private static readonly ACCOUNTS_ACTION_INDEX = 0; - private static readonly GEAR_ICON = registerIcon('settings-view-bar-icon', Codicon.settingsGear, localize('settingsViewBarIcon', "Settings icon in the view bar.")); private static readonly ACCOUNTS_ICON = registerIcon('accounts-view-bar-icon', Codicon.account, localize('accountsViewBarIcon', "Accounts icon in the view bar.")); //#region IView @@ -549,7 +549,7 @@ export class ActivitybarPart extends Part implements IPaneCompositeSelectorPart return { id: 'workbench.actions.manage', name: localize('manage', "Manage"), - classNames: ThemeIcon.asClassNameArray(this.userDataProfileService.currentProfile.icon ? ThemeIcon.fromId(this.userDataProfileService.currentProfile.icon) : ActivitybarPart.GEAR_ICON), + classNames: ThemeIcon.asClassNameArray(this.userDataProfileService.currentProfile.icon ? ThemeIcon.fromId(this.userDataProfileService.currentProfile.icon) : DEFAULT_ICON), }; } diff --git a/src/vs/workbench/browser/parts/activitybar/media/activityaction.css b/src/vs/workbench/browser/parts/activitybar/media/activityaction.css index 449487b420facf..abba016e91d9dc 100644 --- a/src/vs/workbench/browser/parts/activitybar/media/activityaction.css +++ b/src/vs/workbench/browser/parts/activitybar/media/activityaction.css @@ -194,9 +194,10 @@ color: var(--vscode-activityBar-inactiveForeground); } +.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.active .profile-badge .profile-icon-overlay .codicon, .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus .profile-badge .profile-icon-overlay .codicon, .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:hover .profile-badge .profile-icon-overlay .codicon { - color: var(--vscode-activityBar-activeForeground); + color: var(--vscode-activityBar-foreground) !important; } .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .profile-badge .profile-text-overlay { diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 2e247ec5e88c4c..1056ed9c09de61 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -11,7 +11,7 @@ import { IEditorFactoryRegistry, EditorExtensions } from 'vs/workbench/common/ed import { TextCompareEditorActiveContext, ActiveEditorPinnedContext, EditorGroupEditorsCountContext, ActiveEditorStickyContext, ActiveEditorAvailableEditorIdsContext, MultipleEditorGroupsContext, ActiveEditorDirtyContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, - EditorTabsVisibleContext, ActiveEditorLastInGroupContext + EditorTabsVisibleContext, ActiveEditorLastInGroupContext, EditorPinnedAndUnpinnedTabsContext } from 'vs/workbench/common/contextkeys'; import { SideBySideEditorInput, SideBySideEditorInputSerializer } from 'vs/workbench/common/editor/sideBySideEditorInput'; import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; @@ -66,7 +66,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 { ToggleTabsVisibilityAction } from 'vs/workbench/browser/actions/layoutActions'; +import { ToggleSeparatePinnedTabsAction, ToggleTabsVisibilityAction } from 'vs/workbench/browser/actions/layoutActions'; //#region Editor Registrations @@ -351,7 +351,8 @@ 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', "Enable Tabs"), toggled: ContextKeyExpr.has('config.workbench.editor.showTabs') }, group: '3_config', order: 10 }); +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, { 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 MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: CLOSE_EDITOR_COMMAND_ID, title: localize('close', "Close") }, group: '1_close', order: 10 }); @@ -375,7 +376,6 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_DIFF_SID MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: SHOW_EDITORS_IN_GROUP, title: localize('showOpenedEditors', "Show Opened Editors") }, group: '3_open', order: 10 }); MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: localize('closeAll', "Close All") }, group: '5_close', order: 10 }); MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: CLOSE_SAVED_EDITORS_COMMAND_ID, title: localize('closeAllSaved', "Close Saved") }, group: '5_close', order: 20 }); -MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: ToggleTabsVisibilityAction.ID, title: localize('toggleTabs', "Enable Tabs"), toggled: ContextKeyExpr.has('config.workbench.editor.showTabs') }, group: '7_settings', order: 5, when: ContextKeyExpr.has('config.workbench.editor.showTabs').negate() /* only shown here when tabs are disabled */ }); MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_KEEP_EDITORS_COMMAND_ID, title: localize('togglePreviewMode', "Enable Preview Editors"), toggled: ContextKeyExpr.has('config.workbench.editor.enablePreview') }, group: '7_settings', order: 10 }); MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_LOCK_GROUP_COMMAND_ID, title: localize('lockGroup', "Lock Group"), toggled: ActiveEditorGroupLockedContext }, group: '8_lock', order: 10, when: MultipleEditorGroupsContext }); diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index e2b3f260decb76..d6511cea131ac4 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -15,6 +15,7 @@ import { ISerializableView } from 'vs/base/browser/ui/grid/grid'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { isObject } from 'vs/base/common/types'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; +import { IWindowsConfiguration } from 'vs/platform/window/common/window'; export interface IEditorPartCreationOptions { restorePreviousState: boolean; @@ -32,7 +33,7 @@ export const DEFAULT_EDITOR_PART_OPTIONS: IEditorPartOptions = { tabSizingFixedMaxWidth: 160, pinnedTabSizing: 'normal', pinnedTabsOnSeparateRow: false, - tabHeight: 'normal', + tabHeight: 'default', preventPinnedEditorClose: 'keyboardAndMouse', titleScrollbarSizing: 'default', focusRecentEditorAfterClose: true, @@ -50,7 +51,7 @@ export const DEFAULT_EDITOR_PART_OPTIONS: IEditorPartOptions = { }; export function impactsEditorPartOptions(event: IConfigurationChangeEvent): boolean { - return event.affectsConfiguration('workbench.editor') || event.affectsConfiguration('workbench.iconTheme'); + return event.affectsConfiguration('workbench.editor') || event.affectsConfiguration('workbench.iconTheme') || event.affectsConfiguration('window.density'); } export function getEditorPartOptions(configurationService: IConfigurationService, themeService: IThemeService): IEditorPartOptions { @@ -79,6 +80,11 @@ export function getEditorPartOptions(configurationService: IConfigurationService } } + const windowConfig = configurationService.getValue(); + if (windowConfig?.window?.density?.editorTabHeight) { + options.tabHeight = windowConfig.window.density.editorTabHeight; + } + return options; } @@ -204,7 +210,7 @@ export interface IInternalEditorOpenOptions extends IInternalEditorTitleControlO supportSideBySide?: SideBySideEditor.ANY | SideBySideEditor.BOTH; /** - * When set to `true`, pass DOM focus into the tab control. + * When set to `true`, pass DOM focus into the tab control. */ focusTabControl?: boolean; } diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index 0a7a26c37c171c..3541d64c29edec 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -1521,7 +1521,7 @@ export function getMultiSelectedEditorContexts(editorContext: IEditorCommandsCon if (focus) { const selection: Array = list.getSelectedElements().filter(onlyEditorGroupAndEditor); - if (selection.length > 0) { + if (selection.length > 1) { return selection.map(elementToContext); } diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index f126367be2b5b2..2d5528e845e884 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/editorgroupview'; import { EditorGroupModel, IEditorOpenOptions, IGroupModelChangeEvent, ISerializedEditorGroupModel, isGroupEditorCloseEvent, isGroupEditorOpenEvent, isSerializedEditorGroupModel } from 'vs/workbench/common/editor/editorGroupModel'; import { GroupIdentifier, CloseDirection, IEditorCloseEvent, IEditorPane, SaveReason, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane, EditorResourceAccessor, EditorInputCapabilities, IUntypedEditorInput, DEFAULT_EDITOR_ASSOCIATION, SideBySideEditor, EditorCloseContext, IEditorWillMoveEvent, IEditorWillOpenEvent, IMatchEditorOptions, GroupModelChangeKind, IActiveEditorChangeEvent, IFindEditorOptions } from 'vs/workbench/common/editor'; -import { ActiveEditorGroupLockedContext, ActiveEditorDirtyContext, EditorGroupEditorsCountContext, ActiveEditorStickyContext, ActiveEditorPinnedContext, ActiveEditorLastInGroupContext, ActiveEditorFirstInGroupContext } from 'vs/workbench/common/contextkeys'; +import { ActiveEditorGroupLockedContext, ActiveEditorDirtyContext, EditorGroupEditorsCountContext, ActiveEditorStickyContext, ActiveEditorPinnedContext, ActiveEditorLastInGroupContext, ActiveEditorFirstInGroupContext, EditorPinnedAndUnpinnedTabsContext } from 'vs/workbench/common/contextkeys'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; import { Emitter, Relay } from 'vs/base/common/event'; @@ -241,6 +241,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { const groupActiveEditorStickyContext = ActiveEditorStickyContext.bindTo(this.scopedContextKeyService); const groupEditorsCountContext = EditorGroupEditorsCountContext.bindTo(this.scopedContextKeyService); const groupLockedContext = ActiveEditorGroupLockedContext.bindTo(this.scopedContextKeyService); + const groupHasPinnedAndUnpinnedContext = EditorPinnedAndUnpinnedTabsContext.bindTo(this.scopedContextKeyService); const activeEditorListener = this._register(new MutableDisposable()); @@ -264,9 +265,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView { case GroupModelChangeKind.GROUP_LOCKED: groupLockedContext.set(this.isLocked); break; - case GroupModelChangeKind.EDITOR_ACTIVE: case GroupModelChangeKind.EDITOR_CLOSE: case GroupModelChangeKind.EDITOR_OPEN: + groupHasPinnedAndUnpinnedContext.set(this.hasPinnedAndUnpinnedEditors()); + case GroupModelChangeKind.EDITOR_ACTIVE: case GroupModelChangeKind.EDITOR_MOVE: groupActiveEditorFirstContext.set(this.model.isFirst(this.model.activeEditor)); groupActiveEditorLastContext.set(this.model.isLast(this.model.activeEditor)); @@ -280,6 +282,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { if (e.editor && e.editor === this.model.activeEditor) { groupActiveEditorStickyContext.set(this.model.isSticky(this.model.activeEditor)); } + groupHasPinnedAndUnpinnedContext.set(this.hasPinnedAndUnpinnedEditors()); break; } @@ -293,7 +296,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView { observeActiveEditor(); })); + // Update context keys on startup observeActiveEditor(); + groupHasPinnedAndUnpinnedContext.set(this.hasPinnedAndUnpinnedEditors()); + } + + private hasPinnedAndUnpinnedEditors(): boolean { + return this.model.stickyCount > 0 && this.model.stickyCount < this.model.count; } private registerContainerListeners(): void { diff --git a/src/vs/workbench/browser/parts/editor/editorGroupWatermark.ts b/src/vs/workbench/browser/parts/editor/editorGroupWatermark.ts index ff8a2778c7c457..67d993e2f04c36 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupWatermark.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupWatermark.ts @@ -141,12 +141,16 @@ export class EditorGroupWatermark extends Disposable { const update = () => { clearNode(box); selected.map(entry => { + const keys = this.keybindingService.lookupKeybinding(entry.id); + if (!keys) { + return; + } const dl = append(box, $('dl')); const dt = append(dl, $('dt')); dt.textContent = entry.text; const dd = append(dl, $('dd')); const keybinding = new KeybindingLabel(dd, OS, { renderUnboundKeybindings: true, ...defaultKeybindingLabelStyles }); - keybinding.set(this.keybindingService.lookupKeybinding(entry.id)); + keybinding.set(keys); }); }; diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 83287009c56be6..1c46734e8061ba 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -625,7 +625,9 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro this.doUpdateMostRecentActive(group, true); // Mark previous one as inactive - previousActiveGroup?.setActive(false); + if (previousActiveGroup && !previousActiveGroup.disposed) { + previousActiveGroup.setActive(false); + } // Mark group as new active group.setActive(true); diff --git a/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css b/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css index e390b16856af45..5fde102f436f04 100644 --- a/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css @@ -193,7 +193,15 @@ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-fit.sticky-shrink, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-shrink.sticky-shrink, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container.disable-sticky-tabs > .tab.sizing-fixed.sticky-shrink { - position: static; /** disable sticky positions for sticky compact/shrink/fixed tabs if the available space is too little */ + + /** + * If sticky tabs are explicitly disabled, because width is too little, make sure + * to reset all styles associated with sticky tabs. This includes position, z-index + * and left property (which is set on the element itself, hence important is needed). + */ + position: relative; + z-index: unset; + left: unset !important; } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.tab-actions-left::after, @@ -234,7 +242,8 @@ } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-top > .tab-border-top-container, -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-bottom > .tab-border-bottom-container, +.monaco-workbench .part.editor > .content .editor-group-container > .title:not(.two-tab-bars) .tabs-container > .tab.active.tab-border-bottom > .tab-border-bottom-container, +.monaco-workbench .part.editor > .content .editor-group-container > .title.two-tab-bars .tabs-and-actions-container:not(:first-child) .tabs-container > .tab.active.tab-border-bottom > .tab-border-bottom-container, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty-border-top > .tab-border-top-container { display: block; position: absolute; diff --git a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index 307d4d30f6398e..26906df911954e 100644 --- a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -390,7 +390,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { tabsContainer.classList.remove('scroll'); if (e.target === tabsContainer) { - this.onDrop(e, this.tabsModel.count, tabsContainer); + this.onDrop(e, this.groupView.count, tabsContainer); } } })); diff --git a/src/vs/workbench/browser/parts/editor/multiRowEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiRowEditorTabsControl.ts index 82ee1d6d9b24ab..6186237e26d9aa 100644 --- a/src/vs/workbench/browser/parts/editor/multiRowEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiRowEditorTabsControl.ts @@ -61,6 +61,8 @@ export class MultiRowEditorControl extends Disposable implements IEditorTabsCont if (didChange) { // HACK: To render all editor tabs on startup, otherwise only one row gets rendered otherTabController.openEditors([]); + + this.handleOpenedEditors(); } return didChange; } @@ -72,7 +74,17 @@ export class MultiRowEditorControl extends Disposable implements IEditorTabsCont const didChangeOpenEditorsSticky = this.stickyEditorTabsControl.openEditors(stickyEditors); const didChangeOpenEditorsUnSticky = this.unstickyEditorTabsControl.openEditors(unstickyEditors); - return didChangeOpenEditorsSticky || didChangeOpenEditorsUnSticky; + const didChange = didChangeOpenEditorsSticky || didChangeOpenEditorsUnSticky; + + if (didChange) { + this.handleOpenedEditors(); + } + + return didChange; + } + + private handleOpenedEditors(): void { + this.handlePinnedTabsSeparateRowToolbars(); } beforeCloseEditor(editor: EditorInput): void { @@ -161,7 +173,11 @@ export class MultiRowEditorControl extends Disposable implements IEditorTabsCont layout(dimensions: IEditorTitleControlDimensions): Dimension { const stickyDimensions = this.stickyEditorTabsControl.layout(dimensions); - const unstickyDimensions = this.unstickyEditorTabsControl.layout(dimensions); + const unstickyAvailableDimensions = { + container: dimensions.container, + available: new Dimension(dimensions.available.width, dimensions.available.height - stickyDimensions.height) + }; + const unstickyDimensions = this.unstickyEditorTabsControl.layout(unstickyAvailableDimensions); return new Dimension( dimensions.container.width, diff --git a/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts index d3d132cb2ed170..4e62e3a111d366 100644 --- a/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts @@ -35,7 +35,6 @@ export class CommandCenterControl { hoverDelegate: IHoverDelegate, @IInstantiationService instantiationService: IInstantiationService, @IQuickInputService quickInputService: IQuickInputService, - @IKeybindingService keybindingService: IKeybindingService ) { this.element.classList.add('command-center'); @@ -83,7 +82,7 @@ class CommandCenterCenterViewItem extends BaseActionViewItem { @IKeybindingService private _keybindingService: IKeybindingService, @IInstantiationService private _instaService: IInstantiationService, ) { - super(undefined, _submenu.actions[0], options); + super(undefined, _submenu.actions.find(action => action.id === 'workbench.action.quickOpenWithModes') ?? _submenu.actions[0], options); } override render(container: HTMLElement): void { diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index c78ebf86e1f528..86a2065ff18af0 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -183,6 +183,10 @@ padding: 0 12px; } +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center.multiple.active .action-label { + background-color: inherit; +} + .monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center:only-child { margin-left: 0; /* no margin if there is only the command center, without nav buttons */ } diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 88eecdc0c9a8dc..2017f37c3b9738 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -165,10 +165,10 @@ const registry = Registry.as(ConfigurationExtensions.Con 'minimum': 38, 'markdownDescription': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.editor.tabSizingFixedMaxWidth' }, "Controls the maximum width of tabs when `#workbench.editor.tabSizing#` size is set to `fixed`.") }, - 'workbench.editor.tabHeight': { + 'window.density.editorTabHeight': { 'type': 'string', - 'enum': ['normal', 'compact'], - 'default': 'normal', + '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.") }, 'workbench.editor.pinnedTabSizing': { diff --git a/src/vs/workbench/common/contextkeys.ts b/src/vs/workbench/common/contextkeys.ts index 6121cc4791b23c..585b45ec13c7b2 100644 --- a/src/vs/workbench/common/contextkeys.ts +++ b/src/vs/workbench/common/contextkeys.ts @@ -77,6 +77,7 @@ export const IsCenteredLayoutContext = new RawContextKey('isCenteredLay export const SplitEditorsVertically = new RawContextKey('splitEditorsVertically', false, localize('splitEditorsVertically', "Whether editors split vertically")); export const EditorAreaVisibleContext = new RawContextKey('editorAreaVisible', true, localize('editorAreaVisible', "Whether the editor area is visible")); export const EditorTabsVisibleContext = new RawContextKey('editorTabsVisible', true, localize('editorTabsVisible', "Whether editor tabs are visible")); +export const EditorPinnedAndUnpinnedTabsContext = new RawContextKey('editorPinnedAndUnpinnedTabsVisible', false, true); //#endregion diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index c64a144dcde1fc..41664fd79f8a3c 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -1100,7 +1100,7 @@ interface IEditorPartConfiguration { tabSizingFixedMaxWidth?: number; pinnedTabSizing?: 'normal' | 'compact' | 'shrink'; pinnedTabsOnSeparateRow?: boolean; - tabHeight?: 'normal' | 'compact'; + tabHeight?: 'default' | 'compact'; preventPinnedEditorClose?: PreventPinnedEditorClose; titleScrollbarSizing?: 'default' | 'large'; focusRecentEditorAfterClose?: boolean; diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 2b9fecde5ce7d2..744907f7020ad3 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -49,6 +49,7 @@ export const enum AccessibilityVerbositySettingId { export const enum AccessibleViewProviderId { Terminal = 'terminal', + TerminalHelp = 'terminal-help', DiffEditor = 'diffEditor', Chat = 'panelChat', InlineChat = 'inlineChat', diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts index 2347e5efed56b5..e7506ef9e9c7f0 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityContributions.ts @@ -16,7 +16,7 @@ import { AccessibilityHelpNLS } from 'vs/editor/common/standaloneStrings'; import { ToggleTabFocusModeAction } from 'vs/editor/contrib/toggleTabFocusMode/browser/toggleTabFocusMode'; import { localize } from 'vs/nls'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { AccessibilityVerbositySettingId, accessibleViewIsShown } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibilityVerbositySettingId, AccessibleViewProviderId, accessibleViewIsShown } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import * as strings from 'vs/base/common/strings'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -56,12 +56,13 @@ export class EditorAccessibilityHelpContribution extends Disposable { await commandService.executeCommand(NEW_UNTITLED_FILE_COMMAND_ID); codeEditor = codeEditorService.getActiveCodeEditor()!; } - accessibleViewService.show(instantiationService.createInstance(AccessibilityHelpProvider, codeEditor)); + accessibleViewService.show(instantiationService.createInstance(EditorAccessibilityHelpProvider, codeEditor)); }, EditorContextKeys.focus)); } } -class AccessibilityHelpProvider implements IAccessibleContentProvider { +class EditorAccessibilityHelpProvider implements IAccessibleContentProvider { + id = AccessibleViewProviderId.Editor; onClose() { this._editor.focus(); } @@ -148,6 +149,7 @@ export class HoverAccessibleViewContribution extends Disposable { } this._options.language = editor?.getModel()?.getLanguageId() ?? undefined; accessibleViewService.show({ + id: AccessibleViewProviderId.Hover, verbositySettingKey: AccessibilityVerbositySettingId.Hover, provideContent() { return editorHoverContent; }, onClose() { @@ -169,6 +171,7 @@ export class HoverAccessibleViewContribution extends Disposable { return false; } accessibleViewService.show({ + id: AccessibleViewProviderId.Hover, verbositySettingKey: AccessibilityVerbositySettingId.Hover, provideContent() { return extensionHoverContent; }, onClose() { @@ -226,6 +229,7 @@ export class NotificationAccessibleViewContribution extends Disposable { } notification.onDidClose(() => accessibleViewService.next()); accessibleViewService.show({ + id: AccessibleViewProviderId.Notification, provideContent: () => { return notification.source ? localize('notification.accessibleViewSrc', '{0} Source: {1}', message, notification.source) : localize('notification.accessibleView', '{0}', message); }, @@ -335,6 +339,7 @@ export class InlineCompletionsAccessibleViewContribution extends Disposable { } this._options.language = editor.getModel()?.getLanguageId() ?? undefined; accessibleViewService.show({ + id: AccessibleViewProviderId.InlineCompletions, verbositySettingKey: AccessibilityVerbositySettingId.InlineCompletions, provideContent() { return lineText + ghostText; }, onClose() { @@ -354,8 +359,6 @@ export class InlineCompletionsAccessibleViewContribution extends Disposable { return true; }; ContextKeyExpr.and(InlineCompletionContextKeys.inlineSuggestionVisible); return show(); - }, - ) - ); + })); } } diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index e93c7388477284..4f8d797430cb39 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -37,7 +37,7 @@ import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IPickerQuickAccessItem } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -import { AccessibilityVerbositySettingId, accessibilityHelpIsShown, accessibleViewCurrentProviderId, accessibleViewGoToSymbolSupported, accessibleViewIsShown, accessibleViewOnLastLine, accessibleViewSupportsNavigation, accessibleViewVerbosityEnabled } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibilityVerbositySettingId, AccessibleViewProviderId, accessibilityHelpIsShown, accessibleViewCurrentProviderId, accessibleViewGoToSymbolSupported, accessibleViewIsShown, accessibleViewOnLastLine, accessibleViewSupportsNavigation, accessibleViewVerbosityEnabled } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; @@ -46,6 +46,7 @@ const enum DIMENSIONS { } export interface IAccessibleContentProvider { + id: AccessibleViewProviderId; verbositySettingKey: AccessibilityVerbositySettingId; options: IAccessibleViewOptions; /** @@ -102,6 +103,7 @@ export interface IAccessibleViewOptions { language?: string; type: AccessibleViewType; positionBottom?: boolean; + customHelp?: () => string; } export class AccessibleView extends Disposable { @@ -208,7 +210,7 @@ export class AccessibleView extends Disposable { this._accessibleViewCurrentProviderId.reset(); } - show(provider?: IAccessibleContentProvider, symbol?: IAccessibleViewSymbol, showAccessibleViewHelp?: boolean): void { + show(provider?: IAccessibleContentProvider, symbol?: IAccessibleViewSymbol, showAccessibleViewHelp?: boolean, lineNumber?: number): void { provider = provider ?? this._currentProvider; if (!provider) { return; @@ -227,6 +229,15 @@ export class AccessibleView extends Disposable { } }; this._contextViewService.showContextView(delegate); + + if (lineNumber) { + // Context view takes time to show up, so we need to wait for it to show up before we can set the position + setTimeout(() => { + this._editorWidget.revealLine(lineNumber); + this._editorWidget.setSelection({ startLineNumber: lineNumber, startColumn: 1, endLineNumber: lineNumber, endColumn: 1 }); + }, 50); + } + if (symbol && this._currentProvider) { this.showSymbol(this._currentProvider, symbol); } @@ -324,9 +335,7 @@ export class AccessibleView extends Disposable { if (lineNumber === undefined) { return; } - this.show(provider); - this._editorWidget.revealLine(lineNumber); - this._editorWidget.setSelection({ startLineNumber: lineNumber, startColumn: 1, endLineNumber: lineNumber, endColumn: 1 }); + this.show(provider, undefined, undefined, lineNumber); this._updateContextKeys(provider, true); } @@ -360,7 +369,7 @@ export class AccessibleView extends Disposable { if (!showAccessibleViewHelp) { // don't overwrite the current provider this._currentProvider = provider; - this._accessibleViewCurrentProviderId.set(provider.verbositySettingKey.replaceAll('accessibility.verbosity.', '')); + this._accessibleViewCurrentProviderId.set(provider.id); } const value = this._configurationService.getValue(provider.verbositySettingKey); const readMoreLink = provider.options.readMoreUrl ? localize("openDoc", "\n\nOpen a browser window with more information related to accessibility (H).") : ''; @@ -384,7 +393,8 @@ export class AccessibleView extends Disposable { message += '\n'; } } - const exitThisDialogHint = localize('exit', '\n\nExit this dialog (Escape).'); + const verbose = this._configurationService.getValue(provider.verbositySettingKey); + const exitThisDialogHint = verbose && !provider.options.positionBottom ? localize('exit', '\n\nExit this dialog (Escape).') : ''; this._currentContent = message + provider.provideContent() + readMoreLink + disableHelpHint + exitThisDialogHint; this._updateContextKeys(provider, true); @@ -403,7 +413,7 @@ export class AccessibleView extends Disposable { const verbose = this._configurationService.getValue(provider.verbositySettingKey); const hasActions = this._accessibleViewSupportsNavigation.get() || this._accessibleViewVerbosityEnabled.get() || this._accessibleViewGoToSymbolSupported.get() || this._currentProvider?.actions; if (verbose && !showAccessibleViewHelp && hasActions) { - actionsHint = localize('ariaAccessibleViewActions', 'Explore actions such as disabling this hint (Shift+Tab).'); + actionsHint = provider.options.positionBottom ? localize('ariaAccessibleViewActionsBottom', 'Explore actions such as disabling this hint (Shift+Tab), use Escape to exit this dialog.') : localize('ariaAccessibleViewActions', 'Explore actions such as disabling this hint (Shift+Tab).'); } let ariaLabel = provider.options.type === AccessibleViewType.Help ? localize('accessibility-help', "Accessibility Help") : localize('accessible-view', "Accessible View"); this._title.textContent = ariaLabel; @@ -506,12 +516,14 @@ export class AccessibleView extends Disposable { } const currentProvider = Object.assign({}, this._currentProvider); + currentProvider.provideContent = this._currentProvider.provideContent.bind(currentProvider); currentProvider.options = Object.assign({}, currentProvider.options); const accessibleViewHelpProvider: IAccessibleContentProvider = { - provideContent: () => this._getAccessibleViewHelpDialogContent(this._goToSymbolsSupported()), + id: currentProvider.id, + provideContent: () => currentProvider.options.customHelp ? currentProvider?.options.customHelp() : this._getAccessibleViewHelpDialogContent(this._goToSymbolsSupported()), onClose: () => this.show(currentProvider), options: { type: AccessibleViewType.Help }, - verbositySettingKey: this._currentProvider.verbositySettingKey + verbositySettingKey: currentProvider.verbositySettingKey }; this._contextViewService.hideContextView(); // HACK: Delay to allow the context view to hide #186514 diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts index 1c16a3d3aa136c..5dda15aee3e8c6 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts @@ -246,7 +246,7 @@ export class BulkEditDataSource implements IAsyncDataSource { - const range = Range.lift(edit.textEdit.textEdit.range); + const range = textModel.validateRange(edit.textEdit.textEdit.range); //prefix-math const startTokens = textModel.tokenization.getLineTokens(range.startLineNumber); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts index c757c26fa1a96c..51b5f46984125a 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts @@ -11,7 +11,7 @@ import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; -import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { AccessibleDiffViewerNext } from 'vs/editor/browser/widget/diffEditor/diffEditor.contribution'; export function getAccessibilityHelpText(accessor: ServicesAccessor, type: 'panelChat' | 'inlineChat'): string { @@ -73,6 +73,7 @@ export async function runAccessibilityHelpAction(accessor: ServicesAccessor, edi inputEditor.getSupportedActions(); const helpText = getAccessibilityHelpText(accessor, type); accessibleViewService.show({ + id: type === 'panelChat' ? AccessibleViewProviderId.Chat : AccessibleViewProviderId.InlineChat, verbositySettingKey: type === 'panelChat' ? AccessibilityVerbositySettingId.Chat : AccessibilityVerbositySettingId.InlineChat, provideContent: () => helpText, onClose: () => { diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts index 8899be6939b411..a2e7aa52b62f74 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCopyActions.ts @@ -3,15 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; -import { CONTEXT_IN_CHAT_LIST } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatRequestViewModel, IChatResponseViewModel, isRequestVM, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; export function registerChatCopyActions() { @@ -60,23 +57,14 @@ export function registerChatCopyActions() { category: CHAT_CATEGORY, menu: { id: MenuId.ChatContext - }, - keybinding: { - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyMod.CtrlCmd | KeyCode.KeyC, - when: CONTEXT_IN_CHAT_LIST } }); } run(accessor: ServicesAccessor, ...args: any[]) { - let item = args[0]; + const item = args[0]; if (!isRequestVM(item) && !isResponseVM(item)) { - const widgetService = accessor.get(IChatWidgetService); - item = widgetService.lastFocusedWidget?.getFocus(); - if (!isRequestVM(item) && !isResponseVM(item)) { - return; - } + return; } const clipboardService = accessor.get(IClipboardService); diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index facf315c8c8c67..9dd6b212c35fc3 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -43,7 +43,7 @@ import { isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { ChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chatAccessibilityService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { ChatWelcomeMessageModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { ChatProviderService, IChatProviderService } from 'vs/workbench/contrib/chat/common/chatProvider'; @@ -183,6 +183,7 @@ class ChatAccessibleViewContribution extends Disposable { const responseIndex = responses?.findIndex(i => i === focusedItem); accessibleViewService.show({ + id: AccessibleViewProviderId.Chat, verbositySettingKey: AccessibilityVerbositySettingId.Chat, provideContent(): string { return responseContent!; }, onClose() { diff --git a/src/vs/workbench/contrib/chat/browser/chatQuick.ts b/src/vs/workbench/contrib/chat/browser/chatQuick.ts index 679b4ac99dcab0..4e121606c81db5 100644 --- a/src/vs/workbench/contrib/chat/browser/chatQuick.ts +++ b/src/vs/workbench/contrib/chat/browser/chatQuick.ts @@ -14,7 +14,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { IQuickInputService, IQuickWidget } from 'vs/platform/quickinput/common/quickInput'; -import { inputBackground, quickInputBackground, quickInputForeground } from 'vs/platform/theme/common/colorRegistry'; +import { editorBackground, inputBackground, quickInputBackground, quickInputForeground } from 'vs/platform/theme/common/colorRegistry'; import { IChatWidgetService, IQuickChatService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatViewOptions } from 'vs/workbench/contrib/chat/browser/chatViewPane'; import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget'; @@ -212,7 +212,7 @@ class QuickChat extends Disposable { listForeground: quickInputForeground, listBackground: quickInputBackground, inputEditorBackground: inputBackground, - resultEditorBackground: quickInputBackground + resultEditorBackground: editorBackground })); this.widget.render(parent); this.widget.setVisible(true); diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 7948c9dd8fc8ac..91c15635f2e735 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -25,7 +25,7 @@ import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart'; import { ChatAccessibilityProvider, ChatListDelegate, ChatListItemRenderer, IChatListItemRendererOptions, IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer'; import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane'; -import { CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_LIST, CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; import { IChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatReplyFollowup, IChatService, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService'; @@ -119,8 +119,6 @@ export class ChatWidget extends Disposable implements IChatWidget { private lastSlashCommands: ISlashCommand[] | undefined; private slashCommandsPromise: Promise | undefined; - private readonly chatListFocused: IContextKey; - constructor( readonly viewContext: IChatWidgetViewContext, private readonly styles: IChatWidgetStyles, @@ -134,7 +132,6 @@ export class ChatWidget extends Disposable implements IChatWidget { ) { super(); CONTEXT_IN_CHAT_SESSION.bindTo(contextKeyService).set(true); - this.chatListFocused = CONTEXT_IN_CHAT_LIST.bindTo(contextKeyService); this.requestInProgress = CONTEXT_CHAT_REQUEST_IN_PROGRESS.bindTo(contextKeyService); this._register((chatWidgetService as ChatWidgetService).register(this)); @@ -354,9 +351,7 @@ export class ChatWidget extends Disposable implements IChatWidget { })); this._register(this.tree.onDidFocus(() => { this._onDidFocus.fire(); - this.chatListFocused.set(this.tree.isDOMFocused()); })); - this._register(this.tree.onDidBlur(() => this.chatListFocused.set(false))); } private onContextMenu(e: ITreeContextMenuEvent): void { diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index 6b1b18d87e8d61..fe3c8359513aa2 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -26,7 +26,7 @@ import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart'; import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget'; import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { chatSlashCommandBackground, chatSlashCommandForeground } from 'vs/workbench/contrib/chat/common/chatColors'; -import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, ChatRequestTextPart, ChatRequestVariablePart } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, ChatRequestTextPart, ChatRequestVariablePart, chatVariableLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; import { IChatService, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; @@ -76,7 +76,8 @@ class InputEditorDecorations extends Disposable { const theme = this.themeService.getColorTheme(); this.codeEditorService.registerDecorationType(decorationDescription, slashCommandTextDecorationType, { color: theme.getColor(chatSlashCommandForeground)?.toString(), - backgroundColor: theme.getColor(chatSlashCommandBackground)?.toString() + backgroundColor: theme.getColor(chatSlashCommandBackground)?.toString(), + borderRadius: '3px' }); this.codeEditorService.registerDecorationType(decorationDescription, variableTextDecorationType, { color: theme.getColor(chatSlashCommandForeground)?.toString(), @@ -496,7 +497,7 @@ function computeCompletionRanges(model: ITextModel, position: Position, reg: Reg class VariableCompletions extends Disposable { - private static readonly VariableNameDef = /@\w*/g; // MUST be using `g`-flag + private static readonly VariableNameDef = new RegExp(`${chatVariableLeader}\\w*`, 'g'); // MUST be using `g`-flag constructor( @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, @@ -508,7 +509,7 @@ class VariableCompletions extends Disposable { this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { _debugDisplayName: 'chatVariables', - triggerCharacters: ['@'], + triggerCharacters: [chatVariableLeader], provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, _token: CancellationToken) => { const widget = this.chatWidgetService.getWidgetByInputUri(model.uri); @@ -527,19 +528,19 @@ class VariableCompletions extends Disposable { // TODO@roblourens work out a real API for this- maybe it can be part of the two-step flow that @file will probably use const historyVariablesEnabled = this.configurationService.getValue('chat.experimental.historyVariables'); const historyItems = historyVariablesEnabled ? history.map((h, i): CompletionItem => ({ - label: `@response:${i + 1}`, + label: `${chatVariableLeader}response:${i + 1}`, detail: h.response.asString(), - insertText: `@response:${String(i + 1).padStart(String(history.length).length, '0')} `, + insertText: `${chatVariableLeader}response:${String(i + 1).padStart(String(history.length).length, '0')} `, kind: CompletionItemKind.Text, range, })) : []; const variableItems = Array.from(this.chatVariablesService.getVariables()).map(v => { - const withAt = `@${v.name}`; + const withLeader = `${chatVariableLeader}${v.name}`; return { - label: withAt, + label: withLeader, range, - insertText: withAt + ' ', + insertText: withLeader + ' ', detail: v.description, kind: CompletionItemKind.Text, // The icons are disabled here anyway, }; diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 4283d10304e913..5717ef96c5073a 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -437,7 +437,7 @@ /* #endregion */ -.interactive-response-progress-tree .monaco-tl-row:hover { +.interactive-response-progress-tree .monaco-list-row:not(.selected) .monaco-tl-row:hover { background-color: var(--vscode-list-hoverBackground); } diff --git a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts index 95dd8d6e825846..631f0af7d006bf 100644 --- a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts +++ b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts @@ -17,6 +17,5 @@ export const CONTEXT_REQUEST = new RawContextKey('chatRequest', false, export const CONTEXT_CHAT_INPUT_HAS_TEXT = new RawContextKey('chatInputHasText', false, { type: 'boolean', description: localize('interactiveInputHasText', "True when the chat input has text.") }); export const CONTEXT_IN_CHAT_INPUT = new RawContextKey('inChatInput', false, { type: 'boolean', description: localize('inInteractiveInput', "True when focus is in the chat input, false otherwise.") }); export const CONTEXT_IN_CHAT_SESSION = new RawContextKey('inChat', false, { type: 'boolean', description: localize('inChat', "True when focus is in the chat widget, false otherwise.") }); -export const CONTEXT_IN_CHAT_LIST = new RawContextKey('chatListFocused', false, { type: 'boolean', description: localize('chatListFocused', "True when a row of the chat list is focused, but not when focus is on a different element inside the chat row.") }); export const CONTEXT_PROVIDER_EXISTS = new RawContextKey('hasChatProvider', false, { type: 'boolean', description: localize('hasChatProvider', "True when some chat provider has been registered.") }); diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 092427c3dc0f4d..2656e980d05158 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -9,9 +9,10 @@ import { IMarkdownString, MarkdownString, isMarkdownString } from 'vs/base/commo import { Disposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; +import { OffsetRange } from 'vs/editor/common/core/offsetRange'; import { ILogService } from 'vs/platform/log/common/log'; -import { IChatAgentData } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { ChatRequestTextPart, IParsedChatRequest, reviveParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChat, IChatFollowup, IChatProgress, IChatReplyFollowup, IChatResponse, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IUsedContext, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; export interface IChatRequestModel { @@ -347,7 +348,7 @@ export interface ISerializableChatAgentData { export interface ISerializableChatRequestData { providerRequestId: string | undefined; - message: string; + message: string | IParsedChatRequest; response: (IMarkdownString | IChatResponseProgressFileTreeData)[] | undefined; agent?: ISerializableChatAgentData; responseErrorDetails: IChatResponseErrorDetails | undefined; @@ -494,6 +495,7 @@ export class ChatModel extends Disposable implements IChatModel { public readonly providerId: string, private readonly initialData: ISerializableChatData | IExportableChatData | undefined, @ILogService private readonly logService: ILogService, + @IChatAgentService private readonly chatAgentService: IChatAgentService, ) { super(); @@ -519,15 +521,30 @@ export class ChatModel extends Disposable implements IChatModel { this._welcomeMessage = new ChatWelcomeMessageModel(this, content); } - return []; - // return requests.map((raw: ISerializableChatRequestData) => { - // const request = new ChatRequestModel(this, raw.message, 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); - // } - // return request; - // }); + try { + return requests.map((raw: ISerializableChatRequestData) => { + const parsedRequest = typeof raw.message === 'string' ? this.getParsedRequestFromString(raw.message) : + reviveParsedChatRequest(raw.message); + 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); + } + return request; + }); + } catch (error) { + this.logService.error('Failed to parse chat data', error); + return []; + } + } + + private getParsedRequestFromString(message: string): IParsedChatRequest { + // TODO These offsets won't be used, but chat replies need to go through the parser as well + const parts = [new ChatRequestTextPart(new OffsetRange(0, message.length), { startColumn: 1, startLineNumber: 1, endColumn: 1, endLineNumber: 1 }, message)]; + return { + text: message, + parts + }; } startReinitialize(): void { @@ -679,7 +696,7 @@ export class ChatModel extends Disposable implements IChatModel { requests: this._requests.map((r): ISerializableChatRequestData => { return { providerRequestId: r.providerRequestId, - message: typeof r.message === 'string' ? r.message : '', + message: 'text' in r.message ? r.message : r.message.message, response: r.response ? r.response.response.value : undefined, responseErrorDetails: r.response?.errorDetails, followups: r.response?.followups, diff --git a/src/vs/workbench/contrib/chat/common/chatParserTypes.ts b/src/vs/workbench/contrib/chat/common/chatParserTypes.ts index 14dbe0e24b6527..b156a4fde6326d 100644 --- a/src/vs/workbench/contrib/chat/common/chatParserTypes.ts +++ b/src/vs/workbench/contrib/chat/common/chatParserTypes.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { OffsetRange } from 'vs/editor/common/core/offsetRange'; +import { IOffsetRange, OffsetRange } from 'vs/editor/common/core/offsetRange'; import { IRange } from 'vs/editor/common/core/range'; import { IChatAgentData, IChatAgentCommand } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService'; @@ -16,7 +16,8 @@ export interface IParsedChatRequest { } export interface IParsedChatRequestPart { - readonly range: OffsetRange; + readonly kind: string; // for serialization + readonly range: IOffsetRange; readonly editorRange: IRange; readonly text: string; } @@ -24,18 +25,24 @@ export interface IParsedChatRequestPart { // TODO rename to tokens export class ChatRequestTextPart implements IParsedChatRequestPart { + static readonly Kind = 'text'; + readonly kind = ChatRequestTextPart.Kind; constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly text: string) { } } +export const chatVariableLeader = '#'; // warning, this also shows up in a regex in the parser + /** * An invocation of a static variable that can be resolved by the variable service */ export class ChatRequestVariablePart implements IParsedChatRequestPart { + static readonly Kind = 'var'; + readonly kind = ChatRequestVariablePart.Kind; constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly variableName: string, readonly variableArg: string) { } get text(): string { const argPart = this.variableArg ? `:${this.variableArg}` : ''; - return `@${this.variableName}${argPart}`; + return `${chatVariableLeader}${this.variableName}${argPart}`; } } @@ -43,6 +50,8 @@ export class ChatRequestVariablePart implements IParsedChatRequestPart { * An invocation of an agent that can be resolved by the agent service */ export class ChatRequestAgentPart implements IParsedChatRequestPart { + static readonly Kind = 'agent'; + readonly kind = ChatRequestAgentPart.Kind; constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly agent: IChatAgentData) { } get text(): string { @@ -54,6 +63,8 @@ export class ChatRequestAgentPart implements IParsedChatRequestPart { * An invocation of an agent's subcommand */ export class ChatRequestAgentSubcommandPart implements IParsedChatRequestPart { + static readonly Kind = 'subcommand'; + readonly kind = ChatRequestAgentSubcommandPart.Kind; constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly command: IChatAgentCommand) { } get text(): string { @@ -65,9 +76,53 @@ export class ChatRequestAgentSubcommandPart implements IParsedChatRequestPart { * An invocation of a standalone slash command */ export class ChatRequestSlashCommandPart implements IParsedChatRequestPart { + static readonly Kind = 'slash'; + readonly kind = ChatRequestSlashCommandPart.Kind; constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly slashCommand: ISlashCommand) { } get text(): string { return `/${this.slashCommand.command}`; } } + +export function reviveParsedChatRequest(serialized: IParsedChatRequest): IParsedChatRequest { + return { + text: serialized.text, + parts: serialized.parts.map(part => { + if (part.kind === ChatRequestTextPart.Kind) { + return new ChatRequestTextPart( + new OffsetRange(part.range.start, part.range.endExclusive), + part.editorRange, + part.text + ); + } else if (part.kind === ChatRequestVariablePart.Kind) { + return new ChatRequestVariablePart( + new OffsetRange(part.range.start, part.range.endExclusive), + part.editorRange, + (part as ChatRequestVariablePart).variableName, + (part as ChatRequestVariablePart).variableArg + ); + } else if (part.kind === ChatRequestAgentPart.Kind) { + return new ChatRequestAgentPart( + new OffsetRange(part.range.start, part.range.endExclusive), + part.editorRange, + (part as ChatRequestAgentPart).agent + ); + } else if (part.kind === ChatRequestAgentSubcommandPart.Kind) { + return new ChatRequestAgentSubcommandPart( + new OffsetRange(part.range.start, part.range.endExclusive), + part.editorRange, + (part as ChatRequestAgentSubcommandPart).command + ); + } else if (part.kind === ChatRequestSlashCommandPart.Kind) { + return new ChatRequestSlashCommandPart( + new OffsetRange(part.range.start, part.range.endExclusive), + part.editorRange, + (part as ChatRequestSlashCommandPart).slashCommand + ); + } else { + throw new Error(`Unknown chat request part: ${part.kind}`); + } + }) + }; +} diff --git a/src/vs/workbench/contrib/chat/common/chatRequestParser.ts b/src/vs/workbench/contrib/chat/common/chatRequestParser.ts index 03bb2eb11f1110..5b7da071f4260d 100644 --- a/src/vs/workbench/contrib/chat/common/chatRequestParser.ts +++ b/src/vs/workbench/contrib/chat/common/chatRequestParser.ts @@ -8,11 +8,12 @@ import { OffsetRange } from 'vs/editor/common/core/offsetRange'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, ChatRequestTextPart, ChatRequestVariablePart, IParsedChatRequest, IParsedChatRequestPart } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, ChatRequestTextPart, ChatRequestVariablePart, IParsedChatRequest, IParsedChatRequestPart, chatVariableLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; -const variableOrAgentReg = /^@([\w_\-]+)(:\d+)?(?=(\s|$|\b))/i; // An @-variable with an optional numeric : arg (@response:2) +const agentReg = /^@([\w_\-]+)(?=(\s|$|\b))/i; // An @-agent +const variableReg = /^#([\w_\-]+)(:\d+)?(?=(\s|$|\b))/i; // A #-variable with an optional numeric : arg (@response:2) const slashReg = /\/([\w_-]+)(?=(\s|$|\b))/i; // A / command export class ChatRequestParser { @@ -31,8 +32,10 @@ export class ChatRequestParser { const previousChar = message.charAt(i - 1); const char = message.charAt(i); let newPart: IParsedChatRequestPart | undefined; - if (char === '@' && (previousChar === ' ' || i === 0)) { - newPart = this.tryToParseVariableOrAgent(message.slice(i), i, new Position(lineNumber, column), parts); + if (char === chatVariableLeader && (previousChar === ' ' || i === 0)) { + newPart = this.tryToParseVariable(message.slice(i), i, new Position(lineNumber, column), parts); + } else if (char === '@' && (previousChar === ' ' || i === 0)) { + newPart = this.tryToParseAgent(message.slice(i), i, new Position(lineNumber, column), parts); } else if (char === '/' && (previousChar === ' ' || i === 0)) { // TODO try to make this sync newPart = await this.tryToParseSlashCommand(sessionId, message.slice(i), i, new Position(lineNumber, column), parts); @@ -64,10 +67,12 @@ export class ChatRequestParser { const lastPart = parts.at(-1); const lastPartEnd = lastPart?.range.endExclusive ?? 0; - parts.push(new ChatRequestTextPart( - new OffsetRange(lastPartEnd, message.length), - new Range(lastPart?.editorRange.endLineNumber ?? 1, lastPart?.editorRange.endColumn ?? 1, lineNumber, column), - message.slice(lastPartEnd, message.length))); + if (lastPartEnd < message.length) { + parts.push(new ChatRequestTextPart( + new OffsetRange(lastPartEnd, message.length), + new Range(lastPart?.editorRange.endLineNumber ?? 1, lastPart?.editorRange.endColumn ?? 1, lineNumber, column), + message.slice(lastPartEnd, message.length))); + } return { parts, @@ -75,26 +80,41 @@ export class ChatRequestParser { }; } - private tryToParseVariableOrAgent(message: string, offset: number, position: IPosition, parts: ReadonlyArray): ChatRequestAgentPart | ChatRequestVariablePart | undefined { - const nextVariableMatch = message.match(variableOrAgentReg); + private tryToParseAgent(message: string, offset: number, position: IPosition, parts: ReadonlyArray): ChatRequestAgentPart | ChatRequestVariablePart | undefined { + const nextVariableMatch = message.match(agentReg); if (!nextVariableMatch) { return; } const [full, name] = nextVariableMatch; - const variableArg = nextVariableMatch[2] ?? ''; const varRange = new OffsetRange(offset, offset + full.length); const varEditorRange = new Range(position.lineNumber, position.column, position.lineNumber, position.column + full.length); let agent: IChatAgentData | undefined; - if ((agent = this.agentService.getAgent(name)) && !variableArg) { + if ((agent = this.agentService.getAgent(name))) { if (parts.some(p => p instanceof ChatRequestAgentPart)) { // Only one agent allowed return; } else { return new ChatRequestAgentPart(varRange, varEditorRange, agent); } - } else if (this.variableService.hasVariable(name)) { + } + + return; + } + + private tryToParseVariable(message: string, offset: number, position: IPosition, parts: ReadonlyArray): ChatRequestAgentPart | ChatRequestVariablePart | undefined { + const nextVariableMatch = message.match(variableReg); + if (!nextVariableMatch) { + return; + } + + const [full, name] = nextVariableMatch; + const variableArg = nextVariableMatch[2] ?? ''; + const varRange = new OffsetRange(offset, offset + full.length); + const varEditorRange = new Range(position.lineNumber, position.column, position.lineNumber, position.column + full.length); + + if (this.variableService.hasVariable(name)) { return new ChatRequestVariablePart(varRange, varEditorRange, name, variableArg); } diff --git a/src/vs/workbench/contrib/chat/common/chatVariables.ts b/src/vs/workbench/contrib/chat/common/chatVariables.ts index baadbbed31098a..855ff97ae07e9d 100644 --- a/src/vs/workbench/contrib/chat/common/chatVariables.ts +++ b/src/vs/workbench/contrib/chat/common/chatVariables.ts @@ -9,7 +9,7 @@ import { Iterable } from 'vs/base/common/iterator'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; -import { ChatRequestVariablePart, IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { ChatRequestVariablePart, IParsedChatRequest, chatVariableLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; export interface IChatVariableData { name: string; @@ -74,7 +74,7 @@ export class ChatVariablesService implements IChatVariablesService { jobs.push(data.resolver(prompt.text, varPart.variableArg, model, token).then(value => { if (value) { resolvedVariables[varPart.variableName] = value; - parsedPrompt[i] = `[@${varPart.variableName}](values:${varPart.variableName})`; + parsedPrompt[i] = `[${chatVariableLeader}${varPart.variableName}](values:${varPart.variableName})`; } else { parsedPrompt[i] = varPart.text; } diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_not_first.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_not_first.0.snap index 3a65b0a6ea47f0..a6a8d0d1516698 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_not_first.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_not_first.0.snap @@ -11,7 +11,8 @@ endLineNumber: 1, endColumn: 11 }, - text: "Hello Mr. " + text: "Hello Mr. ", + kind: "text" }, { range: { @@ -30,7 +31,8 @@ description: "", subCommands: [ { name: "subCommand" } ] } - } + }, + kind: "agent" }, { range: { @@ -43,7 +45,8 @@ endLineNumber: 1, endColumn: 18 }, - text: " " + text: " ", + kind: "text" }, { range: { @@ -56,7 +59,8 @@ endLineNumber: 1, endColumn: 29 }, - command: { name: "subCommand" } + command: { name: "subCommand" }, + kind: "subcommand" }, { range: { @@ -69,7 +73,8 @@ endLineNumber: 1, endColumn: 36 }, - text: " thanks" + text: " thanks", + kind: "text" } ], text: "Hello Mr. @agent /subCommand thanks" diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_question_mark.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_question_mark.0.snap index ce4e5ebadbaf9d..9c3a23726272ca 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_question_mark.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agent_with_question_mark.0.snap @@ -11,7 +11,8 @@ endLineNumber: 1, endColumn: 15 }, - text: "Are you there " + text: "Are you there ", + kind: "text" }, { range: { @@ -30,7 +31,8 @@ description: "", subCommands: [ { name: "subCommand" } ] } - } + }, + kind: "agent" }, { range: { @@ -43,7 +45,8 @@ endLineNumber: 1, endColumn: 22 }, - text: "?" + text: "?", + kind: "text" } ], text: "Are you there @agent?" diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents.0.snap index 9b5a1010a4cd34..f89e75eabf3b9d 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents.0.snap @@ -17,7 +17,8 @@ description: "", subCommands: [ { name: "subCommand" } ] } - } + }, + kind: "agent" }, { range: { @@ -30,7 +31,8 @@ endLineNumber: 1, endColumn: 18 }, - text: " Please do " + text: " Please do ", + kind: "text" }, { range: { @@ -43,7 +45,8 @@ endLineNumber: 1, endColumn: 29 }, - command: { name: "subCommand" } + command: { name: "subCommand" }, + kind: "subcommand" }, { range: { @@ -56,7 +59,8 @@ endLineNumber: 1, endColumn: 36 }, - text: " thanks" + text: " thanks", + kind: "text" } ], text: "@agent Please do /subCommand thanks" diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline.0.snap index d74138f7fb494e..27a7c90ce8c813 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_agents_and_variables_and_multiline.0.snap @@ -17,7 +17,8 @@ description: "", subCommands: [ { name: "subCommand" } ] } - } + }, + kind: "agent" }, { range: { @@ -30,7 +31,8 @@ endLineNumber: 2, endColumn: 4 }, - text: " Please \ndo " + text: " Please \ndo ", + kind: "text" }, { range: { @@ -43,21 +45,67 @@ endLineNumber: 2, endColumn: 15 }, - command: { name: "subCommand" } + command: { name: "subCommand" }, + kind: "subcommand" }, { range: { start: 29, - endExclusive: 63 + endExclusive: 35 }, editorRange: { startLineNumber: 2, startColumn: 15, + endLineNumber: 2, + endColumn: 21 + }, + text: " with ", + kind: "text" + }, + { + range: { + start: 35, + endExclusive: 45 + }, + editorRange: { + startLineNumber: 2, + startColumn: 21, + endLineNumber: 2, + endColumn: 31 + }, + variableName: "selection", + variableArg: "", + kind: "var" + }, + { + range: { + start: 45, + endExclusive: 50 + }, + editorRange: { + startLineNumber: 2, + startColumn: 31, + endLineNumber: 3, + endColumn: 5 + }, + text: "\nand ", + kind: "text" + }, + { + range: { + start: 50, + endExclusive: 63 + }, + editorRange: { + startLineNumber: 3, + startColumn: 5, endLineNumber: 3, endColumn: 18 }, - text: " with @selection\nand @debugConsole" + variableName: "debugConsole", + variableArg: "", + kind: "var" } ], - text: "@agent Please \ndo /subCommand with @selection\nand @debugConsole" + text: "@agent Please \ndo /subCommand with #selection\nand #debugConsole" } \ No newline at end of file diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_invalid_slash_command.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_invalid_slash_command.0.snap index 86e07d1cb2ae57..c56b6f495de120 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_invalid_slash_command.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_invalid_slash_command.0.snap @@ -11,7 +11,8 @@ endLineNumber: 1, endColumn: 14 }, - text: "/explain this" + text: "/explain this", + kind: "text" } ], text: "/explain this" diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_invalid_variables.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_invalid_variables.0.snap index 854189c56e90fb..e7411fc8e9dc58 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_invalid_variables.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_invalid_variables.0.snap @@ -11,8 +11,9 @@ endLineNumber: 1, endColumn: 27 }, - text: "What does @selection mean?" + text: "What does #selection mean?", + kind: "text" } ], - text: "What does @selection mean?" + text: "What does #selection mean?" } \ No newline at end of file diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_multiple_slash_commands.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_multiple_slash_commands.0.snap index 9babacf74e1b25..d54b848a4110b1 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_multiple_slash_commands.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_multiple_slash_commands.0.snap @@ -11,7 +11,8 @@ endLineNumber: 1, endColumn: 5 }, - slashCommand: { command: "fix" } + slashCommand: { command: "fix" }, + kind: "slash" }, { range: { @@ -24,7 +25,8 @@ endLineNumber: 1, endColumn: 10 }, - text: " /fix" + text: " /fix", + kind: "text" } ], text: "/fix /fix" diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_plain_text.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_plain_text.0.snap index e5e1fac6b73f6e..71043963dd13f3 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_plain_text.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_plain_text.0.snap @@ -11,7 +11,8 @@ endLineNumber: 1, endColumn: 5 }, - text: "test" + text: "test", + kind: "text" } ], text: "test" diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_plain_text_with_newlines.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_plain_text_with_newlines.0.snap index 7f0c88fb724063..75f02818a8e650 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_plain_text_with_newlines.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_plain_text_with_newlines.0.snap @@ -11,7 +11,8 @@ endLineNumber: 3, endColumn: 7 }, - text: "line 1\nline 2\r\nline 3" + text: "line 1\nline 2\r\nline 3", + kind: "text" } ], text: "line 1\nline 2\r\nline 3" diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_slash_command.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_slash_command.0.snap index 75e6df876122d8..6f06f6d0770951 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_slash_command.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_slash_command.0.snap @@ -11,7 +11,8 @@ endLineNumber: 1, endColumn: 5 }, - slashCommand: { command: "fix" } + slashCommand: { command: "fix" }, + kind: "slash" }, { range: { @@ -24,7 +25,8 @@ endLineNumber: 1, endColumn: 10 }, - text: " this" + text: " this", + kind: "text" } ], text: "/fix this" diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_variable_with_question_mark.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_variable_with_question_mark.0.snap index a6b846cf943a62..17b89a36298d47 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_variable_with_question_mark.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_variable_with_question_mark.0.snap @@ -11,7 +11,8 @@ endLineNumber: 1, endColumn: 9 }, - text: "What is " + text: "What is ", + kind: "text" }, { range: { @@ -25,7 +26,8 @@ endColumn: 19 }, variableName: "selection", - variableArg: "" + variableArg: "", + kind: "var" }, { range: { @@ -38,8 +40,9 @@ endLineNumber: 1, endColumn: 20 }, - text: "?" + text: "?", + kind: "text" } ], - text: "What is @selection?" + text: "What is #selection?" } \ No newline at end of file diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_variables.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_variables.0.snap index 721dbc46117f74..5af3a10d3b8636 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_variables.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/ChatRequestParser_variables.0.snap @@ -11,7 +11,8 @@ endLineNumber: 1, endColumn: 11 }, - text: "What does " + text: "What does ", + kind: "text" }, { range: { @@ -25,7 +26,8 @@ endColumn: 21 }, variableName: "selection", - variableArg: "" + variableArg: "", + kind: "var" }, { range: { @@ -38,8 +40,9 @@ endLineNumber: 1, endColumn: 27 }, - text: " mean?" + text: " mean?", + kind: "text" } ], - text: "What does @selection mean?" + text: "What does #selection mean?" } \ No newline at end of file diff --git a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts index 221d59a4de4e95..90b31b1ace978f 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts @@ -82,7 +82,7 @@ suite('ChatRequestParser', () => { instantiationService.stub(IChatVariablesService, variablesService as any); parser = instantiationService.createInstance(ChatRequestParser); - const text = 'What does @selection mean?'; + const text = 'What does #selection mean?'; const result = await parser.parseChatRequest('1', text); await assertSnapshot(result); }); @@ -93,7 +93,7 @@ suite('ChatRequestParser', () => { instantiationService.stub(IChatVariablesService, variablesService as any); parser = instantiationService.createInstance(ChatRequestParser); - const text = 'What is @selection?'; + const text = 'What is #selection?'; const result = await parser.parseChatRequest('1', text); await assertSnapshot(result); }); @@ -104,7 +104,7 @@ suite('ChatRequestParser', () => { instantiationService.stub(IChatVariablesService, variablesService as any); parser = instantiationService.createInstance(ChatRequestParser); - const text = 'What does @selection mean?'; + const text = 'What does #selection mean?'; const result = await parser.parseChatRequest('1', text); await assertSnapshot(result); }); @@ -149,7 +149,7 @@ suite('ChatRequestParser', () => { instantiationService.stub(IChatVariablesService, variablesService as any); parser = instantiationService.createInstance(ChatRequestParser); - const result = await parser.parseChatRequest('1', '@agent Please \ndo /subCommand with @selection\nand @debugConsole'); + const result = await parser.parseChatRequest('1', '@agent Please \ndo /subCommand with #selection\nand #debugConsole'); await assertSnapshot(result); }); }); diff --git a/src/vs/workbench/contrib/chat/test/common/chatVariables.test.ts b/src/vs/workbench/contrib/chat/test/common/chatVariables.test.ts index dae6a364994f30..c5ef1f465c0480 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatVariables.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatVariables.test.ts @@ -44,43 +44,43 @@ suite('ChatVariables', function () { }; { - const data = await resolveVariables('Hello @foo and@far'); + const data = await resolveVariables('Hello #foo and#far'); assert.strictEqual(Object.keys(data.variables).length, 1); assert.deepEqual(Object.keys(data.variables).sort(), ['foo']); - assert.strictEqual(data.prompt, 'Hello [@foo](values:foo) and@far'); + assert.strictEqual(data.prompt, 'Hello [#foo](values:foo) and#far'); } { - const data = await resolveVariables('@foo Hello'); + const data = await resolveVariables('#foo Hello'); assert.strictEqual(Object.keys(data.variables).length, 1); assert.deepEqual(Object.keys(data.variables).sort(), ['foo']); - assert.strictEqual(data.prompt, '[@foo](values:foo) Hello'); + assert.strictEqual(data.prompt, '[#foo](values:foo) Hello'); } { - const data = await resolveVariables('Hello @foo'); + const data = await resolveVariables('Hello #foo'); assert.strictEqual(Object.keys(data.variables).length, 1); assert.deepEqual(Object.keys(data.variables).sort(), ['foo']); } { - const data = await resolveVariables('Hello @foo?'); + const data = await resolveVariables('Hello #foo?'); assert.strictEqual(Object.keys(data.variables).length, 1); assert.deepEqual(Object.keys(data.variables).sort(), ['foo']); - assert.strictEqual(data.prompt, 'Hello [@foo](values:foo)?'); + assert.strictEqual(data.prompt, 'Hello [#foo](values:foo)?'); } { - const data = await resolveVariables('Hello @foo and@far @foo'); + const data = await resolveVariables('Hello #foo and#far #foo'); assert.strictEqual(Object.keys(data.variables).length, 1); assert.deepEqual(Object.keys(data.variables).sort(), ['foo']); } { - const data = await resolveVariables('Hello @foo and @far @foo'); + const data = await resolveVariables('Hello #foo and #far #foo'); assert.strictEqual(Object.keys(data.variables).length, 2); assert.deepEqual(Object.keys(data.variables).sort(), ['far', 'foo']); } { - const data = await resolveVariables('Hello @foo and @far @foo @unknown'); + const data = await resolveVariables('Hello #foo and #far #foo #unknown'); assert.strictEqual(Object.keys(data.variables).length, 2); assert.deepEqual(Object.keys(data.variables).sort(), ['far', 'foo']); - assert.strictEqual(data.prompt, 'Hello [@foo](values:foo) and [@far](values:far) [@foo](values:foo) @unknown'); + assert.strictEqual(data.prompt, 'Hello [#foo](values:foo) and [#far](values:far) [#foo](values:foo) #unknown'); } v1.dispose(); diff --git a/src/vs/workbench/contrib/codeActions/browser/codeActionsContribution.ts b/src/vs/workbench/contrib/codeActions/browser/codeActionsContribution.ts index bd0c4c4393c9b7..3d0bc4148f386a 100644 --- a/src/vs/workbench/contrib/codeActions/browser/codeActionsContribution.ts +++ b/src/vs/workbench/contrib/codeActions/browser/codeActionsContribution.ts @@ -19,10 +19,16 @@ import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensi const createCodeActionsAutoSave = (description: string): IJSONSchema => { return { - type: 'string', - enum: ['always', 'never', 'explicit'], - enumDescriptions: [nls.localize('alwaysSave', 'Always triggers Code Actions on save'), nls.localize('neverSave', 'Never triggers Code Actions on save'), nls.localize('explicitSave', 'Triggers Code Actions only when explicitly saved')], - default: 'explicit', + type: ['string', 'boolean'], + enum: ['always', 'explicit', 'never', true, false], + enumDescriptions: [ + nls.localize('alwaysSave', 'Triggers Code Actions on explicit saves and auto saves triggered by window or focus changes.'), + nls.localize('explicitSave', 'Triggers Code Actions only when explicitly saved'), + nls.localize('neverSave', 'Never triggers Code Actions on save'), + nls.localize('explicitSaveBoolean', 'Triggers Code Actions only when explicitly saved. This value will be deprecated in favor of "explicit".'), + nls.localize('neverSaveBoolean', 'Never triggers Code Actions on save. This value will be deprecated in favor of "never".') + ], + default: true, description: description }; }; @@ -37,7 +43,7 @@ const codeActionsOnSaveSchema: IConfigurationPropertySchema = { type: 'object', properties: codeActionsOnSaveDefaultProperties, additionalProperties: { - type: 'string' + type: ['string', 'boolean'] }, }, { @@ -48,7 +54,8 @@ const codeActionsOnSaveSchema: IConfigurationPropertySchema = { markdownDescription: nls.localize('editor.codeActionsOnSave', 'Run CodeActions for the editor on save. CodeActions must be specified and the editor must not be shutting down. Example: `"source.organizeImports": "explicit" `'), type: 'object', additionalProperties: { - type: 'string' + type: ['string', 'boolean'], + enum: ['always', 'explicit', 'never', true, false], }, default: {}, scope: ConfigurationScope.LANGUAGE_OVERRIDABLE, diff --git a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts index 5c5a0d187a8f28..e5331b3b34d8ee 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts @@ -21,7 +21,7 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/ import { Registry } from 'vs/platform/registry/common/platform'; import { FloatingEditorClickWidget } from 'vs/workbench/browser/codeeditor'; import { Extensions, IConfigurationMigrationRegistry } from 'vs/workbench/common/configuration'; -import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { getCommentCommandInfo } from 'vs/workbench/contrib/accessibility/browser/accessibilityContributions'; import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; @@ -106,7 +106,7 @@ function createScreenReaderHelp(): IDisposable { const keys = ['audioCues.diffLineDeleted', 'audioCues.diffLineInserted', 'audioCues.diffLineModified']; const content = [ localize('msg1', "You are in a diff editor."), - localize('msg2', "View the next {0} or previous {1} diff in diff review mode that is optimized for screen readers.", next, previous), + localize('msg2', "View the next ({0}) or previous ({1}) diff in diff review mode, which is optimized for screen readers.", next, previous), localize('msg3', "To control which audio cues should be played, the following settings can be configured: {0}.", keys.join(', ')), ]; const commentCommandInfo = getCommentCommandInfo(keybindingService, contextKeyService, codeEditor); @@ -114,6 +114,7 @@ function createScreenReaderHelp(): IDisposable { content.push(commentCommandInfo); } accessibleViewService.show({ + id: AccessibleViewProviderId.DiffEditor, verbositySettingKey: AccessibilityVerbositySettingId.DiffEditor, provideContent: () => content.join('\n\n'), onClose: () => { diff --git a/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts b/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts index 13ae0ab4aed5fb..e5bf6d70f5c378 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/emptyTextEditorHint/emptyTextEditorHint.ts @@ -66,10 +66,10 @@ export class EmptyTextEditorHintContribution implements IEditorContribution { @ICommandService private readonly commandService: ICommandService, @IConfigurationService protected readonly configurationService: IConfigurationService, @IKeybindingService private readonly keybindingService: IKeybindingService, - @IInlineChatSessionService inlineChatSessionService: IInlineChatSessionService, + @IInlineChatSessionService private readonly inlineChatSessionService: IInlineChatSessionService, @IInlineChatService protected readonly inlineChatService: IInlineChatService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IProductService private readonly productService: IProductService, + @IProductService protected readonly productService: IProductService, ) { this.toDispose = []; this.toDispose.push(this.editor.onDidChangeModel(() => this.update())); @@ -108,6 +108,10 @@ export class EmptyTextEditorHintContribution implements IEditorContribution { return false; } + if (this.inlineChatSessionService.getSession(this.editor, model.uri)) { + return false; + } + const conflictingDecoration = this.editor.getLineDecorations(1)?.find((d) => d.options.beforeContentClassName || d.options.afterContentClassName diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index fbdb839cd6b334..d015ba6761ba22 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -16,7 +16,7 @@ import { ILanguageService } from 'vs/editor/common/languages/language'; import { MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ICommentService } from 'vs/workbench/contrib/comments/browser/commentService'; -import { STARTING_EDITOR_HEIGHT, SimpleCommentEditor, calculateEditorHeight } from 'vs/workbench/contrib/comments/browser/simpleCommentEditor'; +import { LayoutableEditor, MIN_EDITOR_HEIGHT, SimpleCommentEditor, calculateEditorHeight } from 'vs/workbench/contrib/comments/browser/simpleCommentEditor'; import { Selection } from 'vs/editor/common/core/selection'; import { Emitter, Event } from 'vs/base/common/event'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -72,7 +72,7 @@ export class CommentNode extends Disposable { private _commentEditor: SimpleCommentEditor | null = null; private _commentEditorDisposables: IDisposable[] = []; private _commentEditorModel: ITextModel | null = null; - private _editorHeight = STARTING_EDITOR_HEIGHT; + private _editorHeight = MIN_EDITOR_HEIGHT; private _isPendingLabel!: HTMLElement; private _timestamp: HTMLElement | undefined; @@ -98,6 +98,7 @@ export class CommentNode extends Disposable { public isEditing: boolean = false; constructor( + private readonly parentEditor: LayoutableEditor, private commentThread: languages.CommentThread, public comment: languages.Comment, private pendingEdit: string | undefined, @@ -536,7 +537,7 @@ export class CommentNode extends Disposable { private calculateEditorHeight(): boolean { if (this._commentEditor) { - const newEditorHeight = calculateEditorHeight(this._commentEditor, this._editorHeight); + const newEditorHeight = calculateEditorHeight(this.parentEditor, this._commentEditor, this._editorHeight); if (newEditorHeight !== this._editorHeight) { this._editorHeight = newEditorHeight; return true; diff --git a/src/vs/workbench/contrib/comments/browser/commentReply.ts b/src/vs/workbench/contrib/comments/browser/commentReply.ts index e05c8f02faab12..18bf8527b6caf0 100644 --- a/src/vs/workbench/contrib/comments/browser/commentReply.ts +++ b/src/vs/workbench/contrib/comments/browser/commentReply.ts @@ -28,7 +28,7 @@ import { ICommentService } from 'vs/workbench/contrib/comments/browser/commentSe import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys'; import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/commentThreadWidget'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; -import { STARTING_EDITOR_HEIGHT, SimpleCommentEditor, calculateEditorHeight } from './simpleCommentEditor'; +import { LayoutableEditor, MIN_EDITOR_HEIGHT, SimpleCommentEditor, calculateEditorHeight } from './simpleCommentEditor'; const COMMENT_SCHEME = 'comment'; let INMEM_MODEL_ID = 0; @@ -45,11 +45,12 @@ export class CommentReply extends Disposable { private _commentFormActions!: CommentFormActions; private _commentEditorActions!: CommentFormActions; private _reviewThreadReplyButton!: HTMLElement; - private _editorHeight = STARTING_EDITOR_HEIGHT; + private _editorHeight = MIN_EDITOR_HEIGHT; constructor( readonly owner: string, container: HTMLElement, + private readonly _parentEditor: LayoutableEditor, private _commentThread: languages.CommentThread, private _scopedInstatiationService: IInstantiationService, private _contextKeyService: IContextKeyService, @@ -117,7 +118,7 @@ export class CommentReply extends Disposable { } private calculateEditorHeight(): boolean { - const newEditorHeight = calculateEditorHeight(this.commentEditor, this._editorHeight); + const newEditorHeight = calculateEditorHeight(this._parentEditor, this.commentEditor, this._editorHeight); if (newEditorHeight !== this._editorHeight) { this._editorHeight = newEditorHeight; return true; diff --git a/src/vs/workbench/contrib/comments/browser/commentService.ts b/src/vs/workbench/contrib/comments/browser/commentService.ts index 8e5f5e4de7dcd9..8de54b23195a0c 100644 --- a/src/vs/workbench/contrib/comments/browser/commentService.ts +++ b/src/vs/workbench/contrib/comments/browser/commentService.ts @@ -173,6 +173,9 @@ export class CommentService extends Disposable implements ICommentService { const storageListener = this._register(new DisposableStore()); storageListener.add(this.storageService.onDidChangeValue(StorageScope.WORKSPACE, CONTINUE_ON_COMMENTS, storageListener)((v) => { + if (!this.configurationService.getValue(COMMENTS_SECTION)?.experimentalContinueOn) { + return; + } if (!v.external) { return; } @@ -193,6 +196,9 @@ export class CommentService extends Disposable implements ICommentService { } })); this._register(storageService.onWillSaveState(() => { + if (!this.configurationService.getValue(COMMENTS_SECTION)?.experimentalContinueOn) { + return; + } for (const provider of this._continueOnCommentProviders) { const pendingComments = provider.provideContinueOnComments(); this._addContinueOnComments(pendingComments); @@ -417,7 +423,10 @@ export class CommentService extends Disposable implements ICommentService { removeContinueOnComment(pendingComment: { range: IRange; uri: URI; owner: string }): PendingCommentThread | undefined { const pendingComments = this._continueOnComments.get(pendingComment.owner); if (pendingComments) { - return pendingComments.splice(pendingComments.findIndex(comment => comment.uri.toString() === pendingComment.uri.toString() && Range.equalsRange(comment.range, pendingComment.range)), 1)[0]; + const commentIndex = pendingComments.findIndex(comment => comment.uri.toString() === pendingComment.uri.toString() && Range.equalsRange(comment.range, pendingComment.range)); + if (commentIndex > -1) { + return pendingComments.splice(commentIndex, 1)[0]; + } } return undefined; } diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts b/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts index a06b7a7716d6ca..739cf5c0de77ea 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts @@ -20,6 +20,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { IRange } from 'vs/editor/common/core/range'; +import { LayoutableEditor } from 'vs/workbench/contrib/comments/browser/simpleCommentEditor'; export class CommentThreadBody extends Disposable { private _commentsElement!: HTMLElement; @@ -42,6 +43,7 @@ export class CommentThreadBody extends D constructor( + private readonly _parentEditor: LayoutableEditor, readonly owner: string, readonly parentResourceUri: URI, readonly container: HTMLElement, @@ -76,7 +78,7 @@ export class CommentThreadBody extends D this._register(dom.addDisposableListener(this._commentsElement, dom.EventType.KEY_DOWN, (e) => { const event = new StandardKeyboardEvent(e as KeyboardEvent); - if (event.equals(KeyCode.UpArrow) || event.equals(KeyCode.DownArrow)) { + if ((event.equals(KeyCode.UpArrow) || event.equals(KeyCode.DownArrow)) && (!this._focusedComment || !this._commentElements[this._focusedComment].isEditing)) { const moveFocusWithinBounds = (change: number): number => { if (this._focusedComment === undefined && change >= 0) { return 0; } if (this._focusedComment === undefined && change < 0) { return this._commentElements.length - 1; } @@ -254,6 +256,7 @@ export class CommentThreadBody extends D private createNewCommentNode(comment: languages.Comment): CommentNode { const newCommentNode = this._scopedInstatiationService.createInstance(CommentNode, + this._parentEditor, this._commentThread, comment, this._pendingEdits ? this._pendingEdits[comment.uniqueIdInThread!] : undefined, diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index 4b12128a3d1139..6fb56aa03d2c80 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -35,6 +35,7 @@ import { localize } from 'vs/nls'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands'; +import { LayoutableEditor } from 'vs/workbench/contrib/comments/browser/simpleCommentEditor'; export const COMMENTEDITOR_DECORATION_KEY = 'commenteditordecoration'; @@ -60,6 +61,7 @@ export class CommentThreadWidget extends } constructor( readonly container: HTMLElement, + readonly _parentEditor: LayoutableEditor, private _owner: string, private _parentResourceUri: URI, private _contextKeyService: IContextKeyService, @@ -126,6 +128,7 @@ export class CommentThreadWidget extends })); this._body = this._scopedInstantiationService.createInstance( CommentThreadBody, + this._parentEditor, this._owner, this._parentResourceUri, bodyElement, @@ -286,6 +289,7 @@ export class CommentThreadWidget extends CommentReply, this._owner, this._body.container, + this._parentEditor, this._commentThread, this._scopedInstantiationService, this._contextKeyService, diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts index b9da89e59853d3..e543daf3720022 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts @@ -220,6 +220,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._commentThreadWidget = this._scopedInstantiationService.createInstance( CommentThreadWidget, container, + this.editor, this._owner, this.editor.getModel()!.uri, this._contextKeyService, diff --git a/src/vs/workbench/contrib/comments/browser/comments.contribution.ts b/src/vs/workbench/contrib/comments/browser/comments.contribution.ts index 4f303fd162ac29..2f06cf6f809dee 100644 --- a/src/vs/workbench/contrib/comments/browser/comments.contribution.ts +++ b/src/vs/workbench/contrib/comments/browser/comments.contribution.ts @@ -11,7 +11,7 @@ import { ICommentService, CommentService } from 'vs/workbench/contrib/comments/b import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { ctxCommentEditorFocused } from 'vs/workbench/contrib/comments/browser/simpleCommentEditor'; import * as strings from 'vs/base/common/strings'; -import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { AccessibleViewType, IAccessibleContentProvider, IAccessibleViewOptions, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -20,6 +20,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys'; import { CommentCommandId } from 'vs/workbench/contrib/comments/common/commentCommandIds'; +import { ToggleTabFocusModeAction } from 'vs/editor/contrib/toggleTabFocusMode/browser/toggleTabFocusMode'; Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ id: 'comments', @@ -69,16 +70,18 @@ registerSingleton(ICommentService, CommentService, InstantiationType.Delayed); export namespace CommentAccessibilityHelpNLS { export const intro = nls.localize('intro', "The editor contains commentable range(s). Some useful commands include:"); - export const introWidget = nls.localize('introWidget', "Some useful comment commands include:"); + export const introWidget = nls.localize('introWidget', "This widget contains a text area, for composition of new comments, and actions, that can be tabbed to once tab moves focus mode has been enabled ({0})."); + export const introWidgetNoKb = nls.localize('introWidgetNoKb', "This widget contains a text area, for composition of new comments, and actions, that can be tabbed to once tab moves focus mode has been enabled with the command Toggle Tab Key Moves Focus, which is currently not triggerable via keybinding."); + export const commentCommands = nls.localize('commentCommands', "Some useful comment commands include:"); export const escape = nls.localize('escape', "- Dismiss Comment (Escape)"); - export const nextRange = nls.localize('next', "- Navigate to the next commenting range ({0})"); + export const nextRange = nls.localize('next', "- Go to Next Commenting Range ({0})"); export const nextRangeNoKb = nls.localize('nextNoKb', "- Go to Next Commenting Range, which is currently not triggerable via keybinding."); - export const previousRange = nls.localize('previous', "- Navigate to the previous commenting range ({0})"); - export const previousRangeNoKb = nls.localize('previousNoKb', "Run the command: Go to Previous Commenting Range, which is currently not triggerable via keybinding."); - export const nextCommentThreadKb = nls.localize('nextCommentThreadKb', "- Navigate to the next comment thread ({0})"); - export const nextCommentThreadNoKb = nls.localize('nextCommentThreadNoKb', "- Run the command: Go to Next Comment Thread, which is currently not triggerable via keybinding."); - export const previousCommentThreadKb = nls.localize('previousCommentThreadKb', "- Navigate to the previous comment thread ({0})"); - export const previousCommentThreadNoKb = nls.localize('previousCommentThreadNoKb', "- Run the command: Go to Previous Comment Thread, which is currently not triggerable via keybinding."); + export const previousRange = nls.localize('previous', "- Go to Previous Commenting Range ({0})"); + export const previousRangeNoKb = nls.localize('previousNoKb', "- Go to Previous Commenting Range, which is currently not triggerable via keybinding."); + export const nextCommentThreadKb = nls.localize('nextCommentThreadKb', "- Go to Next Comment Thread ({0})"); + export const nextCommentThreadNoKb = nls.localize('nextCommentThreadNoKb', "- Go to Next Comment Thread, which is currently not triggerable via keybinding."); + export const previousCommentThreadKb = nls.localize('previousCommentThreadKb', "- Go to Previous Comment Thread ({0})"); + export const previousCommentThreadNoKb = nls.localize('previousCommentThreadNoKb', "- Go to Previous Comment Thread, which is currently not triggerable via keybinding."); export const addComment = nls.localize('addComment', "- Add Comment ({0})"); export const addCommentNoKb = nls.localize('addCommentNoKb', "- Add Comment on Current Selection, which is currently not triggerable via keybinding."); export const submitComment = nls.localize('submitComment', "- Submit Comment ({0})"); @@ -98,6 +101,7 @@ export class CommentsAccessibilityHelpContribution extends Disposable { } } export class CommentsAccessibilityHelpProvider implements IAccessibleContentProvider { + id = AccessibleViewProviderId.Comments; verbositySettingKey: AccessibilityVerbositySettingId = AccessibilityVerbositySettingId.Comments; options: IAccessibleViewOptions = { type: AccessibleViewType.Help }; private _element: HTMLElement | undefined; @@ -116,7 +120,8 @@ export class CommentsAccessibilityHelpProvider implements IAccessibleContentProv provideContent(): string { this._element = document.activeElement as HTMLElement; const content: string[] = []; - content.push(CommentAccessibilityHelpNLS.introWidget); + content.push(this._descriptionForCommand(ToggleTabFocusModeAction.ID, CommentAccessibilityHelpNLS.introWidget, CommentAccessibilityHelpNLS.introWidgetNoKb) + '\n'); + content.push(CommentAccessibilityHelpNLS.commentCommands); content.push(CommentAccessibilityHelpNLS.escape); content.push(this._descriptionForCommand(CommentCommandId.Add, CommentAccessibilityHelpNLS.addComment, CommentAccessibilityHelpNLS.addCommentNoKb)); content.push(this._descriptionForCommand(CommentCommandId.Submit, CommentAccessibilityHelpNLS.submitComment, CommentAccessibilityHelpNLS.submitCommentNoKb)); diff --git a/src/vs/workbench/contrib/comments/browser/commentsController.ts b/src/vs/workbench/contrib/comments/browser/commentsController.ts index c7fb47476c85ab..f05d745a66846d 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsController.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsController.ts @@ -344,7 +344,7 @@ class CommentingRangeDecorator { return range; } - return decorations[0].getActiveRange() ?? undefined; + return (decorations.length > 0 ? (decorations[0].getActiveRange() ?? undefined) : undefined); } public dispose(): void { diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts index 9889c3f7fab641..32d064d28006a1 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts @@ -43,7 +43,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ controller.nextCommentThread(); }, weight: KeybindingWeight.EditorContrib, - when: EditorContextKeys.focus, primary: KeyMod.Alt | KeyCode.F9, }); @@ -62,7 +61,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ controller.previousCommentThread(); }, weight: KeybindingWeight.EditorContrib, - when: EditorContextKeys.focus, primary: KeyMod.Shift | KeyMod.Alt | KeyCode.F9 }); diff --git a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts index 890437bb65a096..6db24044ed35dc 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts @@ -369,13 +369,7 @@ export class CommentsList extends WorkbenchObjectTree { - if (element instanceof CommentsModel || element instanceof ResourceWithCommentThreads) { - return false; - } - - return true; - }, + expandOnlyOnTwistieClick: true, collapseByDefault: false, overrideStyles: options.overrideStyles, filter: options.filter, diff --git a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts index 2e2f4a74d886bd..cda25cbdfac994 100644 --- a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts +++ b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts @@ -26,11 +26,16 @@ import { ILanguageConfigurationService } from 'vs/editor/common/languages/langua import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { clamp } from 'vs/base/common/numbers'; export const ctxCommentEditorFocused = new RawContextKey('commentEditorFocused', false); -export const STARTING_EDITOR_HEIGHT = 5 * 18; +export const MIN_EDITOR_HEIGHT = 5 * 18; export const MAX_EDITOR_HEIGHT = 25 * 18; +export interface LayoutableEditor { + getLayoutInfo(): { height: number }; +} + export class SimpleCommentEditor extends CodeEditorWidget { private _parentThread: ICommentThreadWidget; private _commentEditorFocused: IContextKey; @@ -94,7 +99,8 @@ export class SimpleCommentEditor extends CodeEditorWidget { horizontal: 'auto', useShadows: true, verticalHasArrows: false, - horizontalHasArrows: false + horizontalHasArrows: false, + alwaysConsumeMouseWheel: false }, overviewRulerLanes: 2, lineDecorationsWidth: 0, @@ -112,15 +118,15 @@ export class SimpleCommentEditor extends CodeEditorWidget { } } -export function calculateEditorHeight(editor: ICodeEditor, currentHeight: number): number { +export function calculateEditorHeight(parentEditor: LayoutableEditor, editor: ICodeEditor, currentHeight: number): number { const layoutInfo = editor.getLayoutInfo(); - const contentHeight = editor.getContentHeight(); const lineHeight = editor.getOption(EditorOption.lineHeight); + const contentHeight = (editor.getModel()?.getLineCount()! * lineHeight) ?? editor.getContentHeight(); // Can't just call getContentHeight() because it returns an incorrect, large, value when the editor is first created. if ((contentHeight > layoutInfo.height) || - (contentHeight < layoutInfo.height && currentHeight > STARTING_EDITOR_HEIGHT)) { + (contentHeight < layoutInfo.height && currentHeight > MIN_EDITOR_HEIGHT)) { const linesToAdd = Math.ceil((contentHeight - layoutInfo.height) / lineHeight); - const newEditorHeight = Math.min(MAX_EDITOR_HEIGHT, layoutInfo.height + (lineHeight * linesToAdd)); - return newEditorHeight; + const proposedHeight = layoutInfo.height + (lineHeight * linesToAdd); + return clamp(proposedHeight, MIN_EDITOR_HEIGHT, clamp(parentEditor.getLayoutInfo().height - 90, MIN_EDITOR_HEIGHT, MAX_EDITOR_HEIGHT)); } return currentHeight; } diff --git a/src/vs/workbench/contrib/comments/common/commentsConfiguration.ts b/src/vs/workbench/contrib/comments/common/commentsConfiguration.ts index 44004fe47d7985..6894e0666024c6 100644 --- a/src/vs/workbench/contrib/comments/common/commentsConfiguration.ts +++ b/src/vs/workbench/contrib/comments/common/commentsConfiguration.ts @@ -9,6 +9,7 @@ export interface ICommentsConfiguration { visible: boolean; maxHeight: boolean; collapseOnResolve: boolean; + experimentalContinueOn: boolean; } export const COMMENTS_SECTION = 'comments'; diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index 1859e2dc31b286..fec7dc3a7869e3 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -35,7 +35,7 @@ import { FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/d import { debugToolBarBackground, debugToolBarBorder } from 'vs/workbench/contrib/debug/browser/debugColors'; import { CONTINUE_ID, CONTINUE_LABEL, DISCONNECT_AND_SUSPEND_ID, DISCONNECT_AND_SUSPEND_LABEL, DISCONNECT_ID, DISCONNECT_LABEL, FOCUS_SESSION_ID, FOCUS_SESSION_LABEL, PAUSE_ID, PAUSE_LABEL, RESTART_LABEL, RESTART_SESSION_ID, REVERSE_CONTINUE_ID, STEP_BACK_ID, STEP_INTO_ID, STEP_INTO_LABEL, STEP_OUT_ID, STEP_OUT_LABEL, STEP_OVER_ID, STEP_OVER_LABEL, STOP_ID, STOP_LABEL } from 'vs/workbench/contrib/debug/browser/debugCommands'; import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons'; -import { CONTEXT_DEBUG_STATE, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_MULTI_SESSION_DEBUG, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED, IDebugConfiguration, IDebugService, State, VIEWLET_ID } from 'vs/workbench/contrib/debug/common/debug'; +import { CONTEXT_DEBUG_STATE, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_IN_DEBUG_MODE, CONTEXT_MULTI_SESSION_DEBUG, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED, IDebugConfiguration, IDebugService, State, VIEWLET_ID } from 'vs/workbench/contrib/debug/common/debug'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { Codicon } from 'vs/base/common/codicons'; @@ -336,12 +336,14 @@ MenuRegistry.onDidChangeMenu(e => { }); +const CONTEXT_TOOLBAR_COMMAND_CENTER = ContextKeyExpr.equals('config.debug.toolBarLocation', 'commandCenter'); + MenuRegistry.appendMenuItem(MenuId.CommandCenterCenter, { submenu: MenuId.DebugToolBar, title: 'Debug', icon: Codicon.debug, order: 1, - when: ContextKeyExpr.and(CONTEXT_DEBUG_STATE.notEqualsTo('inactive'), ContextKeyExpr.equals('config.debug.toolBarLocation', 'commandCenter')) + when: ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, CONTEXT_TOOLBAR_COMMAND_CENTER) }); registerDebugToolBarItem(CONTINUE_ID, CONTINUE_LABEL, 10, icons.debugContinue, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); @@ -354,7 +356,7 @@ registerDebugToolBarItem(STEP_OUT_ID, STEP_OUT_LABEL, 40, icons.debugStepOut, un registerDebugToolBarItem(RESTART_SESSION_ID, RESTART_LABEL, 60, icons.debugRestart); registerDebugToolBarItem(STEP_BACK_ID, localize('stepBackDebug', "Step Back"), 50, icons.debugStepBack, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); registerDebugToolBarItem(REVERSE_CONTINUE_ID, localize('reverseContinue', "Reverse"), 55, icons.debugReverseContinue, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); -registerDebugToolBarItem(FOCUS_SESSION_ID, FOCUS_SESSION_LABEL, 100, Codicon.listTree, CONTEXT_MULTI_SESSION_DEBUG); +registerDebugToolBarItem(FOCUS_SESSION_ID, FOCUS_SESSION_LABEL, 100, Codicon.listTree, ContextKeyExpr.and(CONTEXT_MULTI_SESSION_DEBUG, CONTEXT_TOOLBAR_COMMAND_CENTER.negate())); MenuRegistry.appendMenuItem(MenuId.DebugToolBarStop, { group: 'navigation', diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 6bfd77d60b41bb..d426590f0da3a7 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -2384,6 +2384,10 @@ export class ExtensionStatusAction extends ExtensionAction { const isRunning = this.extensionService.extensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier)); if (isEnabled && isRunning) { + if (this.extension.enablementState === EnablementState.EnabledWorkspace) { + this.updateStatus({ message: new MarkdownString(localize('workspace enabled', "This extension is enabled for this workspace by the user.")) }, true); + return; + } if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) { if (this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer) { this.updateStatus({ message: new MarkdownString(localize('extension enabled on remote', "Extension is enabled on '{0}'", this.extension.server.label)) }, true); @@ -2394,10 +2398,6 @@ export class ExtensionStatusAction extends ExtensionAction { this.updateStatus({ message: new MarkdownString(localize('globally enabled', "This extension is enabled globally.")) }, true); return; } - if (this.extension.enablementState === EnablementState.EnabledWorkspace) { - this.updateStatus({ message: new MarkdownString(localize('workspace enabled', "This extension is enabled for this workspace by the user.")) }, true); - return; - } } if (!isEnabled && !isRunning) { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatAccessibleView.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatAccessibleView.ts index 162d8329ebba00..fbb0e1b2012f64 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatAccessibleView.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatAccessibleView.ts @@ -5,7 +5,7 @@ import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_RESPONSE_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; -import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { Disposable } from 'vs/base/common/lifecycle'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; @@ -33,6 +33,7 @@ export class InlineChatAccessibleViewContribution extends Disposable { return false; } accessibleViewService.show({ + id: AccessibleViewProviderId.InlineChat, verbositySettingKey: AccessibilityVerbositySettingId.InlineChat, provideContent(): string { return responseContent; }, onClose() { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index a8c3f04aa1d9f8..48aa66b8d241cc 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -248,6 +248,8 @@ export class InlineChatController implements IEditorContribution { if (!needsMargin) { this._zone.value.setWidgetMargins(widgetPosition, 0); } + } + if (this._activeSession) { this._zone.value.updateBackgroundColor(widgetPosition, this._activeSession.wholeRange.value); } this._zone.value.show(widgetPosition); @@ -371,10 +373,12 @@ export class InlineChatController implements IEditorContribution { this._messages.fire(msg); })); + const altVersionNow = this._editor.getModel()?.getAlternativeVersionId(); + this._sessionStore.add(this._editor.onDidChangeModelContent(e => { if (!this._ignoreModelContentChanged && this._strategy?.hasFocus()) { - this._ctxUserDidEdit.set(true); + this._ctxUserDidEdit.set(altVersionNow !== this._editor.getModel()?.getAlternativeVersionId()); } if (this._ignoreModelContentChanged || this._strategy?.hasFocus()) { @@ -465,7 +469,12 @@ export class InlineChatController implements IEditorContribution { const { lastExchange } = this._activeSession; this._activeSession.addInput(lastExchange.prompt.retry()); if (lastExchange.response instanceof EditResponse) { - await this._strategy.undoChanges(lastExchange.response); + try { + this._ignoreModelContentChanged = true; + await this._strategy.undoChanges(lastExchange.response); + } finally { + this._ignoreModelContentChanged = false; + } } return State.MAKE_REQUEST; } @@ -593,7 +602,7 @@ export class InlineChatController implements IEditorContribution { } } - private async [State.APPLY_RESPONSE](): Promise { + private async [State.APPLY_RESPONSE](): Promise { assertType(this._activeSession); assertType(this._strategy); @@ -604,7 +613,7 @@ export class InlineChatController implements IEditorContribution { const canContinue = this._strategy.checkChanges(response); if (!canContinue) { - return State.ACCEPT; + return State.CANCEL; } } return State.SHOW_RESPONSE; @@ -643,7 +652,7 @@ export class InlineChatController implements IEditorContribution { } } - private async [State.SHOW_RESPONSE](): Promise { + private async [State.SHOW_RESPONSE](): Promise { assertType(this._activeSession); assertType(this._strategy); @@ -705,7 +714,7 @@ export class InlineChatController implements IEditorContribution { const canContinue = this._strategy.checkChanges(response); if (!canContinue) { - return State.ACCEPT; + return State.CANCEL; } status = this._configurationService.getValue('accessibility.verbosity.inlineChat') === true ? localize('editResponseMessage', "Use tab to navigate to the diff editor and review proposed changes.") : ''; await this._strategy.renderChanges(response); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/editorHint/emptyCellEditorHint.ts b/src/vs/workbench/contrib/notebook/browser/contrib/editorHint/emptyCellEditorHint.ts index 3948fef31179fd..24e258e00ffac2 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/editorHint/emptyCellEditorHint.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/editorHint/emptyCellEditorHint.ts @@ -54,6 +54,11 @@ export class EmptyCellEditorHintContribution extends EmptyTextEditorHintContribu } protected override _shouldRenderHint(): boolean { + // TODO@rebornix, remove this when we have a better way to present the editor hints in empty cells + if (this.productService.quality === 'stable') { + return false; + } + const shouldRenderHint = super._shouldRenderHint(); if (!shouldRenderHint) { return false; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts index 4305c75e301454..d93af0a7a1cbdc 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts @@ -26,7 +26,7 @@ import { listErrorForeground, listWarningForeground } from 'vs/platform/theme/co import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { IEditorPane } from 'vs/workbench/common/editor'; -import { CellRevealType, INotebookEditorOptions, INotebookEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellRevealType, ICellModelDecorations, ICellModelDeltaDecorations, INotebookEditorOptions, INotebookEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEditor'; import { NotebookCellOutlineProvider } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider'; import { CellKind, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -35,6 +35,8 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle import { IOutline, IOutlineComparator, IOutlineCreator, IOutlineListConfig, IOutlineService, IQuickPickDataSource, IQuickPickOutlineElement, OutlineChangeEvent, OutlineConfigCollapseItemsValues, OutlineConfigKeys, OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; import { OutlineEntry } from 'vs/workbench/contrib/notebook/browser/viewModel/OutlineEntry'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { IModelDeltaDecoration } from 'vs/editor/common/model'; +import { Range } from 'vs/editor/common/core/range'; class NotebookOutlineTemplate { @@ -303,12 +305,49 @@ export class NotebookCellOutline implements IOutline { if (!widget) { return Disposable.None; } - widget.revealInCenterIfOutsideViewport(entry.cell); + + + if (entry.range) { + const range = Range.lift(entry.range); + widget.revealRangeInCenterIfOutsideViewportAsync(entry.cell, range); + } else { + widget.revealInCenterIfOutsideViewport(entry.cell); + } + const ids = widget.deltaCellDecorations([], [{ handle: entry.cell.handle, options: { className: 'nb-symbolHighlight', outputClassName: 'nb-symbolHighlight' } }]); - return toDisposable(() => { widget.deltaCellDecorations(ids, []); }); + + let editorDecorations: ICellModelDecorations[]; + widget.changeModelDecorations(accessor => { + if (entry.range) { + const decorations: IModelDeltaDecoration[] = [ + { + range: entry.range, options: { + description: 'document-symbols-outline-range-highlight', + className: 'rangeHighlight', + isWholeLine: true + } + } + ]; + const deltaDecoration: ICellModelDeltaDecorations = { + ownerId: entry.cell.handle, + decorations: decorations + }; + + editorDecorations = accessor.deltaDecorations([], [deltaDecoration]); + } + }); + + return toDisposable(() => { + widget.deltaCellDecorations(ids, []); + if (editorDecorations?.length) { + widget.changeModelDecorations(accessor => { + accessor.deltaDecorations(editorDecorations, []); + }); + } + }); } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/saveParticipants/saveParticipants.ts b/src/vs/workbench/contrib/notebook/browser/contrib/saveParticipants/saveParticipants.ts index dc42987bbce874..1195c7767b2066 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/saveParticipants/saveParticipants.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/saveParticipants/saveParticipants.ts @@ -45,8 +45,7 @@ class FormatOnSaveParticipant implements IStoredFileWorkingCopySaveParticipant { @ITextModelService private readonly textModelService: ITextModelService, @IBulkEditService private readonly bulkEditService: IBulkEditService, @IConfigurationService private readonly configurationService: IConfigurationService, - ) { - } + ) { } async participate(workingCopy: IStoredFileWorkingCopy, context: { reason: SaveReason }, progress: IProgress, token: CancellationToken): Promise { if (!workingCopy.model || !(workingCopy.model instanceof NotebookFileWorkingCopyModel)) { @@ -177,8 +176,8 @@ class TrimFinalNewLinesParticipant implements IStoredFileWorkingCopySaveParticip ) { } async participate(workingCopy: IStoredFileWorkingCopy, context: { reason: SaveReason }, progress: IProgress, _token: CancellationToken): Promise { - if (this.configurationService.getValue('files.trimTrailingWhitespace')) { - this.doTrimFinalNewLines(workingCopy, context.reason === SaveReason.AUTO, progress); + if (this.configurationService.getValue('files.trimFinalNewlines')) { + await this.doTrimFinalNewLines(workingCopy, context.reason === SaveReason.AUTO, progress); } } @@ -250,15 +249,16 @@ class FinalNewLineParticipant implements IStoredFileWorkingCopySaveParticipant { constructor( @IConfigurationService private readonly configurationService: IConfigurationService, @IBulkEditService private readonly bulkEditService: IBulkEditService, + @IEditorService private readonly editorService: IEditorService, ) { } async participate(workingCopy: IStoredFileWorkingCopy, context: { reason: SaveReason }, progress: IProgress, _token: CancellationToken): Promise { if (this.configurationService.getValue('files.insertFinalNewline')) { - this.doInsertFinalNewLine(workingCopy, context, progress); + await this.doInsertFinalNewLine(workingCopy, context.reason === SaveReason.AUTO, progress); } } - private async doInsertFinalNewLine(workingCopy: IStoredFileWorkingCopy, context: { reason: SaveReason }, progress: IProgress): Promise { + private async doInsertFinalNewLine(workingCopy: IStoredFileWorkingCopy, isAutoSaved: boolean, progress: IProgress): Promise { if (!workingCopy.model || !(workingCopy.model instanceof NotebookFileWorkingCopyModel)) { return; } @@ -266,6 +266,13 @@ class FinalNewLineParticipant implements IStoredFileWorkingCopySaveParticipant { const disposable = new DisposableStore(); const notebook = workingCopy.model.notebookModel; + // get initial cursor positions + const activeCellEditor = getActiveCellCodeEditor(this.editorService); + let selections; + if (activeCellEditor) { + selections = activeCellEditor.getSelections() ?? []; + } + try { const allCellEdits = await Promise.all(notebook.cells.map(async (cell) => { if (cell.cellKind !== CellKind.Code) { @@ -285,6 +292,10 @@ class FinalNewLineParticipant implements IStoredFileWorkingCopySaveParticipant { const filteredEdits = allCellEdits.filter(edit => edit !== undefined) as ResourceEdit[]; await this.bulkEditService.apply(filteredEdits, { label: localize('insertFinalNewLine', "Insert Final New Line"), code: 'undoredo.insertFinalNewLine' }); + // set cursor back to initial position after inserting final new line + if (activeCellEditor && selections) { + activeCellEditor.setSelections(selections); + } } finally { progress.report({ increment: 100 }); disposable.dispose(); @@ -318,7 +329,8 @@ class CodeActionOnSaveParticipant implements IStoredFileWorkingCopySaveParticipa if (context.reason === SaveReason.AUTO) { // currently this won't happen, as vs/editor/contrib/codeAction/browser/codeAction.ts L#104 filters out codeactions on autosave. Just future-proofing // ? notebook CodeActions on autosave seems dangerous (perf-wise) - saveTrigger = 'always'; + // saveTrigger = 'always'; // TODO@Yoyokrazy, support during debt + return undefined; } else if (context.reason === SaveReason.EXPLICIT) { saveTrigger = 'explicit'; } else { @@ -328,7 +340,7 @@ class CodeActionOnSaveParticipant implements IStoredFileWorkingCopySaveParticipa const notebookModel = workingCopy.model.notebookModel; - const setting = this.configurationService.getValue<{ [kind: string]: string }>(NotebookSetting.codeActionsOnSave); + const setting = this.configurationService.getValue<{ [kind: string]: string | boolean }>(NotebookSetting.codeActionsOnSave); if (!setting) { return undefined; } @@ -341,9 +353,9 @@ class CodeActionOnSaveParticipant implements IStoredFileWorkingCopySaveParticipa const allCodeActions = this.createCodeActionsOnSave(settingItems); const excludedActions = allCodeActions - .filter(x => setting[x.value] === 'never'); + .filter(x => setting[x.value] === 'never' || setting[x.value] === false); const includedActions = allCodeActions - .filter(x => setting[x.value] === saveTrigger); + .filter(x => setting[x.value] === saveTrigger || setting[x.value] === true); const editorCodeActionsOnSave = includedActions.filter(x => !CodeActionKind.Notebook.contains(x)); const notebookCodeActionsOnSave = includedActions.filter(x => CodeActionKind.Notebook.contains(x)); diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 6df0f791687e80..1c534d746b2553 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -964,14 +964,14 @@ configurationRegistry.registerConfiguration({ default: false }, [NotebookSetting.codeActionsOnSave]: { - markdownDescription: nls.localize('notebook.codeActionsOnSave', "Run a series of CodeActions for a notebook on save. CodeActions must be specified, the file must not be saved after delay, and the editor must not be shutting down. Example: `source.fixAll: true`"), + markdownDescription: nls.localize('notebook.codeActionsOnSave', 'Run a series of CodeActions for a notebook on save. CodeActions must be specified, the file must not be saved after delay, and the editor must not be shutting down. Example: `"notebook.source.organizeImports": "explicit"`'), type: 'object', additionalProperties: { - type: 'string', - enum: ['explicit', 'never'], + type: ['string', 'boolean'], + enum: ['explicit', 'never', true, false], // enum: ['explicit', 'always', 'never'], -- autosave support needs to be built first // nls.localize('always', 'Always triggers Code Actions on save, including autosave, focus, and window change events.'), - enumDescriptions: [nls.localize('never', 'Never triggers Code Actions on save.'), nls.localize('explicit', 'Triggers Code Actions only when explicitly saved.')], + enumDescriptions: [nls.localize('explicit', 'Triggers Code Actions only when explicitly saved.'), nls.localize('never', 'Never triggers Code Actions on save.'), nls.localize('explicitBoolean', 'Triggers Code Actions only when explicitly saved. This value will be deprecated in favor of "explicit".'), nls.localize('neverBoolean', 'Triggers Code Actions only when explicitly saved. This value will be deprecated in favor of "never".')], }, default: {} }, @@ -1020,22 +1020,24 @@ configurationRegistry.registerConfiguration({ default: typeof product.quality === 'string' && product.quality !== 'stable' // only enable as default in insiders }, [NotebookSetting.scrollToRevealCell]: { - markdownDescription: nls.localize('notebook.scrolling.revealCellBehavior.description', "How far to scroll when revealing the selected, e.g. when exectuting {0}.", 'notebook.cell.executeAndSelectBelow'), + markdownDescription: nls.localize('notebook.scrolling.revealNextCellOnExecute.description', "How far to scroll when revealing the next cell upon running {0}.", 'notebook.cell.executeAndSelectBelow'), type: 'string', enum: ['fullCell', 'firstLine', 'none'], markdownEnumDescriptions: [ - nls.localize('notebook.scrolling.revealCellBehavior.fullCell.description', 'Scroll to fully reveal the selected cell.'), - nls.localize('notebook.scrolling.revealCellBehavior.firstLine.description', 'Scroll to reveal the first line of the selected cell.'), - nls.localize('notebook.scrolling.revealCellBehavior.nonedescription', 'Do not scroll to reveal the selected cell.'), + nls.localize('notebook.scrolling.revealNextCellOnExecute.fullCell.description', 'Scroll to fully reveal the next cell.'), + nls.localize('notebook.scrolling.revealNextCellOnExecute.firstLine.description', 'Scroll to reveal the first line of the next cell.'), + nls.localize('notebook.scrolling.revealNextCellOnExecute.none.description', 'Do not scroll.'), ], default: 'fullCell' }, [NotebookSetting.anchorToFocusedCell]: { - markdownDescription: nls.localize('notebook.scrolling.anchorToFocusedCell.description', "Keep the focused cell steady while surrounding cells change size"), + markdownDescription: nls.localize('notebook.scrolling.anchorToFocusedCell.description', "Experimental. Keep the focused cell steady while surrounding cells change size."), type: 'string', - enum: ['auto', 'true', 'false'], + enum: ['auto', 'on', 'off'], markdownEnumDescriptions: [ - nls.localize('notebook.scrolling.anchorToFocusedCell.auto.description', "Anchor to the focused cell unless {0} is set to {1}", 'notebook.scrolling.revealCellBehavior', 'none') + nls.localize('notebook.scrolling.anchorToFocusedCell.auto.description', "Anchor the viewport to the focused cell depending on context unless {0} is set to {1}.", 'notebook.scrolling.revealCellBehavior', 'none'), + nls.localize('notebook.scrolling.anchorToFocusedCell.on.description', "Always anchor the viewport to the focused cell."), + nls.localize('notebook.scrolling.anchorToFocusedCell.off.description', "The focused cell may shift around as cells resize.") ], default: 'auto' } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookAccessibility.ts b/src/vs/workbench/contrib/notebook/browser/notebookAccessibility.ts index a019db5693e981..9dd59dfbcc51a4 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookAccessibility.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookAccessibility.ts @@ -9,7 +9,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; -import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -49,6 +49,7 @@ export async function runAccessibilityHelpAction(accessor: ServicesAccessor, edi const accessibleViewService = accessor.get(IAccessibleViewService); const helpText = getAccessibilityHelpText(accessor); accessibleViewService.show({ + id: AccessibleViewProviderId.Notebook, verbositySettingKey: AccessibilityVerbositySettingId.Notebook, provideContent: () => helpText, onClose: () => { @@ -108,6 +109,7 @@ export function showAccessibleOutput(accessibleViewService: IAccessibleViewServi } accessibleViewService.show({ + id: AccessibleViewProviderId.Notebook, verbositySettingKey: AccessibilityVerbositySettingId.Notebook, provideContent(): string { return outputContent; }, onClose() { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index d5ae1dc5203011..904519fa756d65 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -2384,6 +2384,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this.revealInView(cell); } else if (options?.revealBehavior === ScrollToRevealBehavior.firstLine) { this.revealFirstLineIfOutsideViewport(cell); + } else if (options?.revealBehavior === ScrollToRevealBehavior.fullCell) { + this.revealInView(cell); } else { this.revealInCenterIfOutsideViewport(cell); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellComments.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellComments.ts index d8383bcab1c556..bf0fe703109c26 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellComments.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellComments.ts @@ -63,6 +63,7 @@ export class CellComments extends CellContentPart { this._commentThreadWidget = this.instantiationService.createInstance( CommentThreadWidget, this.container, + this.notebookEditor, owner, this.notebookEditor.textModel!.uri, this.contextKeyService, diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 27562d911db51b..8fc90909409663 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -1207,8 +1207,8 @@ export class NotebookCellList extends WorkbenchList implements ID const anchorFocusedSetting = this.configurationService.getValue(NotebookSetting.anchorToFocusedCell); const allowScrolling = this.configurationService.getValue(NotebookSetting.scrollToRevealCell) !== 'none'; - const anchorToFocusedCell = anchorFocusedSetting === 'true' || (allowScrolling && anchorFocusedSetting !== 'false'); - if (focused && anchorToFocusedCell) { + const scrollHeuristic = allowScrolling && anchorFocusedSetting === 'auto' && this.view.elementTop(index) < this.view.getScrollTop(); + if (focused && (anchorFocusedSetting === 'on' || scrollHeuristic)) { this.view.updateElementHeight(index, size, focus); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 5197ae5372d2eb..113c4dc11af7f2 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -476,7 +476,7 @@ async function webviewPreloads(ctx: PreloadContext) { function focusFirstFocusableOrContainerInOutput(cellOrOutputId: string, alternateId?: string) { const cellOutputContainer = document.getElementById(cellOrOutputId) ?? - alternateId ? document.getElementById(alternateId!) : undefined; + (alternateId ? document.getElementById(alternateId!) : undefined); if (cellOutputContainer) { if (cellOutputContainer.contains(document.activeElement)) { return; diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/OutlineEntry.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/OutlineEntry.ts index 9fd1c4f7160b75..398f676c876ddd 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/OutlineEntry.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/OutlineEntry.ts @@ -8,10 +8,9 @@ import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/marke import { ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { executingStateIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { Range } from 'vs/editor/common/core/range'; +import { IRange } from 'vs/editor/common/core/range'; import { SymbolKind, SymbolKinds } from 'vs/editor/common/languages'; - export interface IOutlineMarkerInfo { readonly count: number; readonly topSev: MarkerSeverity; @@ -38,7 +37,7 @@ export class OutlineEntry { readonly label: string, readonly isExecuting: boolean, readonly isPaused: boolean, - readonly position?: Range, + readonly range?: IRange, readonly symbolKind?: SymbolKind, ) { } @@ -59,6 +58,13 @@ export class OutlineEntry { return this._markerInfo; } + get position() { + if (this.range) { + return { startLineNumber: this.range.startLineNumber, startColumn: this.range.startColumn }; + } + return undefined; + } + updateMarkers(markerService: IMarkerService): void { if (this.cell.cellKind === CellKind.Code) { // a code cell can have marker diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineEntryFactory.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineEntryFactory.ts index fdcd175c3543f7..54335576ac92ed 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineEntryFactory.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineEntryFactory.ts @@ -12,12 +12,12 @@ import { getMarkdownHeadersInCell } from 'vs/workbench/contrib/notebook/browser/ import { OutlineEntry } from './OutlineEntry'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; -import { Range } from 'vs/editor/common/core/range'; +import { IRange } from 'vs/editor/common/core/range'; import { SymbolKind } from 'vs/editor/common/languages'; type entryDesc = { name: string; - position: Range; + range: IRange; level: number; kind: SymbolKind; }; @@ -72,9 +72,8 @@ export class NotebookOutlineEntryFactory { // So symbols need to be precached before this function is called to get the full list. if (cachedEntries) { cachedEntries.forEach((cached) => { - entries.push(new OutlineEntry(index++, cached.level, cell, cached.name, false, false, cached.position, cached.kind)); + entries.push(new OutlineEntry(index++, cached.level, cell, cached.name, false, false, cached.range, cached.kind)); }); - } } @@ -107,11 +106,7 @@ type documentSymbol = ReturnType[number]; function createOutlineEntries(symbols: documentSymbol[], level: number): entryDesc[] { const entries: entryDesc[] = []; symbols.forEach(symbol => { - const position = new Range(symbol.selectionRange.startLineNumber, - symbol.selectionRange.startColumn, - symbol.selectionRange.startLineNumber, - symbol.selectionRange.startColumn); - entries.push({ name: symbol.name, position, level, kind: symbol.kind }); + entries.push({ name: symbol.name, range: symbol.range, level, kind: symbol.kind }); if (symbol.children) { entries.push(...createOutlineEntries(symbol.children, level + 1)); } diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 8e785ee7f5b961..98d58e5bf3fdba 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -964,8 +964,8 @@ export const NotebookSetting = { confirmDeleteRunningCell: 'notebook.confirmDeleteRunningCell', remoteSaving: 'notebook.experimental.remoteSave', gotoSymbolsAllSymbols: 'notebook.gotoSymbols.showAllSymbols', - scrollToRevealCell: 'notebook.scrolling.revealCellBehavior', - anchorToFocusedCell: 'notebook.scrolling.anchorToFocusedCell' + scrollToRevealCell: 'notebook.scrolling.revealNextCellOnExecute', + anchorToFocusedCell: 'notebook.scrolling.experimental.anchorToFocusedCell' } as const; export const enum CellStatusbarAlignment { diff --git a/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookSymbols.test.ts b/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookSymbols.test.ts index 94ae1e475237d3..8826eb3dda787e 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookSymbols.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookSymbols.test.ts @@ -16,7 +16,7 @@ import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/co suite('Notebook Symbols', function () { ensureNoDisposablesAreLeakedInTestSuite(); - type textSymbol = { name: string; selectionRange: {}; children?: textSymbol[] }; + type textSymbol = { name: string; range: {}; children?: textSymbol[] }; const symbolsPerTextModel: Record = {}; function setSymbolsForTextModel(symbols: textSymbol[], textmodelId = 'textId') { symbolsPerTextModel[textmodelId] = symbols; @@ -64,7 +64,7 @@ suite('Notebook Symbols', function () { } test('Cell without symbols cache', function () { - setSymbolsForTextModel([{ name: 'var', selectionRange: {} }]); + setSymbolsForTextModel([{ name: 'var', range: {} }]); const entryFactory = new NotebookOutlineEntryFactory(executionService); const entries = entryFactory.getOutlineEntries(createCellViewModel(), 0); @@ -73,7 +73,7 @@ suite('Notebook Symbols', function () { }); test('Cell with simple symbols', async function () { - setSymbolsForTextModel([{ name: 'var1', selectionRange: {} }, { name: 'var2', selectionRange: {} }]); + setSymbolsForTextModel([{ name: 'var1', range: {} }, { name: 'var2', range: {} }]); const entryFactory = new NotebookOutlineEntryFactory(executionService); const cell = createCellViewModel(); @@ -92,8 +92,8 @@ suite('Notebook Symbols', function () { test('Cell with nested symbols', async function () { setSymbolsForTextModel([ - { name: 'root1', selectionRange: {}, children: [{ name: 'nested1', selectionRange: {} }, { name: 'nested2', selectionRange: {} }] }, - { name: 'root2', selectionRange: {}, children: [{ name: 'nested1', selectionRange: {} }] } + { name: 'root1', range: {}, children: [{ name: 'nested1', range: {} }, { name: 'nested2', range: {} }] }, + { name: 'root2', range: {}, children: [{ name: 'nested1', range: {} }] } ]); const entryFactory = new NotebookOutlineEntryFactory(executionService); const cell = createCellViewModel(); @@ -115,8 +115,8 @@ suite('Notebook Symbols', function () { }); test('Multiple Cells with symbols', async function () { - setSymbolsForTextModel([{ name: 'var1', selectionRange: {} }], '$1'); - setSymbolsForTextModel([{ name: 'var2', selectionRange: {} }], '$2'); + setSymbolsForTextModel([{ name: 'var1', range: {} }], '$1'); + setSymbolsForTextModel([{ name: 'var2', range: {} }], '$2'); const entryFactory = new NotebookOutlineEntryFactory(executionService); const cell1 = createCellViewModel(1, '$1'); diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookCellList.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookCellList.test.ts index fb3db12dacec12..2137c0421c9c26 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookCellList.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookCellList.test.ts @@ -6,8 +6,10 @@ import * as assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { createNotebookCellList, setupInstantiationService, withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; suite('NotebookCellList', () => { @@ -23,6 +25,10 @@ suite('NotebookCellList', () => { setup(() => { testDisposables = new DisposableStore(); instantiationService = setupInstantiationService(testDisposables); + const config = new TestConfigurationService({ + [NotebookSetting.anchorToFocusedCell]: 'auto' + }); + instantiationService.stub(IConfigurationService, config); }); test('revealElementsInView: reveal fully visible cell should not scroll', async function () { diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts b/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts index f06dd87b995a18..3b666ca6d9ae5c 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts @@ -354,7 +354,7 @@ class RemoteSearchKeysProvider { export class RemoteSearchProvider implements ISearchProvider { private static readonly AI_RELATED_INFORMATION_THRESHOLD = 0.73; - private static readonly AI_RELATED_INFORMATION_MAX_PICKS = 15; + private static readonly AI_RELATED_INFORMATION_MAX_PICKS = 5; private readonly _keysProvider: RemoteSearchKeysProvider; private _filter: string = ''; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index fbeea71b9f9026..4f656a0c467fa1 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -194,6 +194,7 @@ export class SettingsEditor2 extends EditorPane { private _searchResultModel: SearchResultModel | null = null; private searchResultLabel: string | null = null; private lastSyncedLabel: string | null = null; + private settingsOrderByTocIndex: Map | null = null; private tocRowFocused: IContextKey; private settingRowFocused: IContextKey; @@ -1241,9 +1242,31 @@ export class SettingsEditor2 extends EditorPane { return undefined; } + private createSettingsOrderByTocIndex(resolvedSettingsRoot: ITOCEntry): Map { + const index = new Map(); + function indexSettings(resolvedSettingsRoot: ITOCEntry, counter = 0): number { + if (resolvedSettingsRoot.settings) { + for (const setting of resolvedSettingsRoot.settings) { + if (!index.has(setting.key)) { + index.set(setting.key, counter++); + } + } + } + if (resolvedSettingsRoot.children) { + for (const child of resolvedSettingsRoot.children) { + counter = indexSettings(child, counter); + } + } + return counter; + } + indexSettings(resolvedSettingsRoot); + return index; + } + private refreshModels(resolvedSettingsRoot: ITOCEntry) { this.settingsTreeModel.update(resolvedSettingsRoot); - this.tocTreeModel.settingsTreeRoot = this.settingsTreeModel.root as SettingsTreeGroupElement; + this.tocTreeModel.settingsTreeRoot = this.settingsTreeModel.root; + this.settingsOrderByTocIndex = this.createSettingsOrderByTocIndex(resolvedSettingsRoot); } private async onConfigUpdate(keys?: ReadonlySet, forceRefresh = false, schemaChange = false): Promise { @@ -1559,7 +1582,7 @@ export class SettingsEditor2 extends EditorPane { * Return a fake SearchResultModel which can hold a flat list of all settings, to be filtered (@modified etc) */ private createFilterModel(): SearchResultModel { - const filterModel = this.instantiationService.createInstance(SearchResultModel, this.viewState, this.workspaceTrustManagementService.isWorkspaceTrusted()); + const filterModel = this.instantiationService.createInstance(SearchResultModel, this.viewState, this.settingsOrderByTocIndex, this.workspaceTrustManagementService.isWorkspaceTrusted()); const fullResult: ISearchResult = { filterMatches: [] @@ -1663,7 +1686,7 @@ export class SettingsEditor2 extends EditorPane { return null; } if (!this.searchResultModel) { - this.searchResultModel = this.instantiationService.createInstance(SearchResultModel, this.viewState, this.workspaceTrustManagementService.isWorkspaceTrusted()); + this.searchResultModel = this.instantiationService.createInstance(SearchResultModel, this.viewState, this.settingsOrderByTocIndex, this.workspaceTrustManagementService.isWorkspaceTrusted()); // Must be called before this.renderTree() // to make sure the search results count is set. this.searchResultModel.setResult(type, result); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index e370cf8382a304..f9bc2e00fb0e56 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -62,7 +62,7 @@ import { ISettingOverrideClickEvent, SettingsTreeIndicatorsLabel, getIndicatorsL import { ITOCEntry } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; import { ISettingsEditorViewState, SettingsTreeElement, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeNewExtensionsElement, SettingsTreeSettingElement, inspectSetting, settingKeyToDisplayFormat } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; import { ExcludeSettingWidget, IListDataItem, IObjectDataItem, IObjectEnumOption, IObjectKeySuggester, IObjectValueSuggester, ISettingListChangeEvent, IncludeSettingWidget, ListSettingWidget, ObjectSettingCheckboxWidget, ObjectSettingDropdownWidget, ObjectValue } from 'vs/workbench/contrib/preferences/browser/settingsWidgets'; -import { LANGUAGE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/preferences/common/preferences'; +import { LANGUAGE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU, compareTwoNullableNumbers } from 'vs/workbench/contrib/preferences/common/preferences'; import { settingsNumberInputBackground, settingsNumberInputBorder, settingsNumberInputForeground, settingsSelectBackground, settingsSelectBorder, settingsSelectForeground, settingsSelectListBorder, settingsTextInputBackground, settingsTextInputBorder, settingsTextInputForeground } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry'; import { APPLY_ALL_PROFILES_SETTING, IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -409,12 +409,6 @@ export function resolveConfiguredUntrustedSettings(groups: ISettingsGroup[], tar return [...allSettings].filter(setting => setting.restricted && inspectSetting(setting.key, target, languageFilter, configurationService).isConfigured); } -function compareNullableIntegers(a?: number, b?: number) { - const firstElem = a ?? Number.MAX_SAFE_INTEGER; - const secondElem = b ?? Number.MAX_SAFE_INTEGER; - return firstElem - secondElem; -} - export async function createTocTreeForExtensionSettings(extensionService: IExtensionService, groups: ISettingsGroup[]): Promise> { const extGroupTree = new Map>(); const addEntryToTree = (extensionId: string, extensionName: string, childEntry: ITOCEntry) => { @@ -452,9 +446,10 @@ export async function createTocTreeForExtensionSettings(extensionService: IExten const extGroups: ITOCEntry[] = []; for (const extensionRootEntry of extGroupTree.values()) { for (const child of extensionRootEntry.children!) { - // Sort the individual settings of the child. + // Sort the individual settings of the child by order. + // Leave the undefined order settings untouched. child.settings?.sort((a, b) => { - return compareNullableIntegers(a.order, b.order); + return compareTwoNullableNumbers(a.order, b.order); }); } @@ -468,8 +463,9 @@ export async function createTocTreeForExtensionSettings(extensionService: IExten }); } else { // Sort the categories. + // Leave the undefined order categories untouched. extensionRootEntry.children!.sort((a, b) => { - return compareNullableIntegers(a.order, b.order); + return compareTwoNullableNumbers(a.order, b.order); }); // If there is a category that matches the setting name, diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index 7e30259a1bba17..2cc75e4d5b0d31 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -10,7 +10,7 @@ import { URI } from 'vs/base/common/uri'; import { ConfigurationTarget, IConfigurationValue } from 'vs/platform/configuration/common/configuration'; import { SettingsTarget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets'; import { ITOCEntry, knownAcronyms, knownTermMappings, tocData } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; -import { ENABLE_EXTENSION_TOGGLE_SETTINGS, ENABLE_LANGUAGE_FILTER, MODIFIED_SETTING_TAG, POLICY_SETTING_TAG, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences'; +import { ENABLE_EXTENSION_TOGGLE_SETTINGS, ENABLE_LANGUAGE_FILTER, MODIFIED_SETTING_TAG, POLICY_SETTING_TAG, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG, compareTwoNullableNumbers } from 'vs/workbench/contrib/preferences/common/preferences'; import { IExtensionSetting, ISearchResult, ISetting, ISettingMatch, SettingMatchType, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { FOLDER_SCOPES, WORKSPACE_SCOPES, REMOTE_MACHINE_SCOPES, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService, APPLICATION_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; @@ -858,35 +858,44 @@ export class SearchResultModel extends SettingsTreeModel { private cachedUniqueSearchResults: ISearchResult | null = null; private newExtensionSearchResults: ISearchResult | null = null; private searchResultCount: number | null = null; + private settingsOrderByTocIndex: Map | null; readonly id = 'searchResultModel'; constructor( viewState: ISettingsEditorViewState, + settingsOrderByTocIndex: Map | null, isWorkspaceTrusted: boolean, - @IWorkbenchConfigurationService configurationService: IWorkbenchConfigurationService, - @IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService, + @IWorkbenchConfigurationService private readonly configurationService: IWorkbenchConfigurationService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @ILanguageService languageService: ILanguageService, @IUserDataProfileService userDataProfileService: IUserDataProfileService, @IProductService productService: IProductService ) { super(viewState, isWorkspaceTrusted, configurationService, languageService, userDataProfileService, productService); + this.settingsOrderByTocIndex = settingsOrderByTocIndex; this.update({ id: 'searchResultModel', label: '' }); } - private compareTwoNullableNumbers(a: number | undefined, b: number | undefined): number { - const aOrMax = a ?? Number.MAX_SAFE_INTEGER; - const bOrMax = b ?? Number.MAX_SAFE_INTEGER; - if (aOrMax < bOrMax) { - return -1; - } else if (aOrMax > bOrMax) { - return 1; - } else { - return 0; + private sortResults(filterMatches: ISettingMatch[]): ISettingMatch[] { + if (this.settingsOrderByTocIndex) { + for (const match of filterMatches) { + match.setting.internalOrder = this.settingsOrderByTocIndex.get(match.setting.key); + } } - } - private sortResults(filterMatches: ISettingMatch[]): ISettingMatch[] { + const tocHiddenDuringSearch = this.configurationService.getValue('workbench.settings.settingsSearchTocBehavior') === 'hide'; + if (!tocHiddenDuringSearch) { + // Sort the settings according to internal order if indexed. + if (this.settingsOrderByTocIndex) { + filterMatches.sort((a, b) => compareTwoNullableNumbers(a.setting.internalOrder, b.setting.internalOrder)); + } + return filterMatches; + } + + // The table of contents is hidden during the search. + // The settings could appear in a more haphazard order. + // Sort the settings according to their score. filterMatches.sort((a, b) => { if (a.matchType !== b.matchType) { // Sort by match type if the match types are not the same. @@ -897,24 +906,9 @@ export class SearchResultModel extends SettingsTreeModel { // Sort by score. return b.score - a.score; } else { - // The match types are the same. - if (a.setting.extensionInfo && b.setting.extensionInfo - && a.setting.extensionInfo.id === b.setting.extensionInfo.id) { - // These settings belong to the same extension. - if (a.setting.categoryLabel !== b.setting.categoryLabel - && (a.setting.categoryOrder !== undefined || b.setting.categoryOrder !== undefined) - && a.setting.categoryOrder !== b.setting.categoryOrder) { - // These two settings don't belong to the same category and have different category orders. - return this.compareTwoNullableNumbers(a.setting.categoryOrder, b.setting.categoryOrder); - } else if (a.setting.categoryLabel === b.setting.categoryLabel - && (a.setting.order !== undefined || b.setting.order !== undefined) - && a.setting.order !== b.setting.order) { - // These two settings belong to the same category, but have different orders. - return this.compareTwoNullableNumbers(a.setting.order, b.setting.order); - } - } - // In the worst case, go back to lexicographical order. - return b.score - a.score; + // The match types are the same but are not RemoteMatch. + // Sort by their order in the table of contents. + return compareTwoNullableNumbers(a.setting.internalOrder, b.setting.internalOrder); } }); return filterMatches; @@ -946,7 +940,6 @@ export class SearchResultModel extends SettingsTreeModel { this.newExtensionSearchResults = this.rawSearchResults[SearchResultIdx.NewExtensions]; } - // Combine and sort results. combinedFilterMatches = this.sortResults(combinedFilterMatches); this.cachedUniqueSearchResults = { diff --git a/src/vs/workbench/contrib/preferences/common/preferences.ts b/src/vs/workbench/contrib/preferences/common/preferences.ts index 6ead086ea19c1b..1c07a0e9418602 100644 --- a/src/vs/workbench/contrib/preferences/common/preferences.ts +++ b/src/vs/workbench/contrib/preferences/common/preferences.ts @@ -128,3 +128,18 @@ export async function getExperimentalExtensionToggleData(workbenchAssignmentServ } return undefined; } + +/** + * Compares two nullable numbers such that null values always come after defined ones. + */ +export function compareTwoNullableNumbers(a: number | undefined, b: number | undefined): number { + const aOrMax = a ?? Number.MAX_SAFE_INTEGER; + const bOrMax = b ?? Number.MAX_SAFE_INTEGER; + if (aOrMax < bOrMax) { + return -1; + } else if (aOrMax > bOrMax) { + return 1; + } else { + return 0; + } +} diff --git a/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts b/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts index 896ddc2d6032ed..c1c1e3fa5c2b3c 100644 --- a/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts +++ b/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts @@ -152,8 +152,8 @@ registry.registerConfiguration({ 'type': 'string', 'enum': ['hide', 'filter'], 'enumDescriptions': [ - nls.localize('settingsSearchTocBehavior.hide', "Hide the Table of Contents while searching."), - nls.localize('settingsSearchTocBehavior.filter', "Filter the Table of Contents to just categories that have matching settings. Clicking a category will filter the results to that category."), + nls.localize('settingsSearchTocBehavior.hide', "Hide the Table of Contents while searching. The search results will not be grouped by category, and instead will be sorted by similarity to the query, with exact keyword matches coming first."), + nls.localize('settingsSearchTocBehavior.filter', "Filter the Table of Contents to just categories that have matching settings. Clicking a category will filter the results to that category. The search results will be grouped by category."), ], 'description': nls.localize('settingsSearchTocBehavior', "Controls the behavior of the settings editor Table of Contents while searching."), 'default': 'filter', diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index f278f782373891..16eba22cc576d0 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -37,7 +37,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { basename } from 'vs/base/common/resources'; import { MenuId, IMenuService, IMenu, MenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IEditorModel, ScrollType, IEditorContribution, IDiffEditorModel } from 'vs/editor/common/editorCommon'; +import { ScrollType, IEditorContribution, IDiffEditorModel, IEditorModel, IEditorDecorationsCollection } from 'vs/editor/common/editorCommon'; import { OverviewRulerLane, ITextModel, IModelDecorationOptions, MinimapPosition, shouldSynchronizeModel } from 'vs/editor/common/model'; import { equals, sortedDiff } from 'vs/base/common/arrays'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; @@ -54,8 +54,6 @@ import { IChange } from 'vs/editor/common/diff/legacyLinesDiffComputer'; import { Color } from 'vs/base/common/color'; import { ResourceMap } from 'vs/base/common/map'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor'; -import { FILE_EDITOR_INPUT_ID } from 'vs/workbench/contrib/files/common/files'; import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IQuickDiffService, QuickDiff } from 'vs/workbench/contrib/scm/common/quickDiff'; @@ -73,7 +71,7 @@ class DiffActionRunner extends ActionRunner { } export interface IModelRegistry { - getModel(editorModel: IEditorModel): DirtyDiffModel | undefined; + getModel(editorModel: IEditorModel, codeEditor: ICodeEditor): DirtyDiffModel | undefined; } export interface DirtyDiffContribution extends IEditorContribution { @@ -593,7 +591,7 @@ export class GotoPreviousChangeAction extends EditorAction { } const lineNumber = outerEditor.getPosition().lineNumber; - const model = controller.modelRegistry.getModel(outerEditor.getModel()); + const model = controller.modelRegistry.getModel(outerEditor.getModel(), outerEditor); if (!model || model.changes.length === 0) { return; } @@ -635,7 +633,7 @@ export class GotoNextChangeAction extends EditorAction { } const lineNumber = outerEditor.getPosition().lineNumber; - const model = controller.modelRegistry.getModel(outerEditor.getModel()); + const model = controller.modelRegistry.getModel(outerEditor.getModel(), outerEditor); if (!model || model.changes.length === 0) { return; @@ -845,7 +843,7 @@ export class DirtyDiffController extends Disposable implements DirtyDiffContribu return false; } - const model = this.modelRegistry.getModel(editorModel); + const model = this.modelRegistry.getModel(editorModel, this.editor); if (!model) { return false; @@ -958,7 +956,7 @@ export class DirtyDiffController extends Disposable implements DirtyDiffContribu return; } - const model = this.modelRegistry.getModel(editorModel); + const model = this.modelRegistry.getModel(editorModel, this.editor); if (!model) { return; @@ -985,7 +983,7 @@ export class DirtyDiffController extends Disposable implements DirtyDiffContribu return []; } - const model = this.modelRegistry.getModel(this.editor.getModel()); + const model = this.modelRegistry.getModel(this.editor.getModel(), this.editor); if (!model) { return []; @@ -1080,11 +1078,12 @@ class DirtyDiffDecorator extends Disposable { private modifiedOptions: ModelDecorationOptions; private modifiedPatternOptions: ModelDecorationOptions; private deletedOptions: ModelDecorationOptions; - private decorations: string[] = []; + private decorationsCollection: IEditorDecorationsCollection | undefined; private editorModel: ITextModel | null; constructor( editorModel: ITextModel, + private readonly codeEditor: ICodeEditor, private model: DirtyDiffModel, @IConfigurationService private readonly configurationService: IConfigurationService ) { @@ -1176,18 +1175,22 @@ class DirtyDiffDecorator extends Disposable { } }); - this.decorations = this.editorModel.deltaDecorations(this.decorations, decorations); + if (!this.decorationsCollection) { + this.decorationsCollection = this.codeEditor.createDecorationsCollection(decorations); + } else { + this.decorationsCollection.set(decorations); + } } override dispose(): void { super.dispose(); - if (this.editorModel && !this.editorModel.isDisposed()) { - this.editorModel.deltaDecorations(this.decorations, []); + if (this.decorationsCollection) { + this.decorationsCollection?.clear(); } this.editorModel = null; - this.decorations = []; + this.decorationsCollection = undefined; } } @@ -1543,7 +1546,7 @@ export class DirtyDiffWorkbenchController extends Disposable implements ext.IWor private enabled = false; private viewState: IViewState = { width: 3, visibility: 'always' }; - private items = new ResourceMap(); + private items = new ResourceMap>(); // resource -> editor id -> DirtyDiffItem private readonly transientDisposables = this._register(new DisposableStore()); private stylesheet: HTMLStyleElement; @@ -1636,7 +1639,7 @@ export class DirtyDiffWorkbenchController extends Disposable implements ext.IWor this.transientDisposables.clear(); for (const [, dirtyDiff] of this.items) { - dirtyDiff.dispose(); + dispose(dirtyDiff.values()); } this.items.clear(); @@ -1653,28 +1656,33 @@ export class DirtyDiffWorkbenchController extends Disposable implements ext.IWor controller.modelRegistry = this; } - if (textModel && !this.items.has(textModel.uri)) { + if (textModel && (!this.items.has(textModel.uri) || !this.items.get(textModel.uri)!.has(editor.getId()))) { const textFileModel = this.textFileService.files.get(textModel.uri); if (textFileModel?.isResolved()) { const dirtyDiffModel = this.instantiationService.createInstance(DirtyDiffModel, textFileModel); - const decorator = new DirtyDiffDecorator(textFileModel.textEditorModel, dirtyDiffModel, this.configurationService); - this.items.set(textModel.uri, new DirtyDiffItem(dirtyDiffModel, decorator)); + const decorator = new DirtyDiffDecorator(textFileModel.textEditorModel, editor, dirtyDiffModel, this.configurationService); + if (!this.items.has(textModel.uri)) { + this.items.set(textModel.uri, new Map()); + } + this.items.get(textModel.uri)?.set(editor.getId(), new DirtyDiffItem(dirtyDiffModel, decorator)); } } } } for (const [uri, item] of this.items) { - if (!this.editorService.isOpened({ resource: uri, typeId: FILE_EDITOR_INPUT_ID, editorId: DEFAULT_EDITOR_ASSOCIATION.id })) { - item.dispose(); - this.items.delete(uri); + for (const editorId of item.keys()) { + if (!this.editorService.visibleTextEditorControls.find(editor => isCodeEditor(editor) && editor.getModel()?.uri.toString() === uri.toString() && editor.getId() === editorId)) { + dispose(item.values()); + this.items.delete(uri); + } } } } - getModel(editorModel: ITextModel): DirtyDiffModel | undefined { - return this.items.get(editorModel.uri)?.model; + getModel(editorModel: ITextModel, codeEditor: ICodeEditor): DirtyDiffModel | undefined { + return this.items.get(editorModel.uri)?.get(codeEditor.getId())?.model; } override dispose(): void { diff --git a/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts index ab242246b6e7f4..b27c637f0d3f2b 100644 --- a/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts @@ -14,7 +14,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -476,6 +476,7 @@ export class SCMSyncViewPane extends ViewPane { compressionEnabled: true, horizontalScrolling: false, autoExpandSingleChildren: true, + multipleSelectionSupport: false, collapseByDefault: (e) => !ResourceTree.isResourceNode(e), accessibilityProvider: this.instantiationService.createInstance(SCMSyncViewPaneAccessibilityProvider), identityProvider: this.instantiationService.createInstance(SCMSyncViewPaneTreeIdentityProvider), @@ -832,6 +833,7 @@ class RefreshAction extends ViewAction { icon: Codicon.refresh, menu: { id: MenuId.ViewTitle, + when: ContextKeyExpr.equals('view', SYNC_VIEW_PANE_ID), group: 'navigation' } }); @@ -854,6 +856,7 @@ class SetListViewModeAction extends ViewAction { toggled: ContextKeys.ViewMode.isEqualTo(ViewMode.List), menu: { id: MenuId.ViewTitle, + when: ContextKeyExpr.equals('view', SYNC_VIEW_PANE_ID), group: '1_viewmode' } }); @@ -876,6 +879,7 @@ class SetTreeViewModeAction extends ViewAction { toggled: ContextKeys.ViewMode.isEqualTo(ViewMode.Tree), menu: { id: MenuId.ViewTitle, + when: ContextKeyExpr.equals('view', SYNC_VIEW_PANE_ID), group: '1_viewmode' } }); diff --git a/src/vs/workbench/contrib/scm/common/quickDiffService.ts b/src/vs/workbench/contrib/scm/common/quickDiffService.ts index 50e26ed2907353..137913c91b32a5 100644 --- a/src/vs/workbench/contrib/scm/common/quickDiffService.ts +++ b/src/vs/workbench/contrib/scm/common/quickDiffService.ts @@ -43,10 +43,6 @@ export class QuickDiffService extends Disposable implements IQuickDiffService { private readonly _onDidChangeQuickDiffProviders = this._register(new Emitter()); readonly onDidChangeQuickDiffProviders = this._onDidChangeQuickDiffProviders.event; - // It is common to get many requests for the same resource back to back (ex. when editing a file) - // Cache the last resource so to avoid unneeded extension host round trips. - private cachedOriginalResource: { uri: URI; resources: Map } | undefined; - constructor(@IUriIdentityService private readonly uriIdentityService: IUriIdentityService) { super(); } @@ -66,19 +62,6 @@ export class QuickDiffService extends Disposable implements IQuickDiffService { return !!diff.originalResource && (typeof diff.label === 'string') && (typeof diff.isSCM === 'boolean'); } - private getOriginalResourceFromCache(provider: string, uri: URI): URI | undefined { - if (this.cachedOriginalResource?.uri.toString() === uri.toString()) { - return this.cachedOriginalResource.resources.get(provider); - } - return undefined; - } - - private updateOriginalResourceCache(uri: URI, quickDiffs: QuickDiff[]) { - if (this.cachedOriginalResource?.uri.toString() !== uri.toString()) { - this.cachedOriginalResource = { uri, resources: new Map(quickDiffs.map(diff => ([diff.label, diff.originalResource]))) }; - } - } - async getQuickDiffs(uri: URI, language: string = '', isSynchronized: boolean = false): Promise { const providers = Array.from(this.quickDiffProviders) .filter(provider => !provider.rootUri || this.uriIdentityService.extUri.isEqualOrParent(uri, provider.rootUri)) @@ -87,14 +70,12 @@ export class QuickDiffService extends Disposable implements IQuickDiffService { const diffs = await Promise.all(providers.map(async provider => { const scoreValue = provider.selector ? score(provider.selector, uri, language, isSynchronized, undefined, undefined) : 10; const diff: Partial = { - originalResource: scoreValue > 0 ? (this.getOriginalResourceFromCache(provider.label, uri) ?? await provider.getOriginalResource(uri) ?? undefined) : undefined, + originalResource: scoreValue > 0 ? await provider.getOriginalResource(uri) ?? undefined : undefined, label: provider.label, isSCM: provider.isSCM }; return diff; })); - const quickDiffs = diffs.filter(this.isQuickDiff); - this.updateOriginalResourceCache(uri, quickDiffs); - return quickDiffs; + return diffs.filter(this.isQuickDiff); } } diff --git a/src/vs/workbench/contrib/search/browser/searchActionsTextQuickAccess.ts b/src/vs/workbench/contrib/search/browser/searchActionsTextQuickAccess.ts index 8306eb4af7c20a..977c05f532eaed 100644 --- a/src/vs/workbench/contrib/search/browser/searchActionsTextQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/searchActionsTextQuickAccess.ts @@ -34,7 +34,7 @@ registerAction2(class TextSearchQuickAccessAction extends Action2 { override async run(accessor: ServicesAccessor, match: RenderableMatch | undefined): Promise { const quickInputService = accessor.get(IQuickInputService); const searchText = getSearchText(accessor) ?? ''; - quickInputService.quickAccess.show(TEXT_SEARCH_QUICK_ACCESS_PREFIX + searchText); + quickInputService.quickAccess.show(TEXT_SEARCH_QUICK_ACCESS_PREFIX + searchText, { preserveValue: !!searchText }); } }); diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index cffbd00a060327..f7716d10ba3578 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -297,6 +297,13 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this._outputChannel.clear(); } + if (e.affectsConfiguration(TaskSettingId.Reconnection)) { + if (!this._configurationService.getValue(TaskSettingId.Reconnection)) { + this._persistentTasks?.clear(); + this._storageService.remove(AbstractTaskService.PersistentTasks_Key, StorageScope.WORKSPACE); + } + } + this._setTaskLRUCacheLimit(); return this._updateWorkspaceTasks(TaskRunSource.ConfigurationChange); })); diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh index 4d1479ca9f50a9..687b9ee025a486 100755 --- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh +++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh @@ -47,7 +47,7 @@ fi # Apply EnvironmentVariableCollections if needed if [ -n "${VSCODE_ENV_REPLACE:-}" ]; then - IFS=':' read -ra ADDR <<<"$VSCODE_ENV_REPLACE" + IFS=':' read -ra ADDR <<< "$VSCODE_ENV_REPLACE" for ITEM in "${ADDR[@]}"; do VARNAME="$(echo $ITEM | cut -d "=" -f 1)" VALUE="$(echo -e "$ITEM" | cut -d "=" -f 2)" @@ -56,7 +56,7 @@ if [ -n "${VSCODE_ENV_REPLACE:-}" ]; then builtin unset VSCODE_ENV_REPLACE fi if [ -n "${VSCODE_ENV_PREPEND:-}" ]; then - IFS=':' read -ra ADDR <<<"$VSCODE_ENV_PREPEND" + IFS=':' read -ra ADDR <<< "$VSCODE_ENV_PREPEND" for ITEM in "${ADDR[@]}"; do VARNAME="$(echo $ITEM | cut -d "=" -f 1)" VALUE="$(echo -e "$ITEM" | cut -d "=" -f 2)" @@ -65,7 +65,7 @@ if [ -n "${VSCODE_ENV_PREPEND:-}" ]; then builtin unset VSCODE_ENV_PREPEND fi if [ -n "${VSCODE_ENV_APPEND:-}" ]; then - IFS=':' read -ra ADDR <<<"$VSCODE_ENV_APPEND" + IFS=':' read -ra ADDR <<< "$VSCODE_ENV_APPEND" for ITEM in "${ADDR[@]}"; do VARNAME="$(echo $ITEM | cut -d "=" -f 1)" VALUE="$(echo -e "$ITEM" | cut -d "=" -f 2)" @@ -95,52 +95,13 @@ __vsc_get_trap() { builtin printf '%s' "${terms[2]:-}" } -__vsc_command_available() { - builtin local trash - trash=$(builtin command -v "$1" 2>&1) - builtin return $? -} - -# We provide two faster escaping functions here. -# The first one escapes each byte 0xab into '\xab', which is most scalable and has promising runtime -# efficiency, except that it relies on external commands od and tr. -# The second one is much faster and has zero dependency, except that it escapes only -# '\\' -> '\\\\' and ';' -> '\x3b' and scales up badly when more patterns are needed. -# We default to use the first function if od and tr are available, and fallback to the second otherwise. -if __vsc_command_available od && __vsc_command_available tr; then - __vsc_escape_value_fast() { - builtin local out - # -An removes line number - # -v do not use * to mark line suppression - # -tx1 prints each byte as two-digit hex - # tr -d '\n' concats all output lines - out=$(od -An -vtx1 <<<"$1" | tr -d '\n') - out=${out// /\\x} - # <<<"$1" prepends a trailing newline already, so we don't need to printf '%s\n' - builtin printf '%s' "${out}" - } -else - __vsc_escape_value_fast() { - builtin local LC_ALL=C out - out=${1//\\/\\\\} - out=${out//;/\\x3b} - builtin printf '%s\n' "${out}" - } -fi - # The property (P) and command (E) codes embed values which require escaping. # Backslashes are doubled. Non-alphanumeric characters are converted to escaped hex. __vsc_escape_value() { - # If the input being too large, switch to the faster function - if [ "${#1}" -ge 2000 ]; then - __vsc_escape_value_fast "$1" - builtin return - fi - # Process text byte by byte, not by codepoint. builtin local LC_ALL=C str="${1}" i byte token out='' - for ((i = 0; i < "${#str}"; ++i)); do + for (( i=0; i < "${#str}"; ++i )); do byte="${str:$i:1}" # Escape backslashes and semi-colons diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 9daee84c55e35f..0cca842ad38332 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -61,7 +61,7 @@ import { TerminalCapability } from 'vs/platform/terminal/common/capabilities/cap import { killTerminalIcon, newTerminalIcon } from 'vs/workbench/contrib/terminal/browser/terminalIcons'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { Iterable } from 'vs/base/common/iterator'; -import { AccessibleViewProviderId, accessibleViewCurrentProviderId, accessibleViewOnLastLine } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibleViewProviderId, accessibleViewCurrentProviderId, accessibleViewIsShown, accessibleViewOnLastLine } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; export const switchTerminalActionViewItemSeparator = '\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500'; export const switchTerminalShowTabsTitle = localize('showTerminalTabs', "Show Tabs"); @@ -365,7 +365,7 @@ export function registerTerminalActions() { keybinding: [ { primary: KeyMod.CtrlCmd | KeyCode.KeyR, - when: ContextKeyExpr.and(TerminalContextKeys.focus, CONTEXT_ACCESSIBILITY_MODE_ENABLED), + when: ContextKeyExpr.and(CONTEXT_ACCESSIBILITY_MODE_ENABLED, ContextKeyExpr.or(TerminalContextKeys.focus, ContextKeyExpr.and(accessibleViewIsShown, accessibleViewCurrentProviderId.isEqualTo(AccessibleViewProviderId.Terminal)))), weight: KeybindingWeight.WorkbenchContrib }, { @@ -1345,7 +1345,7 @@ export function registerTerminalActions() { weight: KeybindingWeight.WorkbenchContrib + 1, // Disable the keybinding when accessibility mode is enabled as chords include // important screen reader keybindings such as cmd+k, cmd+i to show the hover - when: ContextKeyExpr.and(TerminalContextKeys.focus, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), + when: ContextKeyExpr.or(ContextKeyExpr.and(TerminalContextKeys.focus, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), ContextKeyExpr.and(CONTEXT_ACCESSIBILITY_MODE_ENABLED, accessibleViewIsShown, accessibleViewCurrentProviderId.isEqualTo(AccessibleViewProviderId.Terminal))), }], run: (activeInstance) => activeInstance.clearBuffer() }); diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts index f4cd2e45dc5792..ba012fd2ee0a94 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts @@ -23,7 +23,7 @@ import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/wid import { ITerminalProcessManager, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { BufferContentTracker } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/bufferContentTracker'; -import { TerminalAccessibleContentProvider } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp'; +import { TerminalAccessibilityHelpProvider } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp'; import { TextAreaSyncAddon } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/textAreaSyncAddon'; import type { Terminal } from 'xterm'; import { Position } from 'vs/editor/common/core/position'; @@ -119,7 +119,9 @@ export class TerminalAccessibleViewContribution extends Disposable implements IT this._bufferTracker = this._register(this._instantiationService.createInstance(BufferContentTracker, this._xterm)); } if (!this._bufferProvider) { - this._bufferProvider = this._register(this._instantiationService.createInstance(TerminalAccessibleBufferProvider, this._instance, this._bufferTracker)); + this._bufferProvider = this._register(this._instantiationService.createInstance(TerminalAccessibleBufferProvider, this._instance, this._bufferTracker, () => { + return this._register(this._instantiationService.createInstance(TerminalAccessibilityHelpProvider, this._instance, this._xterm!)).provideContent(); + })); } this._accessibleViewService.show(this._bufferProvider); } @@ -199,7 +201,7 @@ export class TerminalAccessibilityHelpContribution extends Disposable { if (!terminal) { return; } - accessibleViewService.show(instantiationService.createInstance(TerminalAccessibleContentProvider, instance, terminal)); + accessibleViewService.show(instantiationService.createInstance(TerminalAccessibilityHelpProvider, instance, terminal)); }, ContextKeyExpr.or(TerminalContextKeys.focus, ContextKeyExpr.and(accessibleViewIsShown, ContextKeyExpr.equals(accessibleViewCurrentProviderId.key, AccessibleViewProviderId.Terminal))))); } } diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts index ec61041471bb99..dfca880887bfe4 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts @@ -24,12 +24,11 @@ export const enum ClassName { EditorTextArea = 'textarea' } -export class TerminalAccessibleContentProvider extends Disposable implements IAccessibleContentProvider { - +export class TerminalAccessibilityHelpProvider extends Disposable implements IAccessibleContentProvider { + id = AccessibleViewProviderId.TerminalHelp; private readonly _hasShellIntegration: boolean = false; - onClose() { - const expr = ContextKeyExpr.and(accessibleViewIsShown, ContextKeyExpr.equals(accessibleViewCurrentProviderId.key, AccessibleViewProviderId.Terminal)); + const expr = ContextKeyExpr.and(accessibleViewIsShown, ContextKeyExpr.equals(accessibleViewCurrentProviderId.key, AccessibleViewProviderId.TerminalHelp)); if (expr?.evaluate(this._contextKeyService.getContext(null))) { this._commandService.executeCommand(TerminalCommandId.FocusAccessibleBuffer); } else { @@ -83,8 +82,8 @@ export class TerminalAccessibleContentProvider extends Disposable implements IAc if (this._hasShellIntegration) { const shellIntegrationCommandList = []; shellIntegrationCommandList.push(localize('shellIntegration', "The terminal has a feature called shell integration that offers an enhanced experience and provides useful commands for screen readers such as:")); - shellIntegrationCommandList.push('- ' + this._descriptionForCommand(TerminalCommandId.AccessibleBufferGoToNextCommand, localize('goToNextCommand', 'Go to Next Command ({0})'), localize('goToNextCommandNoKb', 'Go to Next Command is currently not triggerable by a keybinding.'))); - shellIntegrationCommandList.push('- ' + this._descriptionForCommand(TerminalCommandId.AccessibleBufferGoToPreviousCommand, localize('goToPreviousCommand', 'Go to Previous Command ({0})'), localize('goToPreviousCommandNoKb', 'Go to Previous Command is currently not triggerable by a keybinding.'))); + shellIntegrationCommandList.push('- ' + this._descriptionForCommand(TerminalCommandId.AccessibleBufferGoToNextCommand, localize('goToNextCommand', 'Go to Next Command ({0}) in the accessible view'), localize('goToNextCommandNoKb', 'Go to Next Command in the accessible view is currently not triggerable by a keybinding.'))); + shellIntegrationCommandList.push('- ' + this._descriptionForCommand(TerminalCommandId.AccessibleBufferGoToPreviousCommand, localize('goToPreviousCommand', 'Go to Previous Command ({0}) in the accessible view'), localize('goToPreviousCommandNoKb', 'Go to Previous Command in the accessible view is currently not triggerable by a keybinding.'))); shellIntegrationCommandList.push('- ' + this._descriptionForCommand(AccessibilityCommandId.GoToSymbol, localize('goToSymbol', 'Go to Symbol ({0})'), localize('goToSymbolNoKb', 'Go to symbol is currently not triggerable by a keybinding.'))); shellIntegrationCommandList.push('- ' + this._descriptionForCommand(TerminalCommandId.RunRecentCommand, localize('runRecentCommand', 'Run Recent Command ({0})'), localize('runRecentCommandNoKb', 'Run Recent Command is currently not triggerable by a keybinding.'))); shellIntegrationCommandList.push('- ' + this._descriptionForCommand(TerminalCommandId.GoToRecentDirectory, localize('goToRecentDirectory', 'Go to Recent Directory ({0})'), localize('goToRecentDirectoryNoKb', 'Go to Recent Directory is currently not triggerable by a keybinding.'))); diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts index 7e2a63b37596e2..7f34a9cd5708dd 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts @@ -9,35 +9,31 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { TerminalCapability, ITerminalCommand } from 'vs/platform/terminal/common/capabilities/capabilities'; import { ICurrentPartialCommand } from 'vs/platform/terminal/common/capabilities/commandDetectionCapability'; -import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { AccessibleViewType, IAccessibleContentProvider, IAccessibleViewOptions, IAccessibleViewSymbol } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; -import { IXtermTerminal, ITerminalInstance, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalInstance, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { BufferContentTracker } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/bufferContentTracker'; -import type { Terminal } from 'xterm'; export class TerminalAccessibleBufferProvider extends DisposableStore implements IAccessibleContentProvider { + id = AccessibleViewProviderId.Terminal; options: IAccessibleViewOptions = { type: AccessibleViewType.View, language: 'terminal', positionBottom: true }; verbositySettingKey = AccessibilityVerbositySettingId.Terminal; - private _xterm: IXtermTerminal & { raw: Terminal } | undefined; constructor( private readonly _instance: Pick, private _bufferTracker: BufferContentTracker, + customHelp: () => string, @IModelService _modelService: IModelService, @IConfigurationService _configurationService: IConfigurationService, @IContextKeyService _contextKeyService: IContextKeyService, @ITerminalService _terminalService: ITerminalService ) { super(); + this.options.customHelp = customHelp; } onClose() { this._instance.focus(); } - registerListeners(): void { - if (!this._xterm) { - return; - } - } provideContent(): string { this._bufferTracker.update(); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 215e53b2f9b3eb..1daf515ff3b2aa 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -17,9 +17,8 @@ import { ITextModelContentProvider, ITextModelService } from 'vs/editor/common/s import { localize } from 'vs/nls'; import { MenuId, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr, ContextKeyTrueExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { QuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; @@ -43,7 +42,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ViewContainerLocation, IViewContainersRegistry, Extensions, ViewContainer } from 'vs/workbench/common/views'; import { UserDataSyncDataViews } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncViews'; -import { IUserDataSyncWorkbenchService, getSyncAreaLabel, AccountStatus, CONTEXT_SYNC_STATE, CONTEXT_SYNC_ENABLEMENT, CONTEXT_ACCOUNT_STATE, CONFIGURE_SYNC_COMMAND_ID, SHOW_SYNC_LOG_COMMAND_ID, SYNC_VIEW_CONTAINER_ID, SYNC_TITLE, SYNC_ORIGINAL_TITLE, SYNC_VIEW_ICON, CONTEXT_HAS_CONFLICTS } from 'vs/workbench/services/userDataSync/common/userDataSync'; +import { IUserDataSyncWorkbenchService, getSyncAreaLabel, AccountStatus, CONTEXT_SYNC_STATE, CONTEXT_SYNC_ENABLEMENT, CONTEXT_ACCOUNT_STATE, CONFIGURE_SYNC_COMMAND_ID, SHOW_SYNC_LOG_COMMAND_ID, SYNC_VIEW_CONTAINER_ID, SYNC_TITLE, SYNC_ORIGINAL_TITLE, SYNC_VIEW_ICON, CONTEXT_HAS_CONFLICTS, DOWNLOAD_ACTIVITY_ACTION_DESCRIPTOR } from 'vs/workbench/services/userDataSync/common/userDataSync'; import { Codicon } from 'vs/base/common/codicons'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; @@ -54,11 +53,7 @@ import { ctxIsMergeResultEditor, ctxMergeBaseUri } from 'vs/workbench/contrib/me import { IWorkbenchIssueService } from 'vs/workbench/services/issue/common/issue'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { ILocalizedString } from 'vs/platform/action/common/action'; -import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; -import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { IFileService } from 'vs/platform/files/common/files'; -import { escapeRegExpCharacters } from 'vs/base/common/strings'; -import { IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines'; +import { isWeb } from 'vs/base/common/platform'; type ConfigureSyncQuickPickItem = { id: SyncResource; label: string; description?: string }; @@ -119,7 +114,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo @IOpenerService private readonly openerService: IOpenerService, @IAuthenticationService private readonly authenticationService: IAuthenticationService, @IUserDataSyncStoreManagementService private readonly userDataSyncStoreManagementService: IUserDataSyncStoreManagementService, - @IConfigurationService private readonly configurationService: IConfigurationService, @IHostService private readonly hostService: IHostService, @ICommandService private readonly commandService: ICommandService, @IWorkbenchIssueService private readonly workbenchIssueService: IWorkbenchIssueService @@ -528,8 +522,8 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo quickPick.title = SYNC_TITLE; quickPick.ok = false; quickPick.customButton = true; - quickPick.customLabel = localize('sign in and turn on', "Sign in & Turn on"); - quickPick.description = localize('configure and turn on sync detail', "Please sign in to synchronize your data across devices."); + quickPick.customLabel = localize('sign in and turn on', "Sign in"); + quickPick.description = localize('configure and turn on sync detail', "Please sign in to backup and sync your data across devices."); quickPick.canSelectMany = true; quickPick.ignoreFocusOut = true; quickPick.hideInput = true; @@ -566,7 +560,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo }, { id: SyncResource.Keybindings, label: getSyncAreaLabel(SyncResource.Keybindings), - description: this.configurationService.getValue('settingsSync.keybindingsPerPlatform') ? localize('per platform', "for each platform") : undefined }, { id: SyncResource.Snippets, label: getSyncAreaLabel(SyncResource.Snippets) @@ -717,7 +710,10 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo this.registerShowLogAction(); this.registerResetSyncDataAction(); this.registerAcceptMergesAction(); - this.registerDownloadSyncActivityAction(); + + if (isWeb) { + this.registerDownloadSyncActivityAction(); + } } private registerTurnOnSyncAction(): void { @@ -1134,60 +1130,15 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private registerDownloadSyncActivityAction(): void { this._register(registerAction2(class DownloadSyncActivityAction extends Action2 { constructor() { - super({ - id: 'workbench.userDataSync.actions.downloadSyncActivity', - title: { original: 'Download Settings Sync Activity', value: localize('download sync activity title', "Download Settings Sync Activity") }, - category: Categories.Developer, - f1: true, - precondition: ContextKeyExpr.and(CONTEXT_ACCOUNT_STATE.isEqualTo(AccountStatus.Available), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)) - }); + super(DOWNLOAD_ACTIVITY_ACTION_DESCRIPTOR); } - async run(accessor: ServicesAccessor): Promise { const userDataSyncWorkbenchService = accessor.get(IUserDataSyncWorkbenchService); - const fileDialogService = accessor.get(IFileDialogService); - const progressService = accessor.get(IProgressService); - const uriIdentityService = accessor.get(IUriIdentityService); - const fileService = accessor.get(IFileService); - const userDataSyncMachinesService = accessor.get(IUserDataSyncMachinesService); const notificationService = accessor.get(INotificationService); - - const result = await fileDialogService.showOpenDialog({ - title: localize('download sync activity dialog title', "Select folder to download Settings Sync activity"), - canSelectFiles: false, - canSelectFolders: true, - canSelectMany: false, - openLabel: localize('download sync activity dialog open label', "Save"), - }); - - if (!result?.[0]) { - return; + const folder = await userDataSyncWorkbenchService.downloadSyncActivity(); + if (folder) { + notificationService.info(localize('download sync activity complete', "Successfully downloaded Settings Sync activity.")); } - - await progressService.withProgress({ location: ProgressLocation.Window }, async () => { - const machines = await userDataSyncMachinesService.getMachines(); - const currentMachine = machines.find(m => m.isCurrent); - const name = (currentMachine ? currentMachine.name + ' - ' : '') + 'Settings Sync Activity'; - const stat = await fileService.resolve(result[0]); - - const nameRegEx = new RegExp(`${escapeRegExpCharacters(name)}\\s(\\d+)`); - const indexes: number[] = []; - for (const child of stat.children ?? []) { - if (child.name === name) { - indexes.push(0); - } else { - const matches = nameRegEx.exec(child.name); - if (matches) { - indexes.push(parseInt(matches[1])); - } - } - } - indexes.sort((a, b) => a - b); - - return userDataSyncWorkbenchService.downloadSyncActivity(uriIdentityService.extUri.joinPath(result[0], indexes[0] !== 0 ? name : `${name} ${indexes[indexes.length - 1] + 1}`)); - }); - - notificationService.info(localize('download sync activity complete', "Successfully downloaded Settings Sync activity.")); } })); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts index c4324bba246974..7f9b4686982f20 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncViews.ts @@ -193,8 +193,8 @@ export class UserDataSyncDataViews extends Disposable { this._register(registerAction2(class extends Action2 { constructor() { super({ - id: `workbench.actions.sync.extractActivity`, - title: localize('workbench.actions.sync.extractActivity', "Extract Sync Activity"), + id: `workbench.actions.sync.loadActivity`, + title: localize('workbench.actions.sync.loadActivity', "Load Sync Activity"), icon: Codicon.cloudUpload, menu: { id: MenuId.ViewTitle, diff --git a/src/vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution.ts b/src/vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution.ts index abbf3cfc88268e..7d757dc69b2fe2 100644 --- a/src/vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution.ts +++ b/src/vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution.ts @@ -15,8 +15,8 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { INativeHostService } from 'vs/platform/native/common/native'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { CONTEXT_SYNC_STATE, SYNC_TITLE } from 'vs/workbench/services/userDataSync/common/userDataSync'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { CONTEXT_SYNC_STATE, DOWNLOAD_ACTIVITY_ACTION_DESCRIPTOR, IUserDataSyncWorkbenchService, SYNC_TITLE } from 'vs/workbench/services/userDataSync/common/userDataSync'; import { Schemas } from 'vs/base/common/network'; class UserDataSyncServicesContribution implements IWorkbenchContribution { @@ -58,3 +58,23 @@ registerAction2(class OpenSyncBackupsFolder extends Action2 { } } }); + +registerAction2(class DownloadSyncActivityAction extends Action2 { + constructor() { + super(DOWNLOAD_ACTIVITY_ACTION_DESCRIPTOR); + } + + async run(accessor: ServicesAccessor): Promise { + const userDataSyncWorkbenchService = accessor.get(IUserDataSyncWorkbenchService); + const notificationService = accessor.get(INotificationService); + const hostService = accessor.get(INativeHostService); + const folder = await userDataSyncWorkbenchService.downloadSyncActivity(); + if (folder) { + notificationService.prompt(Severity.Info, localize('download sync activity complete', "Successfully downloaded Settings Sync activity."), + [{ + label: localize('open', "Open Folder"), + run: () => hostService.showItemInFolder(folder.fsPath) + }]); + } + } +}); diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index fda561a69e581e..b1c44b5f12c781 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -220,7 +220,7 @@ import { applicationConfigurationNodeBase } from 'vs/workbench/common/configurat 'enum': ['native', 'custom'], 'default': isLinux ? 'native' : 'custom', 'scope': ConfigurationScope.APPLICATION, - 'description': localize('titleBarStyle', "Adjust the appearance of the window title bar. On Linux and Windows, this setting also affects the application and context menu appearances. Changes require a full restart to apply.") + 'description': localize('titleBarStyle', "Adjust the appearance of the window title bar to be native by the OS or custom. On Linux and Windows, this setting also affects the application and context menu appearances. Changes require a full restart to apply.") }, 'window.dialogStyle': { 'type': 'string', diff --git a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts index 9d3f6a883d2957..1c6eae285bcacd 100644 --- a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts +++ b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts @@ -828,6 +828,9 @@ export class SimpleFileDialog implements ISimpleFileDialog { } else if (!statDirname.isDirectory) { this.filePickBox.validationMessage = nls.localize('remoteFileDialog.validateNonexistentDir', 'Please enter a path that exists.'); return Promise.resolve(false); + } else if (statDirname.readonly || statDirname.locked) { + this.filePickBox.validationMessage = nls.localize('remoteFileDialog.validateReadonlyFolder', 'This folder cannot be used as a save destination. Please choose another folder'); + return Promise.resolve(false); } } else { // open if (!stat) { diff --git a/src/vs/workbench/services/extensionManagement/browser/extensionUrlTrustService.ts b/src/vs/workbench/services/extensionManagement/browser/extensionUrlTrustService.ts deleted file mode 100644 index 31127de9b07c35..00000000000000 --- a/src/vs/workbench/services/extensionManagement/browser/extensionUrlTrustService.ts +++ /dev/null @@ -1,18 +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 { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/common/extensionUrlTrust'; -import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; - -class ExtensionUrlTrustService implements IExtensionUrlTrustService { - - declare readonly _serviceBrand: undefined; - - async isExtensionUrlTrusted(): Promise { - return false; - } -} - -registerSingleton(IExtensionUrlTrustService, ExtensionUrlTrustService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionUrlTrustService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionUrlTrustService.ts deleted file mode 100644 index 3fa5f8a599aa6c..00000000000000 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionUrlTrustService.ts +++ /dev/null @@ -1,9 +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 { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/common/extensionUrlTrust'; -import { registerMainProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services'; - -registerMainProcessRemoteService(IExtensionUrlTrustService, 'extensionUrlTrust'); diff --git a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts index 89afd43ec702a1..9da3a6e5c587c0 100644 --- a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts +++ b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts @@ -26,10 +26,10 @@ import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/act import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; -import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/common/extensionUrlTrust'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IProductService } from 'vs/platform/product/common/productService'; const FIVE_MINUTES = 5 * 60 * 1000; const THIRTY_SECONDS = 30 * 1000; @@ -121,7 +121,7 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { @IConfigurationService private readonly configurationService: IConfigurationService, @IProgressService private readonly progressService: IProgressService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IExtensionUrlTrustService private readonly extensionUrlTrustService: IExtensionUrlTrustService + @IProductService private readonly productService: IProductService ) { this.userTrustedExtensionsStorage = new UserTrustedExtensionIdStorage(storageService); @@ -159,14 +159,14 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { await this.handleUnhandledURL(uri, { id: extensionId }, options); return true; } else { - extensionDisplayName = extension.displayName || extension.name; + extensionDisplayName = extension.displayName ?? ''; } } else { extensionDisplayName = initialHandler.extensionDisplayName; } const trusted = options?.trusted - || (options?.originalUrl ? await this.extensionUrlTrustService.isExtensionUrlTrusted(extensionId, options.originalUrl) : false) + || this.productService.trustedExtensionProtocolHandlers?.includes(extensionId) || this.didUserTrustExtension(ExtensionIdentifier.toKey(extensionId)); if (!trusted) { @@ -177,11 +177,11 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { } const result = await this.dialogService.confirm({ - message: localize('confirmUrl', "Allow an extension to open this URI?", extensionId), + message: localize('confirmUrl', "Allow '{0}' extension to open this URI?", extensionDisplayName), checkbox: { label: localize('rememberConfirmUrl', "Don't ask again for this extension."), }, - detail: `${extensionDisplayName} (${extensionId}) wants to open a URI:\n\n${uriString}`, + detail: uriString, primaryButton: localize({ key: 'open', comment: ['&& denotes a mnemonic'] }, "&&Open") }); @@ -267,8 +267,8 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { // Install the Extension and reload the window to handle. const result = await this.dialogService.confirm({ - message: localize('installAndHandle', "Extension '{0}' is not installed. Would you like to install the extension and open this URL?", galleryExtension.displayName || galleryExtension.name), - detail: `${galleryExtension.displayName || galleryExtension.name} (${extensionIdentifier.id}) wants to open a URL:\n\n${uri.toString()}`, + message: localize('installAndHandle', "Would you like to install '{0}' extension from '{1}' to open this URI?", galleryExtension.displayName, galleryExtension.publisherDisplayName), + detail: `${localize('installDetail', "'{0}' extension wants to open a URI:", galleryExtension.displayName)}\n\n${uri.toString()}`, primaryButton: localize({ key: 'install and open', comment: ['&& denotes a mnemonic'] }, "&&Install and Open") }); diff --git a/src/vs/workbench/services/preferences/common/preferences.ts b/src/vs/workbench/services/preferences/common/preferences.ts index 8fc8941c33d823..a4718b7437c714 100644 --- a/src/vs/workbench/services/preferences/common/preferences.ts +++ b/src/vs/workbench/services/preferences/common/preferences.ts @@ -93,15 +93,15 @@ export interface ISetting { editPresentation?: EditPresentationTypes; nonLanguageSpecificDefaultValueSource?: string | IExtensionInfo; isLanguageTagSetting?: boolean; - categoryOrder?: number; categoryLabel?: string; - // For ExtensionToggle settings + // Internal properties displayExtensionId?: string; stableExtensionId?: string; prereleaseExtensionId?: string; title?: string; extensionGroupTitle?: string; + internalOrder?: number; } export interface IExtensionSetting extends ISetting { diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index 070eab8f0b2dd9..512cca2548860f 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -634,7 +634,6 @@ export class DefaultSettings extends Disposable { // Try using the title if the category id wasn't given // (in which case the category id is the same as the extension id) const categoryLabel = config.extensionInfo?.id === config.id ? config.title : config.id; - const categoryOrder = config.order; for (const key in settingsObject) { const prop: IConfigurationPropertySchema = settingsObject[key]; @@ -721,8 +720,7 @@ export class DefaultSettings extends Disposable { order: prop.order, nonLanguageSpecificDefaultValueSource: defaultValueSource, isLanguageTagSetting, - categoryLabel, - categoryOrder + categoryLabel }); } } diff --git a/src/vs/workbench/services/telemetry/browser/telemetryService.ts b/src/vs/workbench/services/telemetry/browser/telemetryService.ts index dcd2356e566f0d..ac45112d126546 100644 --- a/src/vs/workbench/services/telemetry/browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/browser/telemetryService.ts @@ -14,7 +14,7 @@ import { ClassifiedEvent, IGDPRProperty, OmitMetadata, StrictPropertyCheck } fro import { ITelemetryData, ITelemetryService, TelemetryLevel, TELEMETRY_SETTING_ID } from 'vs/platform/telemetry/common/telemetry'; import { TelemetryLogAppender } from 'vs/platform/telemetry/common/telemetryLogAppender'; import { ITelemetryServiceConfig, TelemetryService as BaseTelemetryService } from 'vs/platform/telemetry/common/telemetryService'; -import { getTelemetryLevel, isInternalTelemetry, ITelemetryAppender, NullTelemetryService, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; +import { getTelemetryLevel, isInternalTelemetry, isLoggingOnly, ITelemetryAppender, NullTelemetryService, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { resolveWorkbenchCommonProperties } from 'vs/workbench/services/telemetry/browser/workbenchCommonProperties'; @@ -71,8 +71,17 @@ export class TelemetryService extends Disposable implements ITelemetryService { // If remote server is present send telemetry through that, else use the client side appender const appenders: ITelemetryAppender[] = []; const isInternal = isInternalTelemetry(productService, configurationService); - const telemetryProvider: ITelemetryAppender = remoteAgentService.getConnection() !== null ? { log: remoteAgentService.logTelemetry.bind(remoteAgentService), flush: remoteAgentService.flushTelemetry.bind(remoteAgentService) } : new OneDataSystemWebAppender(isInternal, 'monacoworkbench', null, productService.aiConfig?.ariaKey); - appenders.push(telemetryProvider); + if (!isLoggingOnly(productService, environmentService)) { + if (remoteAgentService.getConnection() !== null) { + const remoteTelemetryProvider = { + log: remoteAgentService.logTelemetry.bind(remoteAgentService), + flush: remoteAgentService.flushTelemetry.bind(remoteAgentService) + }; + appenders.push(remoteTelemetryProvider); + } else { + appenders.push(new OneDataSystemWebAppender(isInternal, 'monacoworkbench', null, productService.aiConfig?.ariaKey)); + } + } appenders.push(new TelemetryLogAppender(logService, loggerService, environmentService, productService)); const config: ITelemetryServiceConfig = { appenders, diff --git a/src/vs/workbench/services/themes/common/iconExtensionPoint.ts b/src/vs/workbench/services/themes/common/iconExtensionPoint.ts index 754974fd2e39af..23becff84285d6 100644 --- a/src/vs/workbench/services/themes/common/iconExtensionPoint.ts +++ b/src/vs/workbench/services/themes/common/iconExtensionPoint.ts @@ -97,9 +97,10 @@ export class IconExtensionPoint { if (typeof defaultIcon === 'string') { iconRegistry.registerIcon(id, { id: defaultIcon }, iconContribution.description); } else if (typeof defaultIcon === 'object' && typeof defaultIcon.fontPath === 'string' && typeof defaultIcon.fontCharacter === 'string') { - const format = extname(defaultIcon.fontPath).substring(1); - if (['woff', 'woff2', 'ttf'].indexOf(format) === -1) { - collector.warn(nls.localize('invalid.icons.default.fontPath.extension', "Expected `contributes.icons.default.fontPath` to have file extension 'woff', woff2' or 'ttf', is '{0}'.", format)); + const fileExt = extname(defaultIcon.fontPath).substring(1); + const format = formatMap[fileExt]; + if (!format) { + collector.warn(nls.localize('invalid.icons.default.fontPath.extension', "Expected `contributes.icons.default.fontPath` to have file extension 'woff', woff2' or 'ttf', is '{0}'.", fileExt)); return; } const extensionLocation = extension.description.extensionLocation; @@ -132,6 +133,12 @@ export class IconExtensionPoint { } } +const formatMap: Record = { + 'ttf': 'truetype', + 'woff': 'woff', + 'woff2': 'woff2' +}; + function getFontId(description: IExtensionDescription, fontPath: string) { return posix.join(description.identifier.value, fontPath); } diff --git a/src/vs/workbench/services/userDataProfile/browser/media/userDataProfileView.css b/src/vs/workbench/services/userDataProfile/browser/media/userDataProfileView.css index 568c085b01f00f..ea08aa254b2af2 100644 --- a/src/vs/workbench/services/userDataProfile/browser/media/userDataProfileView.css +++ b/src/vs/workbench/services/userDataProfile/browser/media/userDataProfileView.css @@ -91,7 +91,6 @@ display: flex; align-items: center; justify-content: space-between; - font-size: 12px; margin-bottom: 8px; } diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts index 7535eba9f953a3..c0c50006dc5856 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts @@ -62,7 +62,7 @@ import { Mutable, isUndefined } from 'vs/base/common/types'; import { Action, ActionRunner, IAction, IActionRunner } from 'vs/base/common/actions'; import { isWeb } from 'vs/base/common/platform'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; -import { Codicon } from 'vs/base/common/codicons'; +import { Codicon, getAllCodicons } from 'vs/base/common/codicons'; import { Barrier } from 'vs/base/common/async'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; @@ -82,7 +82,6 @@ import { KeyCode } from 'vs/base/common/keyCodes'; interface IUserDataProfileTemplate { readonly name: string; - readonly shortName?: string; readonly icon?: string; readonly settings?: string; readonly keybindings?: string; @@ -97,7 +96,6 @@ function isUserDataProfileTemplate(thing: unknown): thing is IUserDataProfileTem return !!(candidate && typeof candidate === 'object' && (candidate.name && typeof candidate.name === 'string') - && (isUndefined(candidate.shortName) || typeof candidate.shortName === 'string') && (isUndefined(candidate.icon) || typeof candidate.icon === 'string') && (isUndefined(candidate.settings) || typeof candidate.settings === 'string') && (isUndefined(candidate.globalState) || typeof candidate.globalState === 'string') @@ -340,6 +338,9 @@ export class UserDataProfileImportExportService extends Disposable implements IU if (isUserDataProfileTemplate(source) && source.icon) { icon = ThemeIcon.fromId(source.icon); } + if (icon.id !== DEFAULT_ICON.id && !ICONS.some(({ id }) => id === icon.id) && !getAllCodicons().some(({ id }) => id === icon.id)) { + icon = DEFAULT_ICON; + } let result: { name: string; items: ReadonlyArray; icon?: string | null } | undefined; disposables.add(Event.any(quickPick.onDidCustom, quickPick.onDidAccept)(() => { const name = quickPick.value.trim(); @@ -365,13 +366,14 @@ export class UserDataProfileImportExportService extends Disposable implements IU profileIconElement.role = 'button'; profileIconElement.ariaLabel = localize('select icon', "Icon: {0}", icon.id); const iconSelectBox = disposables.add(this.instantiationService.createInstance(WorkbenchIconSelectBox, { icons: ICONS, inputBoxStyles: defaultInputBoxStyles })); - const dimension = new DOM.Dimension(496, 260); + const dimension = new DOM.Dimension(486, 260); iconSelectBox.layout(dimension); let hoverWidget: IHoverWidget | undefined; const updateIcon = (updated: ThemeIcon | undefined) => { icon = updated ?? DEFAULT_ICON; profileIconElement.className = `profile-icon ${ThemeIcon.asClassName(icon)}`; + profileIconElement.ariaLabel = localize('select icon', "Icon: {0}", icon.id); }; disposables.add(iconSelectBox.onDidSelect(selectedIcon => { if (icon.id !== selectedIcon.id) { @@ -564,14 +566,14 @@ export class UserDataProfileImportExportService extends Disposable implements IU private async createFromProfile(profile: IUserDataProfile, name: string, options?: IUserDataProfileOptions): Promise { const userDataProfilesExportState = this.instantiationService.createInstance(UserDataProfileExportState, profile); try { - const profileTemplate = await userDataProfilesExportState.getProfileTemplate(name, undefined); + const profileTemplate = await userDataProfilesExportState.getProfileTemplate(name, options?.icon); await this.progressService.withProgress({ location: ProgressLocation.Notification, delay: 500, sticky: true, }, async progress => { const reportProgress = (message: string) => progress.report({ message: localize('create from profile', "Create Profile: {0}", message) }); - const profile = await this.doCreateProfile(profileTemplate, false, false, { useDefaultFlags: options?.useDefaultFlags }, reportProgress); + const profile = await this.doCreateProfile(profileTemplate, false, false, { useDefaultFlags: options?.useDefaultFlags, icon: options?.icon }, reportProgress); if (profile) { reportProgress(localize('progress extensions', "Applying Extensions...")); await this.instantiationService.createInstance(ExtensionsResource).copy(this.userDataProfileService.currentProfile, profile, false); @@ -912,7 +914,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU const profile = this.userDataProfilesService.profiles.find(p => p.name === profileName); if (profile) { if (temp) { - return this.userDataProfilesService.createNamedProfile(`${profileName} ${this.getProfileNameIndex(profileName)}`, { ...options, shortName: profileTemplate.shortName, transient: temp }); + return this.userDataProfilesService.createNamedProfile(`${profileName} ${this.getProfileNameIndex(profileName)}`, { ...options, transient: temp }); } enum ImportProfileChoice { @@ -963,7 +965,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU } return this.userDataProfilesService.createNamedProfile(name); } else { - return this.userDataProfilesService.createNamedProfile(profileName, { ...options, shortName: profileTemplate.shortName, transient: temp }); + return this.userDataProfilesService.createNamedProfile(profileName, { ...options, transient: temp }); } } @@ -1281,7 +1283,7 @@ abstract class UserDataProfileImportExportState extends Disposable implements IT return this.roots.some(root => this.isSelected(root)); } - async getProfileTemplate(name: string, shortName: string | undefined): Promise { + async getProfileTemplate(name: string, icon: string | undefined): Promise { const roots = await this.getRoots(); let settings: string | undefined; let keybindings: string | undefined; @@ -1310,7 +1312,7 @@ abstract class UserDataProfileImportExportState extends Disposable implements IT return { name, - shortName, + icon, settings, keybindings, tasks, @@ -1439,7 +1441,7 @@ class UserDataProfileExportState extends UserDataProfileImportExportState { } } - return super.getProfileTemplate(name, this.profile.shortName); + return super.getProfileTemplate(name, this.profile.icon); } } @@ -1527,7 +1529,7 @@ class UserDataProfileImportState extends UserDataProfileImportExportState { } async getProfileTemplateToImport(): Promise { - return this.getProfileTemplate(this.profile.name, this.profile.shortName); + return this.getProfileTemplate(this.profile.name, this.profile.icon); } } diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts index 33757af0467cb7..e454c34038ad9b 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts @@ -6,6 +6,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationError } from 'vs/base/common/errors'; import { Disposable } from 'vs/base/common/lifecycle'; +import { equals } from 'vs/base/common/objects'; import { localize } from 'vs/nls'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -146,29 +147,35 @@ export class UserDataProfileManagementService extends Disposable implements IUse private async changeCurrentProfile(profile: IUserDataProfile, reloadMessage?: string): Promise { const isRemoteWindow = !!this.environmentService.remoteAuthority; - if (!isRemoteWindow) { - if (!(await this.extensionService.stopExtensionHosts(localize('switch profile', "Switching to a profile.")))) { - // If extension host did not stop, do not switch profile - if (this.userDataProfilesService.profiles.some(p => p.id === this.userDataProfileService.currentProfile.id)) { - await this.userDataProfilesService.setProfileForWorkspace(toWorkspaceIdentifier(this.workspaceContextService.getWorkspace()), this.userDataProfileService.currentProfile); + const shouldRestartExtensionHosts = this.userDataProfileService.currentProfile.id !== profile.id || !equals(this.userDataProfileService.currentProfile.useDefaultFlags, profile.useDefaultFlags); + + if (shouldRestartExtensionHosts) { + if (!isRemoteWindow) { + if (!(await this.extensionService.stopExtensionHosts(localize('switch profile', "Switching to a profile.")))) { + // If extension host did not stop, do not switch profile + if (this.userDataProfilesService.profiles.some(p => p.id === this.userDataProfileService.currentProfile.id)) { + await this.userDataProfilesService.setProfileForWorkspace(toWorkspaceIdentifier(this.workspaceContextService.getWorkspace()), this.userDataProfileService.currentProfile); + } + throw new CancellationError(); } - throw new CancellationError(); } } // In a remote window update current profile before reloading so that data is preserved from current profile if asked to preserve await this.userDataProfileService.updateCurrentProfile(profile); - if (isRemoteWindow) { - const { confirmed } = await this.dialogService.confirm({ - message: reloadMessage ?? localize('reload message', "Switching a profile requires reloading VS Code."), - primaryButton: localize('reload button', "&&Reload"), - }); - if (confirmed) { - await this.hostService.reload(); + if (shouldRestartExtensionHosts) { + if (isRemoteWindow) { + const { confirmed } = await this.dialogService.confirm({ + message: reloadMessage ?? localize('reload message', "Switching a profile requires reloading VS Code."), + primaryButton: localize('reload button', "&&Reload"), + }); + if (confirmed) { + await this.hostService.reload(); + } + } else { + await this.extensionService.startExtensionHosts(); } - } else { - await this.extensionService.startExtensionHosts(); } } } diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfileIcons.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfileIcons.ts index 5864da3bd62c03..ca64cfa4ef1594 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfileIcons.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfileIcons.ts @@ -4,8 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { Codicon } from 'vs/base/common/codicons'; +import { localize } from 'vs/nls'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; -export const DEFAULT_ICON = Codicon.settingsGear; +export const DEFAULT_ICON = registerIcon('settings-view-bar-icon', Codicon.settingsGear, localize('settingsViewBarIcon', "Settings icon in the view bar.")); export const ICONS = [ diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts index 82ba6f40652768..b589c0c52569f1 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts @@ -19,7 +19,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { localize } from 'vs/nls'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { URI } from 'vs/base/common/uri'; @@ -39,6 +39,8 @@ import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/envir import { IUserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit'; import { ISecretStorageService } from 'vs/platform/secrets/common/secrets'; import { IFileService } from 'vs/platform/files/common/files'; +import { escapeRegExpCharacters } from 'vs/base/common/strings'; +import { IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines'; type AccountQuickPickItem = { label: string; authenticationProvider: IAuthenticationProvider; account?: UserDataSyncAccount; description?: string }; @@ -115,6 +117,8 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat @IEditorService private readonly editorService: IEditorService, @IUserDataInitializationService private readonly userDataInitializationService: IUserDataInitializationService, @IFileService private readonly fileService: IFileService, + @IFileDialogService private readonly fileDialogService: IFileDialogService, + @IUserDataSyncMachinesService private readonly userDataSyncMachinesService: IUserDataSyncMachinesService, ) { super(); this.syncEnablementContext = CONTEXT_SYNC_ENABLEMENT.bindTo(contextKeyService); @@ -477,15 +481,50 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat await this.viewsService.openViewContainer(SYNC_VIEW_CONTAINER_ID); } - async downloadSyncActivity(location: URI): Promise { - await Promise.all([ - this.userDataSyncService.saveRemoteActivityData(this.uriIdentityService.extUri.joinPath(location, 'remoteActivity.json')), - (async () => { - const logResources = await this.getAllLogResources(); - await Promise.all(logResources.map(async logResource => this.fileService.copy(logResource, this.uriIdentityService.extUri.joinPath(location, 'logs', `${this.uriIdentityService.extUri.basename(this.uriIdentityService.extUri.dirname(logResource))}.log`)))); - })(), - this.fileService.copy(this.environmentService.userDataSyncHome, this.uriIdentityService.extUri.joinPath(location, 'localActivity')), - ]); + async downloadSyncActivity(): Promise { + const result = await this.fileDialogService.showOpenDialog({ + title: localize('download sync activity dialog title', "Select folder to download Settings Sync activity"), + canSelectFiles: false, + canSelectFolders: true, + canSelectMany: false, + openLabel: localize('download sync activity dialog open label', "Save"), + }); + + if (!result?.[0]) { + return; + } + + return this.progressService.withProgress({ location: ProgressLocation.Window }, async () => { + const machines = await this.userDataSyncMachinesService.getMachines(); + const currentMachine = machines.find(m => m.isCurrent); + const name = (currentMachine ? currentMachine.name + ' - ' : '') + 'Settings Sync Activity'; + const stat = await this.fileService.resolve(result[0]); + + const nameRegEx = new RegExp(`${escapeRegExpCharacters(name)}\\s(\\d+)`); + const indexes: number[] = []; + for (const child of stat.children ?? []) { + if (child.name === name) { + indexes.push(0); + } else { + const matches = nameRegEx.exec(child.name); + if (matches) { + indexes.push(parseInt(matches[1])); + } + } + } + indexes.sort((a, b) => a - b); + + const folder = this.uriIdentityService.extUri.joinPath(result[0], indexes[0] !== 0 ? name : `${name} ${indexes[indexes.length - 1] + 1}`); + await Promise.all([ + this.userDataSyncService.saveRemoteActivityData(this.uriIdentityService.extUri.joinPath(folder, 'remoteActivity.json')), + (async () => { + const logResources = await this.getAllLogResources(); + await Promise.all(logResources.map(async logResource => this.fileService.copy(logResource, this.uriIdentityService.extUri.joinPath(folder, 'logs', `${this.uriIdentityService.extUri.basename(this.uriIdentityService.extUri.dirname(logResource))}.log`)))); + })(), + this.fileService.copy(this.environmentService.userDataSyncHome, this.uriIdentityService.extUri.joinPath(folder, 'localActivity')), + ]); + return folder; + }); } private async waitForActiveSyncViews(): Promise { diff --git a/src/vs/workbench/services/userDataSync/common/userDataSync.ts b/src/vs/workbench/services/userDataSync/common/userDataSync.ts index 0bc33984d5194f..1aeb8181041eae 100644 --- a/src/vs/workbench/services/userDataSync/common/userDataSync.ts +++ b/src/vs/workbench/services/userDataSync/common/userDataSync.ts @@ -6,12 +6,14 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IAuthenticationProvider, SyncStatus, SyncResource, IUserDataSyncResource, IResourcePreview } from 'vs/platform/userDataSync/common/userDataSync'; import { Event } from 'vs/base/common/event'; -import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { localize } from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import { Codicon } from 'vs/base/common/codicons'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { IView } from 'vs/workbench/common/views'; +import { Categories } from 'vs/platform/action/common/actionCommonCategories'; +import { IAction2Options } from 'vs/platform/actions/common/actions'; export interface IUserDataSyncAccount { readonly authenticationProviderId: string; @@ -45,7 +47,7 @@ export interface IUserDataSyncWorkbenchService { accept(resource: IUserDataSyncResource, conflictResource: URI, content: string | null | undefined, apply: boolean): Promise; getAllLogResources(): Promise; - downloadSyncActivity(location: URI): Promise; + downloadSyncActivity(): Promise; } export function getSyncAreaLabel(source: SyncResource): string { @@ -90,3 +92,11 @@ export const SHOW_SYNC_LOG_COMMAND_ID = 'workbench.userDataSync.actions.showLog' // VIEWS export const SYNC_VIEW_CONTAINER_ID = 'workbench.view.sync'; export const SYNC_CONFLICTS_VIEW_ID = 'workbench.views.sync.conflicts'; + +export const DOWNLOAD_ACTIVITY_ACTION_DESCRIPTOR: Readonly = { + id: 'workbench.userDataSync.actions.downloadSyncActivity', + title: { original: 'Download Settings Sync Activity', value: localize('download sync activity title', "Download Settings Sync Activity") }, + category: Categories.Developer, + f1: true, + precondition: ContextKeyExpr.and(CONTEXT_ACCOUNT_STATE.isEqualTo(AccountStatus.Available), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)) +}; diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index 72cae3c1ca4408..6284ca1aee423f 100644 --- a/src/vs/workbench/services/views/browser/viewDescriptorService.ts +++ b/src/vs/workbench/services/views/browser/viewDescriptorService.ts @@ -47,6 +47,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor private readonly viewContainerModels = this._register(new DisposableMap()); private readonly viewsVisibilityActionDisposables = this._register(new DisposableMap()); + private canRegisterViewsVisibilityActions: boolean = false; private readonly activeViewContextKeys: Map>; private readonly movableViewContextKeys: Map>; private readonly defaultViewLocationContextKeys: Map>; @@ -202,7 +203,10 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor this.saveViewCustomizations(); // Register visibility actions for all views - this.registerViewsVisibilityActions(); + for (const [key, value] of this.viewContainerModels) { + this.registerViewsVisibilityActions(key, value); + } + this.canRegisterViewsVisibilityActions = true; } private onDidRegisterViews(views: { views: IViewDescriptor[]; viewContainer: ViewContainer }[]): void { @@ -697,7 +701,8 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor disposables.add(this.registerResetViewContainerAction(viewContainer)); - this.viewContainerModels.set(viewContainer, { viewContainerModel: viewContainerModel, disposables, dispose: () => disposables.dispose() }); + const value = { viewContainerModel: viewContainerModel, disposables, dispose: () => disposables.dispose() }; + this.viewContainerModels.set(viewContainer, value); // Register all views that were statically registered to this container // Potentially, this is registering something that was handled by another container @@ -712,6 +717,10 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor viewsToRegister.forEach(viewDescriptor => this.getOrCreateMovableViewContextKey(viewDescriptor).set(!!viewDescriptor.canMoveView)); }); } + + if (this.canRegisterViewsVisibilityActions) { + this.registerViewsVisibilityActions(viewContainer, value); + } } return viewContainerModel; @@ -719,6 +728,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor private onDidDeregisterViewContainer(viewContainer: ViewContainer): void { this.viewContainerModels.deleteAndDispose(viewContainer); + this.viewsVisibilityActionDisposables.deleteAndDispose(viewContainer); } private onDidChangeActiveViews({ added, removed }: { added: ReadonlyArray; removed: ReadonlyArray }): void { @@ -735,16 +745,14 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor }); } - private registerViewsVisibilityActions(): void { - for (const [viewContainer, { viewContainerModel, disposables }] of this.viewContainerModels) { - this.viewsVisibilityActionDisposables.set(viewContainer, this.registerViewsVisibilityActionsForContainer(viewContainerModel)); - disposables.add(Event.any( - viewContainerModel.onDidChangeActiveViewDescriptors, - viewContainerModel.onDidAddVisibleViewDescriptors, - viewContainerModel.onDidRemoveVisibleViewDescriptors, - viewContainerModel.onDidMoveVisibleViewDescriptors - )(e => this.viewsVisibilityActionDisposables.set(viewContainer, this.registerViewsVisibilityActionsForContainer(viewContainerModel)))); - } + private registerViewsVisibilityActions(viewContainer: ViewContainer, { viewContainerModel, disposables }: { viewContainerModel: ViewContainerModel; disposables: DisposableStore }): void { + this.viewsVisibilityActionDisposables.set(viewContainer, this.registerViewsVisibilityActionsForContainer(viewContainerModel)); + disposables.add(Event.any( + viewContainerModel.onDidChangeActiveViewDescriptors, + viewContainerModel.onDidAddVisibleViewDescriptors, + viewContainerModel.onDidRemoveVisibleViewDescriptors, + viewContainerModel.onDidMoveVisibleViewDescriptors + )(e => this.viewsVisibilityActionDisposables.set(viewContainer, this.registerViewsVisibilityActionsForContainer(viewContainerModel)))); } private registerViewsVisibilityActionsForContainer(viewContainerModel: ViewContainerModel): IDisposable { diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index e5db24245478b7..62a4f1719456a0 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -249,6 +249,7 @@ export function workbenchInstantiationService( const environmentService = overrides?.environmentService ? overrides.environmentService(instantiationService) : TestEnvironmentService; instantiationService.stub(IEnvironmentService, environmentService); instantiationService.stub(IWorkbenchEnvironmentService, environmentService); + instantiationService.stub(ILogService, new NullLogService()); const contextKeyService = overrides?.contextKeyService ? overrides.contextKeyService(instantiationService) : instantiationService.createInstance(MockContextKeyService); instantiationService.stub(IContextKeyService, contextKeyService); instantiationService.stub(IProgressService, new TestProgressService()); @@ -306,7 +307,6 @@ export function workbenchInstantiationService( instantiationService.stub(IHostService, instantiationService.createInstance(TestHostService)); instantiationService.stub(ITextModelService, disposables.add(instantiationService.createInstance(TextModelResolverService))); instantiationService.stub(ILoggerService, disposables.add(new TestLoggerService(TestEnvironmentService.logsHome))); - instantiationService.stub(ILogService, new NullLogService()); const editorGroupService = new TestEditorGroupsService([new TestEditorGroupView(0)]); instantiationService.stub(IEditorGroupsService, editorGroupService); instantiationService.stub(ILabelService, disposables.add(instantiationService.createInstance(LabelService))); diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 01041abd0d5c2b..c7e3960445e100 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -55,7 +55,6 @@ import 'vs/workbench/services/keybinding/electron-sandbox/nativeKeyboardLayout'; import 'vs/workbench/services/path/electron-sandbox/pathService'; import 'vs/workbench/services/themes/electron-sandbox/nativeHostColorSchemeService'; import 'vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementService'; -import 'vs/workbench/services/extensionManagement/electron-sandbox/extensionUrlTrustService'; import 'vs/workbench/services/encryption/electron-sandbox/encryptionService'; import 'vs/workbench/services/secrets/electron-sandbox/secretStorageService'; import 'vs/workbench/services/localization/electron-sandbox/languagePackService'; diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index d6109358040c1c..3b8c28f5937afd 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -41,7 +41,6 @@ import 'vs/workbench/services/keybinding/browser/keyboardLayoutService'; import 'vs/workbench/services/extensions/browser/extensionService'; import 'vs/workbench/services/extensionManagement/browser/webExtensionsScannerService'; import 'vs/workbench/services/extensionManagement/common/extensionManagementServerService'; -import 'vs/workbench/services/extensionManagement/browser/extensionUrlTrustService'; import 'vs/workbench/services/telemetry/browser/telemetryService'; import 'vs/workbench/services/url/browser/urlService'; import 'vs/workbench/services/update/browser/updateService'; diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 53e139a16718d1..55b547351058ae 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -10072,7 +10072,8 @@ declare module 'vscode' { export const onDidChangeTelemetryEnabled: Event; /** - * An {@link Event} which fires when the default shell changes. + * An {@link Event} which fires when the default shell changes. This fires with the new + * shell path. */ export const onDidChangeShell: Event; diff --git a/test/unit/electron/renderer.js b/test/unit/electron/renderer.js index b7e785a037d88b..704b53cf050c40 100644 --- a/test/unit/electron/renderer.js +++ b/test/unit/electron/renderer.js @@ -89,6 +89,7 @@ Object.assign(globalThis, { __mkdirPInTests: path => fs.promises.mkdir(path, { recursive: true }), }); +const IS_CI = !!process.env.BUILD_ARTIFACTSTAGINGDIRECTORY; const _tests_glob = '**/test/**/*.test.js'; let loader; let _out; @@ -172,14 +173,20 @@ function loadTests(opts) { //#region Unexpected Output - const _allowedTestOutput = new Set([ - 'The vm module of Node.js is deprecated in the renderer process and will be removed.', - ]); + const _allowedTestOutput = [ + /The vm module of Node\.js is deprecated in the renderer process and will be removed./, + ]; + + // allow snapshot mutation messages locally + if (!IS_CI) { + _allowedTestOutput.push(/Creating new snapshot in/); + _allowedTestOutput.push(/Deleting [0-9]+ old snapshots/); + } const _allowedTestsWithOutput = new Set([ - 'creates a snapshot', // https://github.com/microsoft/vscode/issues/192439 - 'validates a snapshot', // https://github.com/microsoft/vscode/issues/192439 - 'cleans up old snapshots', // https://github.com/microsoft/vscode/issues/192439 + 'creates a snapshot', // self-testing + 'validates a snapshot', // self-testing + 'cleans up old snapshots', // self-testing 'issue #149412: VS Code hangs when bad semantic token data is received', // https://github.com/microsoft/vscode/issues/192440 'issue #134973: invalid semantic tokens should be handled better', // https://github.com/microsoft/vscode/issues/192440 'issue #148651: VSCode UI process can hang if a semantic token with negative values is returned by language service', // https://github.com/microsoft/vscode/issues/192440 @@ -194,7 +201,7 @@ function loadTests(opts) { for (const consoleFn of [console.log, console.error, console.info, console.warn, console.trace, console.debug]) { console[consoleFn.name] = function (msg) { - if (!_allowedTestOutput.has(msg) && !_allowedTestsWithOutput.has(currentTestTitle)) { + if (!_allowedTestOutput.some(a => a.test(msg)) && !_allowedTestsWithOutput.has(currentTestTitle)) { _testsWithUnexpectedOutput = true; consoleFn.apply(console, arguments); } @@ -242,7 +249,7 @@ function loadTests(opts) { process.on('uncaughtException', error => onUnexpectedError(error)); process.on('unhandledRejection', (reason, promise) => { onUnexpectedError(reason); - promise.catch(() => {}); + promise.catch(() => { }); }); window.addEventListener('unhandledrejection', event => { event.preventDefault(); // Do not log to test output, we show an error later when test ends diff --git a/yarn.lock b/yarn.lock index 3f2fb06aa84bb8..ff94a6b3053317 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3574,10 +3574,10 @@ electron-to-chromium@^1.4.202: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.207.tgz#9c3310ebace2952903d05dcaba8abe3a4ed44c01" integrity sha512-piH7MJDJp4rJCduWbVvmUd59AUne1AFBJ8JaRQvk0KzNTSUnZrVXHCZc+eg+CGE4OujkcLJznhGKD6tuAshj5Q== -electron@25.8.2: - version "25.8.2" - resolved "https://registry.yarnpkg.com/electron/-/electron-25.8.2.tgz#5e8ea742b68a7d1427bf78902ec40a5b7231a58b" - integrity sha512-AM1ra6b16EQuO1bJtiA8ZiWqqFLLgVfxD56ykiy+EA5C63Hkx8OmIbe+5JAsLiTwRVvBZ4oCAj6wa2qT+iq8ww== +electron@25.8.4: + version "25.8.4" + resolved "https://registry.yarnpkg.com/electron/-/electron-25.8.4.tgz#b50877aac7d96323920437baf309ad86382cb455" + integrity sha512-hUYS3RGdaa6E1UWnzeGnsdsBYOggwMMg4WGxNGvAoWtmRrr6J1BsjFW/yRq4WsJHJce2HdzQXtz4OGXV6yUCLg== dependencies: "@electron/get" "^2.0.0" "@types/node" "^18.11.18" @@ -10695,10 +10695,10 @@ xterm-addon-image@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.26: - version "0.14.0-beta.26" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.14.0-beta.26.tgz#2b5b8af31613c896d354c4799624090b11cd601c" - integrity sha512-CghsGO7fJa0efClbgZH20lh/JUaQYgJ1AJTPm8luc/eDc6DWOJblU0MxIABclLgT8lagv9+sOQfO0VIkAITxig== +xterm-addon-search@0.14.0-beta.27: + version "0.14.0-beta.27" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.14.0-beta.27.tgz#b6f81eac5047253a5c664349c47498a81b6ec168" + integrity sha512-T4Exwf/rqoLHqGUUIta5Pw/i9PljvroZwLxc7RnVyDqpNsTifDn3675kS54CxwqPlv4owFhxujTDzJPCUEkM2A== xterm-addon-serialize@0.12.0-beta.26: version "0.12.0-beta.26"